diff --git a/godot-codegen/src/models/domain_mapping.rs b/godot-codegen/src/models/domain_mapping.rs index 3c884f9ac..f41dc97ae 100644 --- a/godot-codegen/src/models/domain_mapping.rs +++ b/godot-codegen/src/models/domain_mapping.rs @@ -458,7 +458,6 @@ impl ClassMethod { } let is_private = special_cases::is_method_private(class_name, &method.name); - let godot_method_name = method.name.clone(); let qualifier = { diff --git a/godot-codegen/src/special_cases/special_cases.rs b/godot-codegen/src/special_cases/special_cases.rs index 58f1b08d8..eddea21c0 100644 --- a/godot-codegen/src/special_cases/special_cases.rs +++ b/godot-codegen/src/special_cases/special_cases.rs @@ -21,6 +21,8 @@ // NOTE: the methods are generally implemented on Godot types (e.g. AABB, not Aabb) +// This file is deliberately private -- all checks must go through `special_cases`. + #![allow(clippy::match_like_matches_macro)] // if there is only one rule use crate::conv::to_enum_type_uncached; @@ -29,7 +31,6 @@ use crate::models::json::{JsonBuiltinMethod, JsonClassMethod, JsonUtilityFunctio use crate::special_cases::codegen_special_cases; use crate::Context; use proc_macro2::Ident; -// Deliberately private -- all checks must go through `special_cases`. #[rustfmt::skip] pub fn is_class_method_deleted(class_name: &TyName, method: &JsonClassMethod, ctx: &mut Context) -> bool { @@ -62,7 +63,7 @@ pub fn is_class_method_deleted(class_name: &TyName, method: &JsonClassMethod, ct | ("VisualShaderNodeComment", "set_description") | ("VisualShaderNodeComment", "get_description") => true, - + // Workaround for methods unexposed in Release mode, see https://github.com/godotengine/godot/pull/100317 // and https://github.com/godotengine/godot/pull/100328. #[cfg(not(debug_assertions))] @@ -840,9 +841,9 @@ pub fn is_enum_private(class_name: Option<&TyName>, enum_name: &str) -> bool { } /// Certain enums that are extremely unlikely to get new identifiers in the future. -/// +/// /// `class_name` = None for global enums. -/// +/// /// Very conservative, only includes a few enums. Even `VariantType` was extended over time. /// Also does not work for any enums containing duplicate ordinals. #[rustfmt::skip] @@ -873,7 +874,7 @@ pub fn is_enum_bitfield(class_name: Option<&TyName>, enum_name: &str) -> Option< match (class_name, enum_name) { | (Some("Object"), "ConnectFlags") - => Some(true), + => Some(true), _ => None } } diff --git a/godot-core/src/obj/traits.rs b/godot-core/src/obj/traits.rs index 02e72fcca..c376da0dc 100644 --- a/godot-core/src/obj/traits.rs +++ b/godot-core/src/obj/traits.rs @@ -293,7 +293,7 @@ pub trait WithBaseField: GodotClass + Bounds { /// /// #[godot_api] /// impl INode for MyClass { - /// fn process(&mut self, _delta: f64) { + /// fn process(&mut self, _delta: f32) { /// let name = self.base().get_name(); /// godot_print!("name is {name}"); /// } @@ -319,7 +319,7 @@ pub trait WithBaseField: GodotClass + Bounds { /// /// #[godot_api] /// impl INode for MyClass { - /// fn process(&mut self, _delta: f64) { + /// fn process(&mut self, _delta: f32) { /// let node = Node::new_alloc(); /// // fails because `add_child` requires a mutable reference. /// self.base().add_child(&node); @@ -362,7 +362,7 @@ pub trait WithBaseField: GodotClass + Bounds { /// /// #[godot_api] /// impl INode for MyClass { - /// fn process(&mut self, _delta: f64) { + /// fn process(&mut self, _delta: f32) { /// let node = Node::new_alloc(); /// self.base_mut().add_child(&node); /// } @@ -387,7 +387,7 @@ pub trait WithBaseField: GodotClass + Bounds { /// /// #[godot_api] /// impl INode for MyClass { - /// fn process(&mut self, _delta: f64) { + /// fn process(&mut self, _delta: f32) { /// self.base_mut().call("other_method", &[]); /// } /// } diff --git a/godot-macros/src/class/data_models/func.rs b/godot-macros/src/class/data_models/func.rs index 580625906..a5b2a73f5 100644 --- a/godot-macros/src/class/data_models/func.rs +++ b/godot-macros/src/class/data_models/func.rs @@ -187,8 +187,14 @@ pub struct SignatureInfo { pub method_name: Ident, pub receiver_type: ReceiverType, pub param_idents: Vec, + /// Parameter types *without* receiver. pub param_types: Vec, pub return_type: TokenStream, + + /// `(original index, new type)` only for changed parameters; empty if no changes. + /// + /// Index points into original venial tokens (i.e. takes into account potential receiver params). + pub modified_param_types: Vec<(usize, venial::TypeExpr)>, } impl SignatureInfo { @@ -199,6 +205,7 @@ impl SignatureInfo { param_idents: vec![], param_types: vec![], return_type: quote! { () }, + modified_param_types: vec![], } } @@ -359,7 +366,8 @@ pub(crate) fn into_signature_info( }; let mut next_unnamed_index = 0; - for (arg, _) in signature.params.inner { + let mut modified_param_types = vec![]; + for (index, (arg, _)) in signature.params.inner.into_iter().enumerate() { match arg { venial::FnParam::Receiver(recv) => { if receiver_type == ReceiverType::GdSelf { @@ -377,8 +385,17 @@ pub(crate) fn into_signature_info( } venial::FnParam::Typed(arg) => { let ident = maybe_rename_parameter(arg.name, &mut next_unnamed_index); - let ty = venial::TypeExpr { - tokens: map_self_to_class_name(arg.ty.tokens, class_name), + let ty = match maybe_change_parameter_type(arg.ty, &method_name, index) { + // Parameter type was modified. + Ok(ty) => { + modified_param_types.push((index, ty.clone())); + ty + } + + // Not an error, just unchanged. + Err(ty) => venial::TypeExpr { + tokens: map_self_to_class_name(ty.tokens, class_name), + }, }; param_types.push(ty); @@ -393,6 +410,28 @@ pub(crate) fn into_signature_info( param_idents, param_types, return_type: ret_type, + modified_param_types, + } +} + +/// If `f32` is used for a delta parameter in a virtual process function, transparently use `f64` behind the scenes. +fn maybe_change_parameter_type( + param_ty: venial::TypeExpr, + method_name: &Ident, + param_index: usize, +) -> Result { + // A bit hackish, but TokenStream APIs are also notoriously annoying to work with. Not even PartialEq... + + if param_index == 1 + && (method_name == "process" || method_name == "physics_process") + && param_ty.tokens.len() == 1 + && param_ty.tokens[0].to_string() == "f32" + { + Ok(venial::TypeExpr { + tokens: vec![TokenTree::Ident(ident("f64"))], + }) + } else { + Err(param_ty) } } diff --git a/godot-macros/src/class/data_models/interface_trait_impl.rs b/godot-macros/src/class/data_models/interface_trait_impl.rs index 8d7b6a798..d42ea48ac 100644 --- a/godot-macros/src/class/data_models/interface_trait_impl.rs +++ b/godot-macros/src/class/data_models/interface_trait_impl.rs @@ -9,28 +9,14 @@ use crate::class::{into_signature_info, make_virtual_callback, BeforeKind, Signa use crate::util::ident; use crate::{util, ParseResult}; -use proc_macro2::{Ident, TokenStream}; -use quote::quote; +use proc_macro2::{Delimiter, Group, Ident, TokenStream}; +use quote::{quote, ToTokens}; -/// Codegen for `#[godot_api] impl ISomething for MyType` -pub fn transform_trait_impl(original_impl: venial::Impl) -> ParseResult { +/// Codegen for `#[godot_api] impl ISomething for MyType`. +pub fn transform_trait_impl(mut original_impl: venial::Impl) -> ParseResult { let (class_name, trait_path, trait_base_class) = util::validate_trait_impl_virtual(&original_impl, "godot_api")?; - let mut godot_init_impl = TokenStream::new(); - let mut to_string_impl = TokenStream::new(); - let mut register_class_impl = TokenStream::new(); - let mut on_notification_impl = TokenStream::new(); - let mut get_property_impl = TokenStream::new(); - let mut set_property_impl = TokenStream::new(); - let mut get_property_list_impl = TokenStream::new(); - let mut property_get_revert_impl = TokenStream::new(); - let mut validate_property_impl = TokenStream::new(); - - let mut modifiers = Vec::new(); - - let mut overridden_virtuals = vec![]; - let prv = quote! { ::godot::private }; #[cfg(all(feature = "register-docs", since_api = "4.3"))] @@ -38,7 +24,9 @@ pub fn transform_trait_impl(original_impl: venial::Impl) -> ParseResult ParseResult { - // Implements the trait once for each implementation of this method, forwarding the cfg attrs of each - // implementation to the generated trait impl. If the cfg attrs allow for multiple implementations of - // this method to exist, then Rust will generate an error, so we don't have to worry about the multiple - // trait implementations actually generating an error, since that can only happen if multiple - // implementations of the same method are kept by #[cfg] (due to user error). - // Thus, by implementing the trait once for each possible implementation of this method (depending on - // what #[cfg] allows), forwarding the cfg attrs, we ensure this trait impl will remain in the code if - // at least one of the method impls are kept. - register_class_impl = quote! { - #register_class_impl - - #(#cfg_attrs)* - impl ::godot::obj::cap::GodotRegisterClass for #class_name { - fn __godot_register_class(builder: &mut ::godot::builder::GodotBuilder) { - ::register_class(builder) - } - } - }; - - modifiers.push((cfg_attrs, ident("with_register"))); + handle_register_class(&class_name, &trait_path, cfg_attrs, &mut decls); } - "init" => { - godot_init_impl = quote! { - #godot_init_impl - - #(#cfg_attrs)* - impl ::godot::obj::cap::GodotDefault for #class_name { - fn __godot_user_init(base: ::godot::obj::Base) -> Self { - ::init(base) - } - } - }; - - modifiers.push((cfg_attrs, ident("with_create"))); + handle_init(&class_name, &trait_path, cfg_attrs, &mut decls); } - "to_string" => { - to_string_impl = quote! { - #to_string_impl - - #(#cfg_attrs)* - impl ::godot::obj::cap::GodotToString for #class_name { - fn __godot_to_string(&self) -> ::godot::builtin::GString { - ::to_string(self) - } - } - }; - - modifiers.push((cfg_attrs, ident("with_string"))); + handle_to_string(&class_name, &trait_path, cfg_attrs, &mut decls); } - "on_notification" => { - let inactive_class_early_return = make_inactive_class_check(TokenStream::new()); - on_notification_impl = quote! { - #on_notification_impl - - #(#cfg_attrs)* - impl ::godot::obj::cap::GodotNotification for #class_name { - fn __godot_notification(&mut self, what: i32) { - use ::godot::obj::UserClass as _; - - #inactive_class_early_return - - ::on_notification(self, what.into()) - } - } - }; - - modifiers.push((cfg_attrs, ident("with_on_notification"))); + handle_on_notification(&class_name, &trait_path, cfg_attrs, &mut decls); } - "get_property" => { - let inactive_class_early_return = make_inactive_class_check(quote! { None }); - get_property_impl = quote! { - #(#cfg_attrs)* - impl ::godot::obj::cap::GodotGet for #class_name { - fn __godot_get_property(&self, property: ::godot::builtin::StringName) -> Option<::godot::builtin::Variant> { - use ::godot::obj::UserClass as _; - - #inactive_class_early_return - - ::get_property(self, property) - } - } - }; - - modifiers.push((cfg_attrs, ident("with_get_property"))); + handle_get_property(&class_name, &trait_path, cfg_attrs, &mut decls); } - "set_property" => { - let inactive_class_early_return = make_inactive_class_check(quote! { false }); - set_property_impl = quote! { - #(#cfg_attrs)* - impl ::godot::obj::cap::GodotSet for #class_name { - fn __godot_set_property(&mut self, property: ::godot::builtin::StringName, value: ::godot::builtin::Variant) -> bool { - use ::godot::obj::UserClass as _; - - #inactive_class_early_return - - ::set_property(self, property, value) - } - } - }; - - modifiers.push((cfg_attrs, ident("with_set_property"))); + handle_set_property(&class_name, &trait_path, cfg_attrs, &mut decls); } - - #[cfg(before_api = "4.3")] - "get_property_list" => { - get_property_list_impl = quote! { - #(#cfg_attrs)* - compile_error!("`get_property_list` is only supported for Godot versions of at least 4.3"); - }; + #[cfg(since_api = "4.2")] + "validate_property" => { + handle_validate_property(&class_name, &trait_path, cfg_attrs, &mut decls); } - - #[cfg(since_api = "4.3")] "get_property_list" => { - // `get_property_list` is only supported in Godot API >= 4.3. If we add support for `get_property_list` to earlier - // versions of Godot then this code is still needed and should be uncommented. - // - // let inactive_class_early_return = make_inactive_class_check(false); - get_property_list_impl = quote! { - #(#cfg_attrs)* - impl ::godot::obj::cap::GodotGetPropertyList for #class_name { - fn __godot_get_property_list(&mut self) -> Vec<::godot::meta::PropertyInfo> { - // #inactive_class_early_return - - ::get_property_list(self) - } - } - }; - - modifiers.push((cfg_attrs, ident("with_get_property_list"))); + handle_get_property_list(&class_name, &trait_path, cfg_attrs, &mut decls); } - "property_get_revert" => { - let inactive_class_early_return = make_inactive_class_check(quote! { None }); - property_get_revert_impl = quote! { - #(#cfg_attrs)* - impl ::godot::obj::cap::GodotPropertyGetRevert for #class_name { - fn __godot_property_get_revert(&self, property: StringName) -> Option<::godot::builtin::Variant> { - use ::godot::obj::UserClass as _; - - #inactive_class_early_return - - ::property_get_revert(self, property) - } - } - }; - - modifiers.push((cfg_attrs, ident("with_property_get_revert"))); - } - - #[cfg(since_api = "4.2")] - "validate_property" => { - let inactive_class_early_return = make_inactive_class_check(TokenStream::new()); - validate_property_impl = quote! { - #(#cfg_attrs)* - impl ::godot::obj::cap::GodotValidateProperty for #class_name { - fn __godot_validate_property(&self, property: &mut ::godot::meta::PropertyInfo) { - use ::godot::obj::UserClass as _; - - #inactive_class_early_return - - ::validate_property(self, property); - } - } - }; - modifiers.push((cfg_attrs, ident("with_validate_property"))); + handle_property_get_revert(&class_name, &trait_path, cfg_attrs, &mut decls); } - - // Other virtual methods, like ready, process etc. - method_name_str => { - #[cfg(since_api = "4.4")] - let method_name_ident = method.name.clone(); - let method = util::reduce_to_signature(method); - - // Godot-facing name begins with underscore. - // - // 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_str == "init_ext" { - String::from("_init") - } else { - format!("_{method_name_str}") - }; - - let signature_info = into_signature_info(method, &class_name, false); - - // Overridden ready() methods additionally have an additional `__before_ready()` call (for OnReady inits). - let before_kind = if method_name_str == "ready" { - BeforeKind::WithBefore - } else { - BeforeKind::Without - }; - - // Note that, if the same method is implemented multiple times (with different cfg attr combinations), - // then there will be multiple match arms annotated with the same cfg attr combinations, thus they will - // be reduced to just one arm (at most, if the implementations aren't all removed from compilation) for - // each distinct method. - overridden_virtuals.push(OverriddenVirtualFn { + regular_virtual_fn => { + // Break borrow chain to allow handle_regular_virtual_fn() to mutably borrow `method` and modify `original_impl` through it. + // let cfg_attrs = cfg_attrs.iter().cloned().collect() + + // All the non-special engine ones: ready(), process(), etc. + // Can modify original_impl, concretely the fn body for f64->f32 conversions. + let changed_function = handle_regular_virtual_fn( + &class_name, + &trait_path, + method, + regular_virtual_fn, cfg_attrs, - method_name: virtual_method_name, - // If ever the `I*` verbatim validation is relaxed (it won't work with use-renames or other weird edge cases), the approach - // with known_virtual_hashes module could be changed to something like the following (GodotBase = nearest Godot base class): - // __get_virtual_hash::("method") - #[cfg(since_api = "4.4")] - hash_constant: quote! { hashes::#method_name_ident }, - signature_info, - before_kind, - interface_trait: Some(trait_path.clone()), - }); + &mut decls, + ); + + // If the function is modified (e.g. process() declared with f32), apply changes here. + // Borrow-checker: we cannot reassign whole function due to shared borrow on `method.attributes`. + // Thus, separately update signature + body when needed. + if let Some((new_params, new_body)) = changed_function { + method.params = new_params; + method.body = Some(new_body); + //panic!("modify params: {}", method.params.to_token_stream().to_string()); + } } } } @@ -274,7 +99,8 @@ pub fn transform_trait_impl(original_impl: venial::Impl) -> ParseResult ParseResult(); } @@ -313,8 +139,9 @@ pub fn transform_trait_impl(original_impl: venial::Impl) -> ParseResult ParseResult ParseResult( + class_name: &Ident, + trait_path: &venial::TypeExpr, + cfg_attrs: Vec<&'a venial::Attribute>, + decls: &mut IDecls<'a>, +) { + let IDecls { + register_class_impl, + .. + } = decls; + + // Implements the trait once for each implementation of this method, forwarding the cfg attrs of each + // implementation to the generated trait impl. If the cfg attrs allow for multiple implementations of + // this method to exist, then Rust will generate an error, so we don't have to worry about the multiple + // trait implementations actually generating an error, since that can only happen if multiple + // implementations of the same method are kept by #[cfg] (due to user error). + // Thus, by implementing the trait once for each possible implementation of this method (depending on + // what #[cfg] allows), forwarding the cfg attrs, we ensure this trait impl will remain in the code if + // at least one of the method impls are kept. + *register_class_impl = quote! { + #register_class_impl + + #(#cfg_attrs)* + impl ::godot::obj::cap::GodotRegisterClass for #class_name { + fn __godot_register_class(builder: &mut ::godot::builder::GodotBuilder) { + ::register_class(builder) + } + } + }; + + decls.add_modifier(cfg_attrs, "with_register"); +} + +fn handle_init<'a>( + class_name: &Ident, + trait_path: &venial::TypeExpr, + cfg_attrs: Vec<&'a venial::Attribute>, + decls: &mut IDecls<'a>, +) { + let IDecls { + godot_init_impl, .. + } = decls; + + *godot_init_impl = quote! { + #godot_init_impl + + #(#cfg_attrs)* + impl ::godot::obj::cap::GodotDefault for #class_name { + fn __godot_user_init(base: ::godot::obj::Base) -> Self { + ::init(base) + } + } + }; + + decls.add_modifier(cfg_attrs, "with_create"); +} + +fn handle_to_string<'a>( + class_name: &Ident, + trait_path: &venial::TypeExpr, + cfg_attrs: Vec<&'a venial::Attribute>, + decls: &mut IDecls<'a>, +) { + let IDecls { to_string_impl, .. } = decls; + + *to_string_impl = quote! { + #to_string_impl + + #(#cfg_attrs)* + impl ::godot::obj::cap::GodotToString for #class_name { + fn __godot_to_string(&self) -> ::godot::builtin::GString { + ::to_string(self) + } + } + }; + + decls.add_modifier(cfg_attrs, "with_string"); +} + +fn handle_on_notification<'a>( + class_name: &Ident, + trait_path: &venial::TypeExpr, + cfg_attrs: Vec<&'a venial::Attribute>, + decls: &mut IDecls<'a>, +) { + let IDecls { + on_notification_impl, + .. + } = decls; + + let inactive_class_early_return = make_inactive_class_check(TokenStream::new()); + *on_notification_impl = quote! { + #on_notification_impl + + #(#cfg_attrs)* + impl ::godot::obj::cap::GodotNotification for #class_name { + fn __godot_notification(&mut self, what: i32) { + use ::godot::obj::UserClass as _; + + #inactive_class_early_return + + ::on_notification(self, what.into()) + } + } + }; + + decls.add_modifier(cfg_attrs, "with_on_notification"); +} + +fn handle_get_property<'a>( + class_name: &Ident, + trait_path: &venial::TypeExpr, + cfg_attrs: Vec<&'a venial::Attribute>, + decls: &mut IDecls<'a>, +) { + let IDecls { + get_property_impl, .. + } = decls; + + let inactive_class_early_return = make_inactive_class_check(quote! { None }); + *get_property_impl = quote! { + #(#cfg_attrs)* + impl ::godot::obj::cap::GodotGet for #class_name { + fn __godot_get_property(&self, property: ::godot::builtin::StringName) -> Option<::godot::builtin::Variant> { + use ::godot::obj::UserClass as _; + + #inactive_class_early_return + + ::get_property(self, property) + } + } + }; + + decls.add_modifier(cfg_attrs, "with_get_property"); +} + +fn handle_set_property<'a>( + class_name: &Ident, + trait_path: &venial::TypeExpr, + cfg_attrs: Vec<&'a venial::Attribute>, + decls: &mut IDecls<'a>, +) { + let IDecls { + set_property_impl, .. + } = decls; + + let inactive_class_early_return = make_inactive_class_check(quote! { false }); + *set_property_impl = quote! { + #(#cfg_attrs)* + impl ::godot::obj::cap::GodotSet for #class_name { + fn __godot_set_property(&mut self, property: ::godot::builtin::StringName, value: ::godot::builtin::Variant) -> bool { + use ::godot::obj::UserClass as _; + + #inactive_class_early_return + + ::set_property(self, property, value) + } + } + }; + + decls.add_modifier(cfg_attrs, "with_set_property"); +} + +fn handle_validate_property<'a>( + class_name: &Ident, + trait_path: &venial::TypeExpr, + cfg_attrs: Vec<&'a venial::Attribute>, + decls: &mut IDecls<'a>, +) { + let IDecls { + validate_property_impl, + .. + } = decls; + + let inactive_class_early_return = make_inactive_class_check(TokenStream::new()); + *validate_property_impl = quote! { + #(#cfg_attrs)* + impl ::godot::obj::cap::GodotValidateProperty for #class_name { + fn __godot_validate_property(&self, property: &mut ::godot::meta::PropertyInfo) { + use ::godot::obj::UserClass as _; + + #inactive_class_early_return + + ::validate_property(self, property); + } + } + }; + + decls.add_modifier(cfg_attrs, "with_validate_property"); +} + +#[cfg(before_api = "4.3")] +fn handle_get_property_list<'a>( + _class_name: &Ident, + _trait_path: &venial::TypeExpr, + cfg_attrs: Vec<&'a venial::Attribute>, + decls: &mut IDecls<'a>, +) { + decls.get_property_list_impl = quote! { + #(#cfg_attrs)* + compile_error!("`get_property_list` is only supported for Godot versions of at least 4.3"); + }; +} + +#[cfg(since_api = "4.3")] +fn handle_get_property_list<'a>( + class_name: &Ident, + trait_path: &venial::TypeExpr, + cfg_attrs: Vec<&'a venial::Attribute>, + decls: &mut IDecls<'a>, +) { + let IDecls { + get_property_list_impl, + .. + } = decls; + + // `get_property_list` is only supported in Godot API >= 4.3. If we add support for `get_property_list` to earlier + // versions of Godot then this code is still needed and should be uncommented. + // + // let inactive_class_early_return = make_inactive_class_check(false); + *get_property_list_impl = quote! { + #(#cfg_attrs)* + impl ::godot::obj::cap::GodotGetPropertyList for #class_name { + fn __godot_get_property_list(&mut self) -> Vec<::godot::meta::PropertyInfo> { + // #inactive_class_early_return + + ::get_property_list(self) + } + } + }; + + decls.add_modifier(cfg_attrs, "with_get_property_list"); +} + +fn handle_property_get_revert<'a>( + class_name: &Ident, + trait_path: &venial::TypeExpr, + cfg_attrs: Vec<&'a venial::Attribute>, + decls: &mut IDecls<'a>, +) { + let IDecls { + property_get_revert_impl, + .. + } = decls; + + let inactive_class_early_return = make_inactive_class_check(quote! { None }); + *property_get_revert_impl = quote! { + #(#cfg_attrs)* + impl ::godot::obj::cap::GodotPropertyGetRevert for #class_name { + fn __godot_property_get_revert(&self, property: StringName) -> Option<::godot::builtin::Variant> { + use ::godot::obj::UserClass as _; + + #inactive_class_early_return + + ::property_get_revert(self, property) + } + } + }; + + decls.add_modifier(cfg_attrs, "with_property_get_revert"); +} + +fn handle_regular_virtual_fn<'a>( + class_name: &Ident, + trait_path: &venial::TypeExpr, + original_method: &venial::Function, + method_name: &str, + cfg_attrs: Vec<&'a venial::Attribute>, + decls: &mut IDecls<'a>, +) -> Option<(venial::Punctuated, Group)> { + #[cfg(since_api = "4.4")] + let method_name_ident = original_method.name.clone(); + let method = util::reduce_to_signature(original_method); + + // Godot-facing name begins with underscore. + // + // 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}") + }; + + let signature_info = into_signature_info(method, class_name, false); + + let mut updated_function = None; + + // If there was a signature change (e.g. f32 -> f64 in process/physics_process), apply to new function tokens. + if !signature_info.modified_param_types.is_empty() { + let mut param_name = None; + + let mut new_params = original_method.params.clone(); + for (index, new_ty) in signature_info.modified_param_types.iter() { + let venial::FnParam::Typed(typed) = &mut new_params.inner[*index].0 else { + panic!("unexpected parameter type: {new_params:?}"); + }; + + typed.ty = new_ty.clone(); + param_name = Some(typed.name.clone()); + } + + let original_body = &original_method.body; + let param_name = param_name.expect("parameter had no name"); + + // Currently hardcoded to f32/f64 exchange; can be generalized if needed. + let body_code = quote! { + let #param_name = #param_name as f32; + #original_body + }; + + let wrapping_body = Group::new(Delimiter::Brace, body_code); + + updated_function = Some((new_params, wrapping_body)); + } + + // Overridden ready() methods additionally have an additional `__before_ready()` call (for OnReady inits). + let before_kind = if method_name == "ready" { + BeforeKind::WithBefore + } else { + BeforeKind::Without + }; + + // Note that, if the same method is implemented multiple times (with different cfg attr combinations), + // then there will be multiple match arms annotated with the same cfg attr combinations, thus they will + // be reduced to just one arm (at most, if the implementations aren't all removed from compilation) for + // each distinct method. + decls.overridden_virtuals.push(OverriddenVirtualFn { + cfg_attrs, + method_name: virtual_method_name, + // If ever the `I*` verbatim validation is relaxed (it won't work with use-renames or other weird edge cases), the approach + // with known_virtual_hashes module could be changed to something like the following (GodotBase = nearest Godot base class): + // __get_virtual_hash::("method") + #[cfg(since_api = "4.4")] + hash_constant: quote! { hashes::#method_name_ident }, + signature_info, + before_kind, + interface_trait: Some(trait_path.clone()), + }); + + updated_function +} + +// ---------------------------------------------------------------------------------------------------------------------------------------------- +// Rest of implementation + /// Returns `false` if the given class does definitely not inherit `Node`, `true` otherwise. /// /// `#[godot_api]` has currently no way of checking base class at macro-resolve time, so the `_ready` branch is unconditionally @@ -381,6 +554,23 @@ fn is_possibly_node_class(trait_base_class: &Ident) -> bool { | "ScriptExtension" ) } + +#[cfg(before_api = "4.3")] +fn make_inactive_class_check(return_value: TokenStream) -> TokenStream { + quote! { + if ::godot::private::is_class_inactive(Self::__config().is_tool) { + return #return_value; + } + } +} + +#[cfg(since_api = "4.3")] +fn make_inactive_class_check(_return_value: TokenStream) -> TokenStream { + TokenStream::new() +} + +// ---------------------------------------------------------------------------------------------------------------------------------------------- + struct OverriddenVirtualFn<'a> { cfg_attrs: Vec<&'a venial::Attribute>, method_name: String, @@ -420,16 +610,41 @@ impl OverriddenVirtualFn<'_> { } } -#[cfg(before_api = "4.3")] -fn make_inactive_class_check(return_value: TokenStream) -> TokenStream { - quote! { - if ::godot::private::is_class_inactive(Self::__config().is_tool) { - return #return_value; - } +// ---------------------------------------------------------------------------------------------------------------------------------------------- + +/// Accumulates various symbols defined inside a `#[godot_api]` macro. +#[derive(Default)] +struct IDecls<'a> { + godot_init_impl: TokenStream, + to_string_impl: TokenStream, + register_class_impl: TokenStream, + on_notification_impl: TokenStream, + get_property_impl: TokenStream, + set_property_impl: TokenStream, + get_property_list_impl: TokenStream, + property_get_revert_impl: TokenStream, + validate_property_impl: TokenStream, + + modifiers: Vec<(Vec<&'a venial::Attribute>, Ident)>, + overridden_virtuals: Vec>, +} + +impl<'a> IDecls<'a> { + fn add_modifier(&mut self, cfg_attrs: Vec<&'a venial::Attribute>, modifier: &str) { + self.modifiers.push((cfg_attrs, ident(modifier))); } } -#[cfg(since_api = "4.3")] -fn make_inactive_class_check(_return_value: TokenStream) -> TokenStream { - TokenStream::new() +impl ToTokens for IDecls<'_> { + fn to_tokens(&self, tokens: &mut TokenStream) { + self.godot_init_impl.to_tokens(tokens); + self.to_string_impl.to_tokens(tokens); + self.on_notification_impl.to_tokens(tokens); + self.register_class_impl.to_tokens(tokens); + self.get_property_impl.to_tokens(tokens); + self.set_property_impl.to_tokens(tokens); + self.get_property_list_impl.to_tokens(tokens); + self.property_get_revert_impl.to_tokens(tokens); + self.validate_property_impl.to_tokens(tokens); + } } diff --git a/godot-macros/src/util/mod.rs b/godot-macros/src/util/mod.rs index d7a27d45e..ec0cc1d9f 100644 --- a/godot-macros/src/util/mod.rs +++ b/godot-macros/src/util/mod.rs @@ -170,10 +170,10 @@ pub(crate) fn validate_impl( /// Validates that the declaration is the of the form `impl Trait for SomeType`, where the name of `Trait` begins with `I`. /// /// Returns `(class_name, trait_path, trait_base_class)`, e.g. `(MyClass, godot::prelude::INode3D, Node3D)`. -pub(crate) fn validate_trait_impl_virtual<'a>( - original_impl: &'a venial::Impl, +pub(crate) fn validate_trait_impl_virtual( + original_impl: &venial::Impl, attr: &str, -) -> ParseResult<(Ident, &'a venial::TypeExpr, Ident)> { +) -> ParseResult<(Ident, venial::TypeExpr, Ident)> { let trait_name = original_impl.trait_ty.as_ref().unwrap(); // unwrap: already checked outside let typename = extract_typename(trait_name); @@ -191,7 +191,7 @@ pub(crate) fn validate_trait_impl_virtual<'a>( // 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, base_class) + (class_name, trait_name.clone(), base_class) }) } diff --git a/itest/rust/src/engine_tests/codegen_test.rs b/itest/rust/src/engine_tests/codegen_test.rs index 5faa5c457..fec34dda6 100644 --- a/itest/rust/src/engine_tests/codegen_test.rs +++ b/itest/rust/src/engine_tests/codegen_test.rs @@ -95,6 +95,12 @@ impl IHttpRequest for CodegenTest { // Test unnamed parameter in virtual function fn process(&mut self, _: f64) {} + + // Test auto-cast to f32 parameter in virtual function + fn physics_process(&mut self, delta: f32) { + // Test it's actually f32 in the body. + let _use_param: f32 = delta; + } } // ----------------------------------------------------------------------------------------------------------------------------------------------