From d6589fc3b9b9932285969aeeff7c9613c58d406b Mon Sep 17 00:00:00 2001 From: Tim Yuen Date: Tue, 8 Aug 2023 17:56:10 -0400 Subject: [PATCH] Add ability to rename functions bound to Godot * Modify func macro to accept a 'rename = ...' attribute * Add integration tests to cover renamed functions --- .../derive_godot_class/property/field_var.rs | 9 +++- godot-macros/src/godot_api.rs | 40 ++++++++++------ godot-macros/src/method_registration/mod.rs | 8 ++++ .../method_registration/register_method.rs | 10 ++-- itest/godot/ManualFfiTests.gd | 15 ++++++ itest/rust/src/func_test.rs | 48 +++++++++++++++++++ itest/rust/src/lib.rs | 1 + 7 files changed, 113 insertions(+), 18 deletions(-) create mode 100644 itest/rust/src/func_test.rs diff --git a/godot-macros/src/derive_godot_class/property/field_var.rs b/godot-macros/src/derive_godot_class/property/field_var.rs index 230c99a7b..2d80a0382 100644 --- a/godot-macros/src/derive_godot_class/property/field_var.rs +++ b/godot-macros/src/derive_godot_class/property/field_var.rs @@ -6,6 +6,7 @@ use crate::derive_godot_class::{make_existence_check, Field, FieldHint}; use crate::method_registration::make_method_registration; +use crate::method_registration::FuncDefinition; use crate::util::KvParser; use crate::{util, ParseResult}; use proc_macro2::{Ident, TokenStream}; @@ -188,7 +189,13 @@ impl GetterSetterImpl { }; let signature = util::parse_signature(signature); - let export_token = make_method_registration(class_name, signature); + let export_token = make_method_registration( + class_name, + FuncDefinition { + func: signature, + rename: None, + }, + ); Self { function_name, diff --git a/godot-macros/src/godot_api.rs b/godot-macros/src/godot_api.rs index c540a4e8b..d6ba802f3 100644 --- a/godot-macros/src/godot_api.rs +++ b/godot-macros/src/godot_api.rs @@ -4,9 +4,12 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use crate::method_registration::{make_method_registration, make_virtual_method_callback}; +use crate::method_registration::{ + make_method_registration, make_virtual_method_callback, FuncDefinition, +}; use crate::util; use crate::util::bail; +use crate::util::KvParser; use proc_macro2::{Ident, TokenStream}; use quote::quote; use quote::spanned::Spanned; @@ -46,7 +49,7 @@ pub fn transform(input_decl: Declaration) -> Result { /// Attribute for user-declared function enum BoundAttrType { - Func(AttributeValue), + Func { rename: Option }, Signal(AttributeValue), Const(AttributeValue), } @@ -108,7 +111,7 @@ fn transform_inherent_impl(mut decl: Impl) -> Result { let methods_registration = funcs .into_iter() - .map(|func| make_method_registration(&class_name, func)); + .map(|func_def| make_method_registration(&class_name, func_def)); let consts = process_godot_constants(&mut decl)?; let mut integer_constant_names = Vec::new(); @@ -198,8 +201,8 @@ fn transform_inherent_impl(mut decl: Impl) -> Result { Ok(result) } -fn process_godot_fns(decl: &mut Impl) -> Result<(Vec, Vec), Error> { - let mut func_signatures = vec![]; +fn process_godot_fns(decl: &mut Impl) -> Result<(Vec, Vec), Error> { + let mut func_definitions = vec![]; let mut signal_signatures = vec![]; let mut removed_indexes = vec![]; @@ -229,10 +232,10 @@ fn process_godot_fns(decl: &mut Impl) -> Result<(Vec, Vec), } match attr.ty { - BoundAttrType::Func(_attr) => { + BoundAttrType::Func { rename } => { // Signatures are the same thing without body let sig = util::reduce_to_signature(method); - func_signatures.push(sig); + func_definitions.push(FuncDefinition { func: sig, rename }); } BoundAttrType::Signal(ref _attr_val) => { if method.return_ty.is_some() { @@ -259,7 +262,7 @@ fn process_godot_fns(decl: &mut Impl) -> Result<(Vec, Vec), decl.body_items.remove(index); } - Ok((func_signatures, signal_signatures)) + Ok((func_definitions, signal_signatures)) } fn process_godot_constants(decl: &mut Impl) -> Result, Error> { @@ -275,7 +278,7 @@ fn process_godot_constants(decl: &mut Impl) -> Result, Error> { constant.attributes.remove(attr.index); match attr.ty { - BoundAttrType::Func(_) => { + BoundAttrType::Func { .. } => { return bail!(constant, "#[func] can only be used on functions") } BoundAttrType::Signal(_) => { @@ -308,11 +311,20 @@ where .expect("get_single_path_segment"); let new_found = match attr_name { - name if name == "func" => Some(BoundAttr { - attr_name: attr_name.clone(), - index, - ty: BoundAttrType::Func(attr.value.clone()), - }), + name if name == "func" => { + // TODO you-win (August 8, 2023): handle default values here as well? + + // Safe unwrap since #[func] must be present if we got to this point + let mut parser = KvParser::parse(attributes, "func")?.unwrap(); + + let rename = parser.handle_expr("rename")?.map(|ts| ts.to_string()); + + Some(BoundAttr { + attr_name: attr_name.clone(), + index, + ty: BoundAttrType::Func { rename }, + }) + } name if name == "signal" => { // TODO once parameters are supported, this should probably be moved to the struct definition // E.g. a zero-sized type Signal<(i32, String)> with a provided emit(i32, String) method diff --git a/godot-macros/src/method_registration/mod.rs b/godot-macros/src/method_registration/mod.rs index 2bb349687..f07a82e51 100644 --- a/godot-macros/src/method_registration/mod.rs +++ b/godot-macros/src/method_registration/mod.rs @@ -27,6 +27,14 @@ struct SignatureInfo { pub ret_type: TokenStream, } +/// Information used for registering a Rust function with Godot. +pub struct FuncDefinition { + /// Raw information about the Rust function. + pub func: venial::Function, + /// The name the function will be exposed as in Godot. If `None`, the Rust function name is used. + pub rename: Option, +} + /// Returns a closure expression that forwards the parameters to the Rust instance. fn make_forwarding_closure(class_name: &Ident, signature_info: &SignatureInfo) -> TokenStream { let method_name = &signature_info.method_name; diff --git a/godot-macros/src/method_registration/register_method.rs b/godot-macros/src/method_registration/register_method.rs index bf79e64dc..f3f83d581 100644 --- a/godot-macros/src/method_registration/register_method.rs +++ b/godot-macros/src/method_registration/register_method.rs @@ -15,9 +15,9 @@ use quote::quote; /// Generates code that registers the specified method for the given class. pub fn make_method_registration( class_name: &Ident, - method_signature: venial::Function, + func_definition: super::FuncDefinition, ) -> TokenStream { - let signature_info = get_signature_info(&method_signature); + let signature_info = get_signature_info(&func_definition.func); let sig_tuple = util::make_signature_tuple_type(&signature_info.ret_type, &signature_info.param_types); @@ -33,7 +33,11 @@ pub fn make_method_registration( // String literals let class_name_str = class_name.to_string(); - let method_name_str = method_name.to_string(); + let method_name_str = if let Some(rename) = func_definition.rename { + rename + } else { + method_name.to_string() + }; let param_ident_strs = param_idents.iter().map(|ident| ident.to_string()); quote! { diff --git a/itest/godot/ManualFfiTests.gd b/itest/godot/ManualFfiTests.gd index 54c1a8042..9563cad44 100644 --- a/itest/godot/ManualFfiTests.gd +++ b/itest/godot/ManualFfiTests.gd @@ -289,3 +289,18 @@ func test_option_export(): assert_eq(obj.optional_export, null) test_node.free() + +func test_func_rename(): + var func_rename := FuncRename.new() + + assert_eq(func_rename.has_method("long_function_name_for_is_true"), false) + assert_eq(func_rename.has_method("is_true"), true) + assert_eq(func_rename.is_true(), true) + + assert_eq(func_rename.has_method("give_one_inner"), false) + assert_eq(func_rename.has_method("give_one"), true) + assert_eq(func_rename.give_one(), 1) + + assert_eq(func_rename.has_method("renamed_static"), false) + assert_eq(func_rename.has_method("spell_static"), true) + assert_eq(func_rename.spell_static(), "static") diff --git a/itest/rust/src/func_test.rs b/itest/rust/src/func_test.rs new file mode 100644 index 000000000..b29019cf5 --- /dev/null +++ b/itest/rust/src/func_test.rs @@ -0,0 +1,48 @@ +/* + * 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 godot::prelude::*; + +#[derive(GodotClass)] +#[class(base=RefCounted)] +struct FuncRename; + +#[godot_api] +impl FuncRename { + #[func(rename=is_true)] + fn long_function_name_for_is_true(&self) -> bool { + true + } + + #[func(rename=give_one)] + fn give_one_inner(&self) -> i32 { + self.give_one() + } + + #[func(rename=spell_static)] + fn renamed_static() -> GodotString { + GodotString::from("static") + } +} + +impl FuncRename { + /// Unused but present to demonstrate how `rename = ...` can be used to avoid name clashes. + #[allow(dead_code)] + fn is_true(&self) -> bool { + false + } + + fn give_one(&self) -> i32 { + 1 + } +} + +#[godot_api] +impl RefCountedVirtual for FuncRename { + fn init(_base: Base) -> Self { + Self + } +} diff --git a/itest/rust/src/lib.rs b/itest/rust/src/lib.rs index a1624c4ab..90f426286 100644 --- a/itest/rust/src/lib.rs +++ b/itest/rust/src/lib.rs @@ -19,6 +19,7 @@ mod color_test; mod derive_variant; mod dictionary_test; mod enum_test; +mod func_test; mod gdscript_ffi_test; mod init_test; mod native_structures_test;