diff --git a/examples/dodge-the-creeps/rust/src/hud.rs b/examples/dodge-the-creeps/rust/src/hud.rs index 2af5f1eaf..cd474d185 100644 --- a/examples/dodge-the-creeps/rust/src/hud.rs +++ b/examples/dodge-the-creeps/rust/src/hud.rs @@ -1,4 +1,4 @@ -use godot::engine::{Button, CanvasLayer, Label, Timer}; +use godot::engine::{Button, CanvasLayer, CanvasLayerVirtual, Label, Timer}; use godot::prelude::*; #[derive(GodotClass)] @@ -61,7 +61,7 @@ impl Hud { } #[godot_api] -impl GodotExt for Hud { +impl CanvasLayerVirtual 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 157767eed..1349d1a7e 100644 --- a/examples/dodge-the-creeps/rust/src/main_scene.rs +++ b/examples/dodge-the-creeps/rust/src/main_scene.rs @@ -3,7 +3,7 @@ use crate::mob; use crate::player; use godot::engine::node::InternalMode; use godot::engine::packed_scene::GenEditState; -use godot::engine::{Marker2D, PathFollow2D, RigidBody2D, Timer}; +use godot::engine::{Marker2D, NodeVirtual, PathFollow2D, RigidBody2D, Timer}; use godot::prelude::*; use rand::Rng as _; use std::f64::consts::PI; @@ -126,7 +126,7 @@ impl Main { } #[godot_api] -impl GodotExt for Main { +impl NodeVirtual 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 45374dffc..472381aae 100644 --- a/examples/dodge-the-creeps/rust/src/mob.rs +++ b/examples/dodge-the-creeps/rust/src/mob.rs @@ -1,4 +1,4 @@ -use godot::engine::{AnimatedSprite2D, RigidBody2D}; +use godot::engine::{AnimatedSprite2D, RigidBody2D, RigidBody2DVirtual}; use godot::prelude::*; use rand::seq::SliceRandom; @@ -47,7 +47,7 @@ impl Mob { } #[godot_api] -impl GodotExt for Mob { +impl RigidBody2DVirtual 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 c933ed41e..81bc957a4 100644 --- a/examples/dodge-the-creeps/rust/src/player.rs +++ b/examples/dodge-the-creeps/rust/src/player.rs @@ -1,4 +1,4 @@ -use godot::engine::{AnimatedSprite2D, Area2D, CollisionShape2D, PhysicsBody2D}; +use godot::engine::{AnimatedSprite2D, Area2D, Area2DVirtual, CollisionShape2D, PhysicsBody2D}; use godot::prelude::*; #[derive(GodotClass)] @@ -42,7 +42,7 @@ impl Player { } #[godot_api] -impl GodotExt for Player { +impl Area2DVirtual for Player { fn init(base: Base) -> Self { Player { speed: 400.0, diff --git a/godot-codegen/src/api_parser.rs b/godot-codegen/src/api_parser.rs index da5d8643b..aa0140395 100644 --- a/godot-codegen/src/api_parser.rs +++ b/godot-codegen/src/api_parser.rs @@ -167,7 +167,7 @@ pub struct BuiltinClassMethod { pub arguments: Option>, } -#[derive(DeJson)] +#[derive(DeJson, Clone)] pub struct ClassMethod { pub name: String, pub is_const: bool, @@ -200,7 +200,7 @@ pub struct MethodArg { } // Example: get_available_point_id -> {type: "int", meta: "int64"} -#[derive(DeJson)] +#[derive(DeJson, Clone)] pub struct MethodReturn { #[nserde(rename = "type")] pub type_: String, diff --git a/godot-codegen/src/class_generator.rs b/godot-codegen/src/class_generator.rs index b3de7e61b..f23e60fe5 100644 --- a/godot-codegen/src/class_generator.rs +++ b/godot-codegen/src/class_generator.rs @@ -159,6 +159,7 @@ fn make_constructor(class: &Class, ctx: &Context) -> TokenStream { fn make_class(class: &Class, class_name: &TyName, ctx: &mut Context) -> GeneratedClass { // Strings let godot_class_str = &class_name.godot_ty; + let virtual_trait_str = class_name.virtual_trait_name(); // Idents and tokens let base = match class.inherits.as_ref() { @@ -174,6 +175,7 @@ fn make_class(class: &Class, class_name: &TyName, ctx: &mut Context) -> Generate let enums = make_enums(&class.enums, class_name, ctx); let inherits_macro = format_ident!("inherits_transitive_{}", class_name.rust_ty); let all_bases = ctx.inheritance_tree().collect_all_bases(class_name); + let virtual_trait = make_virtual_methods_trait(class, &all_bases, &virtual_trait_str, ctx); let memory = if class_name.rust_ty == "Object" { ident("DynamicRefCount") @@ -199,6 +201,7 @@ fn make_class(class: &Class, class_name: &TyName, ctx: &mut Context) -> Generate pub struct #class_name { object_ptr: sys::GDExtensionObjectPtr, } + #virtual_trait impl #class_name { #constructor #methods @@ -323,12 +326,14 @@ fn make_module_file(classes_and_modules: Vec) -> TokenStre is_pub, .. } = m; + let virtual_trait_name = ident(&class_name.virtual_trait_name()); let vis = is_pub.then_some(quote! { pub }); quote! { #vis mod #module_name; pub use #module_name::re_export::#class_name; + pub use #module_name::re_export::#virtual_trait_name; } }); @@ -463,12 +468,15 @@ fn is_type_excluded(ty: &str, ctx: &mut Context) -> bool { } } -fn is_method_excluded(method: &ClassMethod, #[allow(unused_variables)] ctx: &mut Context) -> bool { +fn is_method_excluded( + method: &ClassMethod, + is_virtual_impl: bool, + #[allow(unused_variables)] ctx: &mut Context, +) -> bool { // Currently excluded: // - // * Private virtual methods designed for override; skip for now - // E.g.: AudioEffectInstance::_process(const void*, AudioFrame*, int) - // TODO decide what to do with them, overriding in a type-safe way? + // * Private virtual methods are only included in a virtual + // implementation. // // * Methods accepting pointers are often supplementary // E.g.: TextServer::font_set_data_ptr() -- in addition to TextServer::font_set_data(). @@ -490,11 +498,14 @@ fn is_method_excluded(method: &ClassMethod, #[allow(unused_variables)] ctx: &mut } // -- end. - method.name.starts_with('_') - || method - .return_value - .as_ref() - .map_or(false, |ret| ret.type_.contains('*')) + if method.name.starts_with('_') && !is_virtual_impl { + return true; + } + + method + .return_value + .as_ref() + .map_or(false, |ret| ret.type_.contains('*')) || method .arguments .as_ref() @@ -523,7 +534,8 @@ fn make_method_definition( class_name: &TyName, ctx: &mut Context, ) -> TokenStream { - if is_method_excluded(method, ctx) || special_cases::is_deleted(class_name, &method.name) { + if is_method_excluded(method, false, ctx) || special_cases::is_deleted(class_name, &method.name) + { return TokenStream::new(); } /*if method.map_args(|args| args.is_empty()) { @@ -768,13 +780,7 @@ fn make_receiver( is_const: bool, receiver_arg: TokenStream, ) -> (TokenStream, TokenStream) { - let receiver = if is_static { - quote! {} - } else if is_const { - quote! { &self, } - } else { - quote! { &mut self, } - }; + let receiver = make_receiver_self_param(is_static, is_const); let receiver_arg = if is_static { quote! { std::ptr::null_mut() } @@ -785,6 +791,16 @@ fn make_receiver( (receiver, receiver_arg) } +fn make_receiver_self_param(is_static: bool, is_const: bool) -> TokenStream { + if is_static { + quote! {} + } else if is_const { + quote! { &self, } + } else { + quote! { &mut self, } + } +} + fn make_params( method_args: &Option>, is_varcall: bool, @@ -891,3 +907,113 @@ fn make_return( (return_decl, call) } + +fn make_virtual_methods_trait( + class: &Class, + all_bases: &[TyName], + trait_name: &str, + ctx: &mut Context, +) -> TokenStream { + let trait_name = ident(trait_name); + + let virtual_method_fns = make_all_virtual_methods(class, all_bases, ctx); + let special_virtual_methods = special_virtual_methods(); + + quote! { + #[allow(unused_variables)] + #[allow(clippy::unimplemented)] + pub trait #trait_name: crate::private::You_forgot_the_attribute__godot_api + crate::obj::GodotClass { + #( #virtual_method_fns )* + #special_virtual_methods + } + } +} + +fn special_virtual_methods() -> TokenStream { + quote! { + fn register_class(builder: &mut crate::builder::ClassBuilder) { + unimplemented!() + } + fn init(base: crate::obj::Base) -> Self { + unimplemented!() + } + fn to_string(&self) -> crate::builtin::GodotString { + unimplemented!() + } + } +} + +fn make_virtual_method(class_method: &ClassMethod, ctx: &mut Context) -> TokenStream { + let method_name = ident(virtual_method_name(class_method)); + + // Virtual methods are never static. + assert!(!class_method.is_static); + + let receiver = make_receiver_self_param(false, class_method.is_const); + let (params, _) = make_params(&class_method.arguments, class_method.is_vararg, ctx); + + quote! { + fn #method_name ( #receiver #( #params , )* ) { + unimplemented!() + } + } +} + +fn make_all_virtual_methods( + class: &Class, + all_bases: &[TyName], + ctx: &mut Context, +) -> Vec { + let mut all_virtuals = vec![]; + let mut extend_virtuals = |class| { + all_virtuals.extend( + get_methods_in_class(class) + .iter() + .cloned() + .filter(|m| m.is_virtual), + ); + }; + + // Get virtuals defined on the current class. + extend_virtuals(class); + // Add virtuals from superclasses. + for base in all_bases { + let superclass = ctx.get_engine_class(base); + extend_virtuals(superclass); + } + all_virtuals + .into_iter() + .filter_map(|method| { + if is_method_excluded(&method, true, ctx) { + None + } else { + Some(make_virtual_method(&method, ctx)) + } + }) + .collect() +} + +fn get_methods_in_class(class: &Class) -> &[ClassMethod] { + match &class.methods { + None => &[], + Some(methods) => methods, + } +} + +fn virtual_method_name(class_method: &ClassMethod) -> &str { + // Matching the C++ convention, we remove the leading underscore + // from virtual method names. + let method_name = class_method + .name + .strip_prefix('_') + .unwrap_or(&class_method.name); + + // As a special exception, a few classes define a virtual method + // called "_init" (distinct from the constructor), so we rename + // those to avoid a name conflict in our trait. + if method_name == "init" { + "init_ext" + } else { + method_name + } +} diff --git a/godot-codegen/src/context.rs b/godot-codegen/src/context.rs index d3525870d..2b19bebef 100644 --- a/godot-codegen/src/context.rs +++ b/godot-codegen/src/context.rs @@ -4,12 +4,13 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +use crate::api_parser::Class; use crate::{ExtensionApi, RustTy, TyName}; use std::collections::{HashMap, HashSet}; #[derive(Default)] pub(crate) struct Context<'a> { - engine_classes: HashSet, + engine_classes: HashMap, builtin_types: HashSet<&'a str>, singletons: HashSet<&'a str>, inheritance_tree: InheritanceTree, @@ -39,7 +40,7 @@ impl<'a> Context<'a> { } println!("-- add engine class {}", class_name.description()); - ctx.engine_classes.insert(class_name.clone()); + ctx.engine_classes.insert(class_name.clone(), class); if let Some(base) = class.inherits.as_ref() { let base_name = TyName::from_godot(base); @@ -50,6 +51,10 @@ impl<'a> Context<'a> { ctx } + pub fn get_engine_class(&self, class_name: &TyName) -> &Class { + self.engine_classes.get(class_name).unwrap() + } + // pub fn is_engine_class(&self, class_name: &str) -> bool { // self.engine_classes.contains(class_name) // } diff --git a/godot-codegen/src/lib.rs b/godot-codegen/src/lib.rs index 02f110e66..f81c33d84 100644 --- a/godot-codegen/src/lib.rs +++ b/godot-codegen/src/lib.rs @@ -197,6 +197,10 @@ impl TyName { format!("{} [renamed {}]", self.godot_ty, self.rust_ty) } } + + fn virtual_trait_name(&self) -> String { + format!("{}Virtual", self.rust_ty) + } } impl ToTokens for TyName { diff --git a/godot-core/src/bind.rs b/godot-core/src/bind.rs deleted file mode 100644 index be74d9f01..000000000 --- a/godot-core/src/bind.rs +++ /dev/null @@ -1,63 +0,0 @@ -/* - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. - */ - -use crate::builder::ClassBuilder; -use crate::builtin::GodotString; -use crate::obj::Base; -use crate::obj::GodotClass; - -/// Extension API for Godot classes, used with `#[godot_api]`. -/// -/// Helps with adding custom functionality: -/// * `init` constructors -/// * `to_string` method -/// * Custom register methods (builder style) -/// * All the lifecycle methods like `ready`, `process` etc. -/// -/// This trait is special in that it needs to be used in combination with the `#[godot_api]` -/// proc-macro attribute to ensure proper registration of its methods. All methods have -/// default implementations, so you can select precisely which functionality you want to have. -/// Those default implementations are never called however, the proc-macro detects what you implement. -/// -/// Do not call any of these methods directly -- they are an interface to Godot. Functionality -/// described here is available through other means (e.g. `init` via `Gd::new_default`). -#[allow(unused_variables)] -#[allow(clippy::unimplemented)] // TODO consider using panic! with specific message, possibly generated code -pub trait GodotExt: crate::private::You_forgot_the_attribute__godot_api -where - Self: GodotClass, -{ - // Note: keep in sync with VIRTUAL_METHOD_NAMES in godot_api.rs - - // Some methods that were called: - // _enter_tree - // _input - // _shortcut_input - // _unhandled_input - // _unhandled_key_input - // _process - // _physics_process - // _ready - - fn register_class(builder: &mut ClassBuilder) {} - - fn init(base: Base) -> Self { - unimplemented!() - } - - fn ready(&mut self) { - unimplemented!() - } - fn process(&mut self, delta: f64) { - unimplemented!() - } - fn physics_process(&mut self, delta: f64) { - unimplemented!() - } - fn to_string(&self) -> GodotString { - unimplemented!() - } -} diff --git a/godot-core/src/lib.rs b/godot-core/src/lib.rs index fd856ede4..c9497ce36 100644 --- a/godot-core/src/lib.rs +++ b/godot-core/src/lib.rs @@ -7,7 +7,6 @@ mod registry; mod storage; -pub mod bind; pub mod builder; pub mod builtin; pub mod init; diff --git a/godot-core/src/obj/traits.rs b/godot-core/src/obj/traits.rs index 748ff1363..0b2e3f5c8 100644 --- a/godot-core/src/obj/traits.rs +++ b/godot-core/src/obj/traits.rs @@ -4,6 +4,8 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +use crate::builder::ClassBuilder; +use crate::builtin::GodotString; use crate::obj::Base; use godot_ffi as sys; @@ -137,6 +139,20 @@ pub mod cap { fn __godot_init(base: Base) -> Self; } + // TODO Evaluate whether we want this public or not + #[doc(hidden)] + pub trait GodotToString: GodotClass { + #[doc(hidden)] + fn __godot_to_string(&self) -> GodotString; + } + + // TODO Evaluate whether we want this public or not + #[doc(hidden)] + pub trait GodotRegisterClass: GodotClass { + #[doc(hidden)] + fn __godot_register_class(builder: &mut ClassBuilder); + } + /// Auto-implemented for `#[godot_api] impl MyClass` blocks pub trait ImplementsGodotApi: GodotClass { #[doc(hidden)] @@ -148,8 +164,8 @@ pub mod cap { fn __register_exports(); } - /// Auto-implemented for `#[godot_api] impl GodotExt for MyClass` blocks - pub trait ImplementsGodotExt: GodotClass { + /// Auto-implemented for `#[godot_api] impl ***Virtual for MyClass` blocks + pub trait ImplementsGodotVirtual: GodotClass { #[doc(hidden)] fn __virtual_call(_name: &str) -> sys::GDExtensionClassCallVirtual; } diff --git a/godot-core/src/registry.rs b/godot-core/src/registry.rs index cc13765ed..8aa8ab390 100644 --- a/godot-core/src/registry.rs +++ b/godot-core/src/registry.rs @@ -13,7 +13,6 @@ use godot_ffi as sys; use sys::interface_fn; -use crate::bind::GodotExt; use crate::builtin::meta::ClassName; use crate::builtin::StringName; use crate::out; @@ -113,7 +112,9 @@ struct ClassRegistrationInfo { } /// Registers a class with static type information. -pub fn register_class() { +pub fn register_class< + T: cap::GodotInit + cap::ImplementsGodotVirtual + cap::GodotToString + cap::GodotRegisterClass, +>() { // TODO: provide overloads with only some trait impls out!("Manually register class {}", std::any::type_name::()); @@ -272,7 +273,6 @@ fn register_class_raw(info: ClassRegistrationInfo) { #[allow(clippy::missing_safety_doc)] pub mod callbacks { use super::*; - use crate::bind::GodotExt; use crate::builder::ClassBuilder; use crate::obj::Base; @@ -326,7 +326,7 @@ pub mod callbacks { let _drop = Box::from_raw(storage as *mut InstanceStorage<_>); } - pub unsafe extern "C" fn get_virtual( + pub unsafe extern "C" fn get_virtual( _class_user_data: *mut std::ffi::c_void, name: sys::GDExtensionConstStringNamePtr, ) -> sys::GDExtensionClassCallVirtual { @@ -338,7 +338,7 @@ pub mod callbacks { T::__virtual_call(method_name.as_str()) } - pub unsafe extern "C" fn to_string( + pub unsafe extern "C" fn to_string( instance: sys::GDExtensionClassInstancePtr, _is_valid: *mut sys::GDExtensionBool, out_string: sys::GDExtensionStringPtr, @@ -348,7 +348,7 @@ pub mod callbacks { let storage = as_storage::(instance); let instance = storage.get(); - let string = ::to_string(&*instance); + let string = ::__godot_to_string(&*instance); // Transfer ownership to Godot, disable destructor string.write_string_sys(out_string); @@ -380,14 +380,14 @@ pub mod callbacks { Box::new(instance) } - pub fn register_class_by_builder(_class_builder: &mut dyn Any) { + pub fn register_class_by_builder(_class_builder: &mut dyn Any) { // TODO use actual argument, once class builder carries state // let class_builder = class_builder // .downcast_mut::>() // .expect("bad type erasure"); let mut class_builder = ClassBuilder::new(); - T::register_class(&mut class_builder); + T::__godot_register_class(&mut class_builder); } pub fn register_user_binds( diff --git a/godot-macros/src/godot_api.rs b/godot-macros/src/godot_api.rs index bff0acbf7..0d2d36af1 100644 --- a/godot-macros/src/godot_api.rs +++ b/godot-macros/src/godot_api.rs @@ -10,9 +10,6 @@ use proc_macro2::{Ident, TokenStream}; use quote::quote; use venial::{AttributeValue, Declaration, Error, Function, Impl, ImplMember}; -// Note: keep in sync with trait GodotExt -const VIRTUAL_METHOD_NAMES: [&str; 3] = ["ready", "process", "physics_process"]; - pub fn transform(input: TokenStream) -> Result { let input_decl = venial::parse_declaration(input)?; let decl = match input_decl { @@ -216,13 +213,17 @@ fn extract_attributes(method: &Function) -> Result, Error> { /// Codegen for `#[godot_api] impl GodotExt for MyType` fn transform_trait_impl(original_impl: Impl) -> Result { - let class_name = util::validate_impl(&original_impl, Some("GodotExt"), "godot_api")?; + let (class_name, trait_name) = util::validate_trait_impl_virtual(&original_impl, "godot_api")?; let class_name_str = class_name.to_string(); let mut godot_init_impl = TokenStream::new(); + let mut to_string_impl = TokenStream::new(); + let mut register_class_impl = TokenStream::new(); + let mut register_fn = quote! { None }; let mut create_fn = quote! { None }; let mut to_string_fn = quote! { None }; + let mut virtual_methods = vec![]; let mut virtual_method_names = vec![]; @@ -238,6 +239,14 @@ fn transform_trait_impl(original_impl: Impl) -> Result { let method_name = method.name.to_string(); match method_name.as_str() { "register_class" => { + register_class_impl = quote! { + impl ::godot::obj::cap::GodotRegisterClass for #class_name { + fn __godot_register_class(builder: &mut ::godot::builder::GodotBuilder) { + ::register_class(builder) + } + } + }; + register_fn = quote! { Some(#prv::ErasedRegisterFn { raw: #prv::callbacks::register_class_by_builder::<#class_name> }) }; @@ -247,7 +256,7 @@ fn transform_trait_impl(original_impl: Impl) -> Result { godot_init_impl = quote! { impl ::godot::obj::cap::GodotInit for #class_name { fn __godot_init(base: ::godot::obj::Base) -> Self { - ::init(base) + ::init(base) } } }; @@ -255,35 +264,48 @@ fn transform_trait_impl(original_impl: Impl) -> Result { } "to_string" => { + to_string_impl = quote! { + impl ::godot::obj::cap::GodotToString for #class_name { + fn __godot_to_string(&self) -> ::godot::builtin::GodotString { + ::to_string(self) + } + } + }; + to_string_fn = quote! { Some(#prv::callbacks::to_string::<#class_name>) }; } // Other virtual methods, like ready, process etc. - known_name if VIRTUAL_METHOD_NAMES.contains(&known_name) => { + _ => { let method = util::reduce_to_signature(method); // Godot-facing name begins with underscore - virtual_method_names.push(format!("_{method_name}")); + // + // Note: godot-codegen special-cases the virtual + // method called _init (which exists on a handful of + // classes, distinct from the default constructor) to + // init_ext, to avoid Rust-side ambiguity. See + // godot_codegen::class_generator::virtual_method_name. + let virtual_method_name = if method_name == "init_ext" { + String::from("_init") + } else { + format!("_{method_name}") + }; + virtual_method_names.push(virtual_method_name); virtual_methods.push(method); } - - // Unknown methods which are declared inside trait impl are not supported (possibly compiler catches those first anyway) - other_name => { - return bail( - format!("Unsupported GodotExt method: {other_name}"), - &method.name, - ) - } } } let result = quote! { #original_impl #godot_init_impl + #to_string_impl + #register_class_impl impl ::godot::private::You_forgot_the_attribute__godot_api for #class_name {} - impl ::godot::obj::cap::ImplementsGodotExt for #class_name { + impl ::godot::obj::cap::ImplementsGodotVirtual for #class_name { fn __virtual_call(name: &str) -> ::godot::sys::GDExtensionClassCallVirtual { //println!("virtual_call: {}.{}", std::any::type_name::(), name); diff --git a/godot-macros/src/util.rs b/godot-macros/src/util.rs index 1f703d5fd..db78c6375 100644 --- a/godot-macros/src/util.rs +++ b/godot-macros/src/util.rs @@ -200,8 +200,20 @@ pub(crate) fn ensure_kv_empty(map: KvMap, span: Span) -> ParseResult<()> { } } +/// Given an impl block for a trait, returns whether that is an impl +/// for a trait with the given name. +/// +/// That is, if `name` is `"MyTrait"`, then this function returns true +/// if and only if `original_impl` is a declaration of the form `impl +/// MyTrait for SomeType`. The type `SomeType` is irrelevant in this +/// example. +pub(crate) fn is_impl_named(original_impl: &Impl, name: &str) -> bool { + let trait_name = original_impl.trait_ty.as_ref().unwrap(); // unwrap: already checked outside + extract_typename(trait_name).map_or(false, |seg| seg.ident == name) +} + /// Validates either: -/// a) the declaration is `impl Trait for SomeType`, if `expected_trait` is `Some("Trait")` +/// a) the declaration is `impl Trait for SomeType`, if `expected_trait` is `Some("Trait")` /// b) the declaration is `impl SomeType`, if `expected_trait` is `None` pub(crate) fn validate_impl( original_impl: &Impl, @@ -210,8 +222,7 @@ pub(crate) fn validate_impl( ) -> ParseResult { if let Some(expected_trait) = expected_trait { // impl Trait for Self -- validate Trait - let trait_name = original_impl.trait_ty.as_ref().unwrap(); // unwrap: already checked outside - if !extract_typename(trait_name).map_or(false, |seg| seg.ident == expected_trait) { + if !is_impl_named(original_impl, expected_trait) { return bail( format!("#[{attr}] for trait impls requires trait to be `{expected_trait}`"), original_impl, @@ -220,6 +231,37 @@ pub(crate) fn validate_impl( } // impl Trait for Self -- validate Self + 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`. +pub(crate) fn validate_trait_impl_virtual( + original_impl: &Impl, + attr: &str, +) -> ParseResult<(Ident, Ident)> { + let trait_name = original_impl.trait_ty.as_ref().unwrap(); // unwrap: already checked outside + let typename = extract_typename(trait_name); + + // Validate trait + if !typename + .as_ref() + .map_or(false, |seg| 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, + ); + } + + // Validate self + validate_self(original_impl, attr).map(|class_name| { + let trait_name = typename.unwrap(); // unwrap: already checked in 'Validate trait' + (class_name, trait_name.ident) + }) +} + +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() { Ok(segment.ident) diff --git a/godot/src/lib.rs b/godot/src/lib.rs index 6c0c201bd..c4312bd15 100644 --- a/godot/src/lib.rs +++ b/godot/src/lib.rs @@ -109,7 +109,6 @@ pub mod init { /// Export user-defined classes and methods to be called by the engine. pub mod bind { - pub use godot_core::bind::*; // Re-exports pub use godot_macros::{godot_api, GodotClass}; @@ -126,7 +125,7 @@ pub use godot_core::private; /// Often-imported symbols. pub mod prelude { - pub use super::bind::{godot_api, GodotClass, GodotExt}; + pub use super::bind::{godot_api, GodotClass}; pub use super::builtin::*; pub use super::builtin::{array, dict, varray}; // Re-export macros. pub use super::engine::{ diff --git a/itest/rust/src/codegen_test.rs b/itest/rust/src/codegen_test.rs index 2fe505f72..f73ae4467 100644 --- a/itest/rust/src/codegen_test.rs +++ b/itest/rust/src/codegen_test.rs @@ -9,7 +9,7 @@ use crate::itest; use godot::builtin::inner::{InnerColor, InnerString}; -use godot::engine::{FileAccess, HttpRequest}; +use godot::engine::{FileAccess, HttpRequest, HttpRequestVirtual}; use godot::prelude::*; pub fn run() -> bool { @@ -69,7 +69,7 @@ pub struct TestBaseRenamed { } #[godot_api] -impl GodotExt for TestBaseRenamed { +impl HttpRequestVirtual 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 1357b1356..7d1306d89 100644 --- a/itest/rust/src/export_test.rs +++ b/itest/rust/src/export_test.rs @@ -4,6 +4,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +use godot::engine::NodeVirtual; use godot::prelude::*; pub(crate) fn run() -> bool { @@ -75,7 +76,7 @@ impl HasProperty { } #[godot_api] -impl GodotExt for HasProperty { +impl NodeVirtual 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 95364f0fa..4e2cdc409 100644 --- a/itest/rust/src/object_test.rs +++ b/itest/rust/src/object_test.rs @@ -5,9 +5,11 @@ */ use crate::{expect_panic, itest}; -use godot::bind::{godot_api, GodotClass, GodotExt}; +use godot::bind::{godot_api, GodotClass}; use godot::builtin::{FromVariant, GodotString, StringName, ToVariant, Variant, Vector3}; -use godot::engine::{file_access, Camera3D, FileAccess, Node, Node3D, Object, RefCounted}; +use godot::engine::{ + file_access, Camera3D, FileAccess, Node, Node3D, Object, RefCounted, RefCountedVirtual, +}; use godot::obj::{Base, Gd, InstanceId}; use godot::obj::{Inherits, Share}; use godot::sys::GodotFfi; @@ -552,7 +554,7 @@ pub struct ObjPayload { } #[godot_api] -impl GodotExt for ObjPayload { +impl RefCountedVirtual 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 c51676ab3..68239920a 100644 --- a/itest/rust/src/virtual_methods_test.rs +++ b/itest/rust/src/virtual_methods_test.rs @@ -6,9 +6,9 @@ #![allow(dead_code)] -use godot::bind::{godot_api, GodotClass, GodotExt}; +use godot::bind::{godot_api, GodotClass}; use godot::builtin::GodotString; -use godot::engine::RefCounted; +use godot::engine::{RefCounted, RefCountedVirtual}; use godot::obj::{Base, Gd}; use godot::test::itest; @@ -33,7 +33,7 @@ struct VirtualMethodTest { impl VirtualMethodTest {} #[godot_api] -impl GodotExt for VirtualMethodTest { +impl RefCountedVirtual for VirtualMethodTest { fn to_string(&self) -> GodotString { format!("VirtualMethodTest[integer={}]", self.integer).into() }