diff --git a/examples/dodge-the-creeps/rust/src/hud.rs b/examples/dodge-the-creeps/rust/src/hud.rs index cd474d185..646e61a63 100644 --- a/examples/dodge-the-creeps/rust/src/hud.rs +++ b/examples/dodge-the-creeps/rust/src/hud.rs @@ -61,7 +61,7 @@ impl Hud { } #[godot_api] -impl CanvasLayerVirtual for Hud { +impl GodotExt for Hud { fn init(base: Base) -> Self { Self { base } } diff --git a/examples/dodge-the-creeps/rust/src/main_scene.rs b/examples/dodge-the-creeps/rust/src/main_scene.rs index 4c86a5802..c528e7496 100644 --- a/examples/dodge-the-creeps/rust/src/main_scene.rs +++ b/examples/dodge-the-creeps/rust/src/main_scene.rs @@ -126,7 +126,7 @@ impl Main { } #[godot_api] -impl NodeVirtual for Main { +impl GodotExt for Main { fn init(base: Base) -> Self { Main { mob_scene: PackedScene::new(), diff --git a/examples/dodge-the-creeps/rust/src/mob.rs b/examples/dodge-the-creeps/rust/src/mob.rs index 8fca5b141..4227b5f87 100644 --- a/examples/dodge-the-creeps/rust/src/mob.rs +++ b/examples/dodge-the-creeps/rust/src/mob.rs @@ -47,7 +47,7 @@ impl Mob { } #[godot_api] -impl RigidBody2DVirtual for Mob { +impl GodotExt for Mob { fn init(base: Base) -> Self { Mob { min_speed: 150.0, diff --git a/examples/dodge-the-creeps/rust/src/player.rs b/examples/dodge-the-creeps/rust/src/player.rs index 72b3d9ee1..07bcc8a4b 100644 --- a/examples/dodge-the-creeps/rust/src/player.rs +++ b/examples/dodge-the-creeps/rust/src/player.rs @@ -42,7 +42,7 @@ impl Player { } #[godot_api] -impl Area2DVirtual for Player { +impl GodotExt for Player { fn init(base: Base) -> Self { Player { speed: 400.0, diff --git a/godot-core/src/obj/traits.rs b/godot-core/src/obj/traits.rs index b588c966a..239a1e821 100644 --- a/godot-core/src/obj/traits.rs +++ b/godot-core/src/obj/traits.rs @@ -50,6 +50,9 @@ pub trait Share { fn share(&self) -> Self; } +// Marker trait that exists only for documentation purposes. +// pub trait GodotExt : private::Sealed {} + /// Non-strict inheritance relationship in the Godot class hierarchy. /// /// `Derived: Inherits` means that either `Derived` is a subclass of `Base`, or the class `Base` itself (hence "non-strict"). @@ -164,7 +167,7 @@ pub mod cap { fn __register_exports(); } - /// Auto-implemented for `#[godot_api] impl ***Virtual for MyClass` blocks + /// Auto-implemented for `#[godot_api] impl GodotExt for MyClass` blocks pub trait ImplementsGodotVirtual: GodotClass { #[doc(hidden)] fn __virtual_call(_name: &str) -> sys::GDExtensionClassCallVirtual; diff --git a/godot-macros/src/derive_godot_class.rs b/godot-macros/src/derive_godot_class.rs index 813a8388d..0aa2cafc3 100644 --- a/godot-macros/src/derive_godot_class.rs +++ b/godot-macros/src/derive_godot_class.rs @@ -22,6 +22,9 @@ pub fn transform(decl: Declaration) -> ParseResult { let class_name = &class.name; let class_name_str = class.name.to_string(); let inherits_macro = format_ident!("inherits_transitive_{}", base_ty); + let virtual_trait = format_ident!("{}Virtual", base_ty); + let virtual_impl_macro = format_ident!("__gdext_virtual_impl_{}", class_name); + let virtual_call_macro = format_ident!("__gdext_virtual_call_{}", class_name); let prv = quote! { ::godot::private }; let deref_impl = make_deref_impl(class_name, &fields); @@ -60,6 +63,28 @@ pub fn transform(decl: Declaration) -> ParseResult { }); #prv::class_macros::#inherits_macro!(#class_name); + + #[allow(non_snake_case)] + macro_rules! #virtual_impl_macro { + ( impl GodotExt for $Class:ident { $($tt:tt)* } ) => { + impl #virtual_trait for $Class { $($tt)* } + }; + + ( impl $Trait:ident for $Class:ident { $($tt:tt)* } ) => { + impl $Trait for $Class { $($tt)* } + }; + } + + #[allow(non_snake_case)] + macro_rules! #virtual_call_macro { + ( :: $function:ident( $($args:expr)* ) ) => { + :: $function( $($args)* ) + }; + + ( :: $function:ident( $($args:expr)* ) ) => { + :: $function( $($args)* ) + }; + } }) } diff --git a/godot-macros/src/godot_api.rs b/godot-macros/src/godot_api.rs index 9c7c5a52d..1dc558b19 100644 --- a/godot-macros/src/godot_api.rs +++ b/godot-macros/src/godot_api.rs @@ -6,9 +6,9 @@ use crate::util; use crate::util::bail; -use proc_macro2::{Ident, TokenStream}; -use quote::quote; -use venial::{AttributeValue, Declaration, Error, Function, Impl, ImplMember}; +use proc_macro2::{Ident, TokenStream, TokenTree}; +use quote::{format_ident, quote}; +use venial::{AttributeValue, Declaration, Error, Function, Impl, ImplMember, TyExpr}; pub fn transform(input_decl: Declaration) -> Result { let decl = match input_decl { @@ -211,8 +211,13 @@ fn extract_attributes(method: &Function) -> Result, Error> { // ---------------------------------------------------------------------------------------------------------------------------------------------- /// Codegen for `#[godot_api] impl GodotExt for MyType` -fn transform_trait_impl(original_impl: Impl) -> Result { +fn transform_trait_impl(mut original_impl: Impl) -> Result { let (class_name, trait_name) = util::validate_trait_impl_virtual(&original_impl, "godot_api")?; + original_impl.trait_ty = Some(TyExpr { + tokens: vec![TokenTree::Ident(trait_name.clone())], + }); + let original_impl = original_impl; // de-mutate + let class_name_str = class_name.to_string(); let mut godot_init_impl = TokenStream::new(); @@ -225,6 +230,8 @@ fn transform_trait_impl(original_impl: Impl) -> Result { let mut virtual_methods = vec![]; let mut virtual_method_names = vec![]; + let virtual_impl_macro = format_ident!("__gdext_virtual_impl_{}", class_name); + let virtual_call_macro = format_ident!("__gdext_virtual_call_{}", class_name); let prv = quote! { ::godot::private }; @@ -241,21 +248,23 @@ fn transform_trait_impl(original_impl: Impl) -> Result { register_class_impl = quote! { impl ::godot::obj::cap::GodotRegisterClass for #class_name { fn __godot_register_class(builder: &mut ::godot::builder::GodotBuilder) { - ::register_class(builder) + #virtual_call_macro! { ::register_class(builder) } } } }; - register_fn = quote! { Some(#prv::ErasedRegisterFn { - raw: #prv::callbacks::register_class_by_builder::<#class_name> - }) }; + register_fn = quote! { + Some(#prv::ErasedRegisterFn { + raw: #prv::callbacks::register_class_by_builder::<#class_name> + }) + }; } "init" => { godot_init_impl = quote! { impl ::godot::obj::cap::GodotInit for #class_name { fn __godot_init(base: ::godot::obj::Base) -> Self { - ::init(base) + #virtual_call_macro! { ::init(base) } } } }; @@ -266,7 +275,7 @@ fn transform_trait_impl(original_impl: Impl) -> Result { to_string_impl = quote! { impl ::godot::obj::cap::GodotToString for #class_name { fn __godot_to_string(&self) -> ::godot::builtin::GodotString { - ::to_string(self) + #virtual_call_macro! { ::to_string(self) } } } }; @@ -297,7 +306,9 @@ fn transform_trait_impl(original_impl: Impl) -> Result { } let result = quote! { - #original_impl + #[doc(hidden)] + #virtual_impl_macro!(#original_impl); + #godot_init_impl #to_string_impl #register_class_impl diff --git a/godot-macros/src/lib.rs b/godot-macros/src/lib.rs index 35eea339c..b622752da 100644 --- a/godot-macros/src/lib.rs +++ b/godot-macros/src/lib.rs @@ -21,6 +21,8 @@ pub fn derive_native_class(input: TokenStream) -> TokenStream { translate(input, derive_godot_class::transform) } +/// Attribute to annotate `impl` blocks. +#[doc(alias = "GodotExt")] #[proc_macro_attribute] pub fn godot_api(_meta: TokenStream, input: TokenStream) -> TokenStream { translate(input, godot_api::transform) diff --git a/godot-macros/src/util.rs b/godot-macros/src/util.rs index 642c41a38..86b8c470f 100644 --- a/godot-macros/src/util.rs +++ b/godot-macros/src/util.rs @@ -380,8 +380,9 @@ pub(crate) fn validate_impl( validate_self(original_impl, attr) } -/// Validates that the declaration is the of the form `impl Trait for -/// SomeType`, where the name of `Trait` ends in `Virtual`. +/// Validates that the declaration is the of the form `impl Trait for SomeType`, where the name of `Trait` ends in `Virtual`. +/// +/// Returns (class_name, trait_name). pub(crate) fn validate_trait_impl_virtual( original_impl: &Impl, attr: &str, @@ -390,10 +391,9 @@ pub(crate) fn validate_trait_impl_virtual( let typename = extract_typename(trait_name); // Validate trait - if !typename - .as_ref() - .map_or(false, |seg| seg.ident.to_string().ends_with("Virtual")) - { + if !typename.as_ref().map_or(false, |seg| { + seg.ident == "GodotExt" || seg.ident.to_string().ends_with("Virtual") + }) { return bail( format!("#[{attr}] for trait impls requires a virtual method trait (trait name should end in 'Virtual')"), original_impl, @@ -407,6 +407,9 @@ pub(crate) fn validate_trait_impl_virtual( }) } +/// Check the `MyType` (self) part of `impl Trait for MyType`. +/// +/// Returns the type name of `Self`, or error. fn validate_self(original_impl: &Impl, attr: &str) -> ParseResult { if let Some(segment) = extract_typename(&original_impl.self_ty) { if segment.generic_args.is_none() { diff --git a/itest/rust/src/codegen_test.rs b/itest/rust/src/codegen_test.rs index 14a7fd0dc..20f35b44b 100644 --- a/itest/rust/src/codegen_test.rs +++ b/itest/rust/src/codegen_test.rs @@ -60,7 +60,7 @@ pub struct TestBaseRenamed { } #[godot_api] -impl HttpRequestVirtual for TestBaseRenamed { +impl GodotExt for TestBaseRenamed { fn init(base: Base) -> Self { TestBaseRenamed { base } } diff --git a/itest/rust/src/export_test.rs b/itest/rust/src/export_test.rs index a2ded88d4..a8341a3dd 100644 --- a/itest/rust/src/export_test.rs +++ b/itest/rust/src/export_test.rs @@ -75,7 +75,7 @@ impl HasProperty { } #[godot_api] -impl NodeVirtual for HasProperty { +impl GodotExt for HasProperty { fn init(base: Base) -> Self { HasProperty { int_val: 0, diff --git a/itest/rust/src/object_test.rs b/itest/rust/src/object_test.rs index b7c160ccc..474a2e161 100644 --- a/itest/rust/src/object_test.rs +++ b/itest/rust/src/object_test.rs @@ -637,7 +637,7 @@ pub struct ObjPayload { } #[godot_api] -impl RefCountedVirtual for ObjPayload { +impl GodotExt for ObjPayload { fn init(_base: Base) -> Self { Self { value: 111 } } diff --git a/itest/rust/src/virtual_methods_test.rs b/itest/rust/src/virtual_methods_test.rs index 2bc687172..b265663a4 100644 --- a/itest/rust/src/virtual_methods_test.rs +++ b/itest/rust/src/virtual_methods_test.rs @@ -35,7 +35,7 @@ struct VirtualMethodTest { impl VirtualMethodTest {} #[godot_api] -impl RefCountedVirtual for VirtualMethodTest { +impl GodotExt for VirtualMethodTest { fn to_string(&self) -> GodotString { format!("VirtualMethodTest[integer={}]", self.integer).into() } @@ -50,7 +50,7 @@ struct ReadyVirtualTest { } #[godot_api] -impl Node2DVirtual for ReadyVirtualTest { +impl GodotExt for ReadyVirtualTest { fn init(base: Base) -> Self { ReadyVirtualTest { some_base: base, @@ -73,7 +73,7 @@ struct TreeVirtualTest { } #[godot_api] -impl Node2DVirtual for TreeVirtualTest { +impl GodotExt for TreeVirtualTest { fn init(base: Base) -> Self { TreeVirtualTest { some_base: base,