Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] in #[var]s, handle renamed #[func]s #1019

Draft
wants to merge 21 commits into
base: master
Choose a base branch
from

Conversation

0x53A
Copy link
Contributor

@0x53A 0x53A commented Jan 20, 2025

closes #863

@0x53A 0x53A force-pushed the dev-handle-func-rename branch from 7d0f540 to 6695a62 Compare January 20, 2025 02:33
@GodotRust
Copy link

API docs are being generated and will be shortly available at: https://godot-rust.github.io/docs/gdext/pr-1019

@0x53A 0x53A force-pushed the dev-handle-func-rename branch from 6695a62 to 56e5be7 Compare January 20, 2025 02:35
};

quote! {
#(#attributes)*
Copy link
Contributor Author

@0x53A 0x53A Jan 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

at the moment, all attributes are copied over.

Maybe this should probably be changed so only #[cfg] attributes are copied over. Are there any other attributes we should copy?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe #[cfg_attr] 🤔

Copy link
Contributor Author

@0x53A 0x53A Jan 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you elaborate?

This would be relevant in, for example, func_test.rs. At first I didn't handle attributes at all, which failed here because it would duplicate the constant. After copying cfg, only one of the constants is active.
image

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe #[cfg_attr]

Oh I think I know what you meant.

that would only be relevant for the special case #[cfg_attr(/**/, cfg)], right?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that would only be relevant for the special case #[cfg_attr(/**/, cfg)], right?

You're right, this probably makes no sense to handle. No one writes code like this 😀

@0x53A 0x53A force-pushed the dev-handle-func-rename branch from 56e5be7 to e8a3c98 Compare January 20, 2025 02:39
Copy link
Member

@Bromeon Bromeon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot! 👍

Some of it may clash with a similar thing I did in #1000, but this is nicely isolated, and I can deal with the conflicts later.

godot-macros/src/class/data_models/inherent_impl.rs Outdated Show resolved Hide resolved
godot-macros/src/class/data_models/inherent_impl.rs Outdated Show resolved Hide resolved
@@ -200,10 +201,14 @@ impl GetterSetterImpl {
}
}

let constant_declaration =
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
let constant_declaration =
let func_name_constant =

To make clear that it's not a user-defined #[constant].

@@ -84,6 +86,8 @@ pub fn transform_inherent_impl(
let (funcs, signals) = process_godot_fns(&class_name, &mut impl_block, meta.secondary)?;
let consts = process_godot_constants(&mut impl_block)?;

let func_export_name_constants = make_function_registered_name_constants(&funcs, &class_name);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
let func_export_name_constants = make_function_registered_name_constants(&funcs, &class_name);
let func_name_constants = make_function_registered_name_constants(&funcs, &class_name);

"export" has a very specific meaning; using it here is probably confusing.

Copy link
Contributor Author

@0x53A 0x53A Jan 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it was also not consistent with the function name. I'd propose standardizing on "registered" function name, at least that's what it's called in other places in the code.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's fine if the local variables are more terse/concise than the function names. Context matters, and if you see the

let func_name_constants = make_function_registered_name_constants(...);

once, then you know what func_name_constants refers to. I think using overly long variable names makes expressions harder to read; the "registered" doesn't really add useful information to understand the codegen.

There's also the point of scope: global function names are visible in their entire module, while variable names are limited to the block/function in which they are declared. It's much easier to mentally keep track of a block, than it is of a module; and there's less potential for collisions.

@@ -184,6 +191,9 @@ pub fn transform_inherent_impl(
let result = quote! {
#impl_block
#fill_storage
impl #class_name{
#( #func_export_name_constants )*
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not quite clear to me why individual constants are defined above next to each function, and then all of them again here... Could you elaborate this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep. If you have a struct declaration with #[var(get, set)], then the macro will autogenerate one or two functions. In that case, the constants are placed next to the macro-generated function.

If the user has user-declared functions in an inherent impl block registered with #[func], then I'm placing the constants in a second impl block below. Reason is simple, the #impl_block is currently just copied as-is, I didn't want to have to insert the constants in the middle of it.

Since I can't push any information between macro invocations, the constants need to be declared by the macro that generates or reads the function.

Simple example:

#[derive(GodotClass)]
#[class(base=Node, init)]
struct O {
    #[var(get, set)]
    int_val: i32,
}

#[godot_api]
impl O {
    #[func(rename=f1)]
    pub fn foo(&self) -> i32 {
        todo!()
    }

    #[func(rename=f2)]
    pub fn bar(&mut self, val: i32) {
        todo!()
    }
}

will generate:

// #[derive(GodotClass)]

impl O {
    pub fn get_int_val(&self) -> <i32 as ::godot::meta::GodotConvert>::Via {
        <i32 as ::godot::register::property::Var>::get_property(&self.int_val)
    }
    #[doc(hidden)]
    #[allow(non_upper_case_globals)]
    pub const __gdext_func_O_get_int_val: &str = "get_int_val";
    pub fn set_int_val(&mut self, int_val: <i32 as ::godot::meta::GodotConvert>::Via) {
        <i32 as ::godot::register::property::Var>::set_property(&mut self.int_val, int_val);
    }
    #[doc(hidden)]
    #[allow(non_upper_case_globals)]
    pub const __gdext_func_O_set_int_val: &str = "set_int_val";
}


// #[godot_api]

impl O {
    #[doc(hidden)]
    #[allow(non_upper_case_globals)]
    pub const __gdext_func_O_foo: &str = "f1";
    #[doc(hidden)]
    #[allow(non_upper_case_globals)]
    pub const __gdext_func_O_bar: &str = "f2";
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you very much!

Makes total sense, could you maybe add 1 line of comments next to each substitution?

// Getter/setter generated from struct fields: place constant next to function in impl.

// User-defined functions: collectively place constants in dedicated impl block.

};

quote! {
#(#attributes)*
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe #[cfg_attr] 🤔

Comment on lines 352 to 354
pub fn format_function_registered_name_constant(class_name: &Ident, func_name: &Ident) -> Ident {
format_ident!("__gdext_func_{}_{}", class_name, func_name)
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd use consistent naming, that makes it obvious you're referring to the same thing.
Above I suggested func_name_constant, maybe that can be done here, too?

Suggested change
pub fn format_function_registered_name_constant(class_name: &Ident, func_name: &Ident) -> Ident {
format_ident!("__gdext_func_{}_{}", class_name, func_name)
}
pub fn format_func_name_constant(class_name: &Ident, func_name: &Ident) -> Ident {
format_ident!("__gdext_func_{class_name}_{func_name}")
}

Comment on lines +486 to +506
// ----------------------------------------------------------------------------------------------------------------------------------------------

#[derive(GodotClass)]
#[class(base=Node, init)]
struct RenamedFunc {
#[var(get = get_int_val, set = set_int_val)]
int_val: i32,
}

#[godot_api]
impl RenamedFunc {
#[func(rename=f1)]
pub fn get_int_val(&self) -> i32 {
self.int_val
}

#[func(rename=f2)]
pub fn set_int_val(&mut self, val: i32) {
self.int_val = val;
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the test! Could you add an #[itest] that actually invokes the getters + setters by their new name?

You can use Object::call() for dynamic calls.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sure thing. before merging I'd also like to add a test that reads/writes the property from gdscript

@0x53A
Copy link
Contributor Author

0x53A commented Jan 21, 2025

proposal: to not pollute the struct namespace with all these constants, I could create a dummy struct.

Currently the following code is generated:

// #[derive(GodotClass)]

impl O {
    pub fn get_int_val(&self) -> <i32 as ::godot::meta::GodotConvert>::Via {
        <i32 as ::godot::register::property::Var>::get_property(&self.int_val)
    }
    #[doc(hidden)]
    #[allow(non_upper_case_globals)]
    pub const __gdext_func_O_get_int_val: &str = "get_int_val";
    pub fn set_int_val(&mut self, int_val: <i32 as ::godot::meta::GodotConvert>::Via) {
        <i32 as ::godot::register::property::Var>::set_property(&mut self.int_val, int_val);
    }
    #[doc(hidden)]
    #[allow(non_upper_case_globals)]
    pub const __gdext_func_O_set_int_val: &str = "set_int_val";
}


// #[godot_api]

impl O {
    #[doc(hidden)]
    #[allow(non_upper_case_globals)]
    pub const __gdext_func_O_foo: &str = "f1";
    #[doc(hidden)]
    #[allow(non_upper_case_globals)]
    pub const __gdext_func_O_bar: &str = "f2";
}

I would propose the following:

// #[derive(GodotClass)]

impl O {
    pub fn get_int_val(&self) -> <i32 as ::godot::meta::GodotConvert>::Via {
        <i32 as ::godot::register::property::Var>::get_property(&self.int_val)
    }
    pub fn set_int_val(&mut self, int_val: <i32 as ::godot::meta::GodotConvert>::Via) {
        <i32 as ::godot::register::property::Var>::set_property(&mut self.int_val, int_val);
    }
}

#[doc(hidden)]
struct O_Functions { }

impl O_Functions {
    #[doc(hidden)]
    #[allow(non_upper_case_globals)]
    pub const __gdext_func_O_set_int_val: &str = "set_int_val";

    #[doc(hidden)]
    #[allow(non_upper_case_globals)]
    pub const __gdext_func_O_get_int_val: &str = "get_int_val";
}

// #[godot_api]

impl O_Functions {
    #[doc(hidden)]
    #[allow(non_upper_case_globals)]
    pub const __gdext_func_O_foo: &str = "f1";
    #[doc(hidden)]
    #[allow(non_upper_case_globals)]
    pub const __gdext_func_O_bar: &str = "f2";
}

The dummy struct is required instead of a mod so that we can contribute to the same namespace from multiple places.

@0x53A
Copy link
Contributor Author

0x53A commented Jan 21, 2025

alright no more time for right now, I need to go.

I'll look through it one more time when I'm back, then I'll rebase-squash.

Todo:

  • make sure naming is sane and consistent
  • simplify the path handling in inherent_impl.rs
  • this failure: linux-memcheck?
=================================================================
==3088==ERROR: LeakSanitizer: detected memory leaks

Direct leak of 232 byte(s) in 1 object(s) allocated from:
    #0 0x55ba3602b62e in malloc (/home/runner/work/_temp/godot_bin/godot.linuxbsd.editor.dev.x86_64.llvm.san+0x21c9c62e) (BuildId: 874db2a84ccfa9b3)
    #1 0x55ba55e654e6 in Memory::alloc_static(unsigned long, bool) /home/runner/work/godot4-nightly/godot4-nightly/core/os/memory.cpp:106:14
    #2 0x55ba55e65362 in operator new(unsigned long, char const*) /home/runner/work/godot4-nightly/godot4-nightly/core/os/memory.cpp:39:9
    #3 0x55ba58953974 in GDExtension::_register_extension_class_method(void*, void const*, GDExtensionClassMethodInfo const*) /home/runner/work/godot4-nightly/godot4-nightly/core/extension/gdextension.cpp:509:12
    #4 0x7f5c738209e0 in godot_core::registry::method::ClassMethodInfo::register_nonvirtual_class_method::h6f44d6b7c80457d4 /home/runner/work/gdext/gdext/godot-core/src/registry/method.rs:157:13
    #5 0x7f5c7381fff8 in godot_core::registry::method::ClassMethodInfo::register_extension_class_method::hfb75fa1a2544ea5f /home/runner/work/gdext/gdext/godot-core/src/registry/method.rs:147:13
    #6 0x7f5c730ae1e2 in itest::register_tests::func_test::_::__init::__inner_init::_$u7b$$u7b$closure$u7d$$u7d$::h9ae97532f88693d3 /home/runner/work/gdext/gdext/itest/rust/src/register_tests/func_test.rs:79:1
    #7 0x7f5c72dbfffd in core::ops::function::FnOnce::call_once::h0a8edd5c08646abf /rustc/f3d1d47fd84dfcf7f513be1dbad356e74c8f3b2b/library/core/src/ops/function.rs:250:5
    #8 0x7f5c72c230a2 in godot_core::registry::callbacks::register_user_methods_constants::ha0604185de96699d /home/runner/work/gdext/gdext/godot-core/src/registry/callbacks.rs:392:5
    #9 0x7f5c7381756d in godot_core::registry::class::auto_register_classes::h71cfbbdffdad7470 /home/runner/work/gdext/gdext/godot-core/src/registry/class.rs:228:9
    #10 0x7f5c7386886c in godot_core::init::gdext_on_level_init::h1d4172dd01e950e9 /home/runner/work/gdext/gdext/godot-core/src/init/mod.rs:161:5
    #11 0x7f5c72fd3a5a in godot_core::init::ffi_initialize_layer::_$u7b$$u7b$closure$u7d$$u7d$::hdf873678b0f13b97 /home/runner/work/gdext/gdext/godot-core/src/init/mod.rs:107:9
    #12 0x7f5c71290641 in godot_core::private::handle_panic_with_print::h4b774d0a828afef5 /home/runner/work/gdext/gdext/godot-core/src/private.rs:377:17
    #13 0x7f5c70cf8878 in godot_core::private::handle_panic::h129b9f0e49e1359a /home/runner/work/gdext/gdext/godot-core/src/private.rs:275:5
    #14 0x55ba5895bbdd in GDExtension::initialize_library(GDExtension::InitializationLevel) /home/runner/work/godot4-nightly/godot4-nightly/core/extension/gdextension.cpp:745:2
    #15 0x55ba589f5fb1 in GDExtensionManager::initialize_extensions(GDExtension::InitializationLevel) /home/runner/work/godot4-nightly/godot4-nightly/core/extension/gdextension_manager.cpp:228:12
    #16 0x55ba364c1f2b in Main::setup2(bool) /home/runner/work/godot4-nightly/godot4-nightly/main/main.cpp:3381:40
    #17 0x55ba364aed45 in Main::setup(char const*, int, char**, bool) /home/runner/work/godot4-nightly/godot4-nightly/main/main.cpp:2676:14
    #18 0x55ba36068f93 in main /home/runner/work/godot4-nightly/godot4-nightly/platform/linuxbsd/godot_linuxbsd.cpp:74:14
    #19 0x7f5c7be29d8f  (/lib/x86_64-linux-gnu/libc.so.6+0x29d8f) (BuildId: 490fef8403240c91833978d494d39e537409b92e)

Direct leak of 232 byte(s) in 1 object(s) allocated from:
    #0 0x55ba3602b62e in malloc (/home/runner/work/_temp/godot_bin/godot.linuxbsd.editor.dev.x86_64.llvm.san+0x21c9c62e) (BuildId: 874db2a84ccfa9b3)
    #1 0x55ba55e654e6 in Memory::alloc_static(unsigned long, bool) /home/runner/work/godot4-nightly/godot4-nightly/core/os/memory.cpp:106:14
    #2 0x55ba55e65362 in operator new(unsigned long, char const*) /home/runner/work/godot4-nightly/godot4-nightly/core/os/memory.cpp:39:9
    #3 0x55ba58953974 in GDExtension::_register_extension_class_method(void*, void const*, GDExtensionClassMethodInfo const*) /home/runner/work/godot4-nightly/godot4-nightly/core/extension/gdextension.cpp:509:12
    #4 0x7f5c738209e0 in godot_core::registry::method::ClassMethodInfo::register_nonvirtual_class_method::h6f44d6b7c80457d4 /home/runner/work/gdext/gdext/godot-core/src/registry/method.rs:157:13
    #5 0x7f5c7381fff8 in godot_core::registry::method::ClassMethodInfo::register_extension_class_method::hfb75fa1a2544ea5f /home/runner/work/gdext/gdext/godot-core/src/registry/method.rs:147:13
    #6 0x7f5c730ae00f in itest::register_tests::func_test::_::__init::__inner_init::_$u7b$$u7b$closure$u7d$$u7d$::h9ae97532f88693d3 /home/runner/work/gdext/gdext/itest/rust/src/register_tests/func_test.rs:79:1
    #7 0x7f5c72dbfffd in core::ops::function::FnOnce::call_once::h0a8edd5c08646abf /rustc/f3d1d47fd84dfcf7f513be1dbad356e74c8f3b2b/library/core/src/ops/function.rs:250:5
    #8 0x7f5c72c230a2 in godot_core::registry::callbacks::register_user_methods_constants::ha0604185de96699d /home/runner/work/gdext/gdext/godot-core/src/registry/callbacks.rs:392:5
    #9 0x7f5c7381756d in godot_core::registry::class::auto_register_classes::h71cfbbdffdad7470 /home/runner/work/gdext/gdext/godot-core/src/registry/class.rs:228:9
    #10 0x7f5c7386886c in godot_core::init::gdext_on_level_init::h1d4172dd01e950e9 /home/runner/work/gdext/gdext/godot-core/src/init/mod.rs:161:5
    #11 0x7f5c72fd3a5a in godot_core::init::ffi_initialize_layer::_$u7b$$u7b$closure$u7d$$u7d$::hdf873678b0f13b97 /home/runner/work/gdext/gdext/godot-core/src/init/mod.rs:107:9
    #12 0x7f5c71290641 in godot_core::private::handle_panic_with_print::h4b774d0a828afef5 /home/runner/work/gdext/gdext/godot-core/src/private.rs:377:17
    #13 0x7f5c70cf8878 in godot_core::private::handle_panic::h129b9f0e49e1359a /home/runner/work/gdext/gdext/godot-core/src/private.rs:275:5
    #14 0x55ba5895bbdd in GDExtension::initialize_library(GDExtension::InitializationLevel) /home/runner/work/godot4-nightly/godot4-nightly/core/extension/gdextension.cpp:745:2
    #15 0x55ba589f5fb1 in GDExtensionManager::initialize_extensions(GDExtension::InitializationLevel) /home/runner/work/godot4-nightly/godot4-nightly/core/extension/gdextension_manager.cpp:228:12
    #16 0x55ba364c1f2b in Main::setup2(bool) /home/runner/work/godot4-nightly/godot4-nightly/main/main.cpp:3381:40
    #17 0x55ba364aed45 in Main::setup(char const*, int, char**, bool) /home/runner/work/godot4-nightly/godot4-nightly/main/main.cpp:2676:14
    #18 0x55ba36068f93 in main /home/runner/work/godot4-nightly/godot4-nightly/platform/linuxbsd/godot_linuxbsd.cpp:74:14
    #19 0x7f5c7be29d8f  (/lib/x86_64-linux-gnu/libc.so.6+0x29d8f) (BuildId: 490fef8403240c91833978d494d39e537409b92e)

Indirect leak of 20 byte(s) in 1 object(s) allocated from:
    #0 0x55ba3602b62e in malloc (/home/runner/work/_temp/godot_bin/godot.linuxbsd.editor.dev.x86_64.llvm.san+0x21c9c62e) (BuildId: 874db2a84ccfa9b3)
    #1 0x55ba55e654e6 in Memory::alloc_static(unsigned long, bool) /home/runner/work/godot4-nightly/godot4-nightly/core/os/memory.cpp:106:14
    #2 0x55ba3957bf2d in Variant::Type* memnew_arr_template<Variant::Type>(unsigned long) /home/runner/work/godot4-nightly/godot4-nightly/./core/os/memory.h:180:28
    #3 0x55ba58b24fc5 in MethodBind::_generate_argument_types(int) /home/runner/work/godot4-nightly/godot4-nightly/core/object/method_bind.cpp:104:24
    #4 0x55ba5898964e in GDExtensionMethodBind::update(GDExtensionClassMethodInfo const*) /home/runner/work/godot4-nightly/godot4-nightly/core/extension/gdextension.cpp:223:3
    #5 0x55ba5896bf65 in GDExtensionMethodBind::GDExtensionMethodBind(GDExtensionClassMethodInfo const*) /home/runner/work/godot4-nightly/godot4-nightly/core/extension/gdextension.cpp:237:3
    #6 0x55ba589539b7 in GDExtension::_register_extension_class_method(void*, void const*, GDExtensionClassMethodInfo const*) /home/runner/work/godot4-nightly/godot4-nightly/core/extension/gdextension.cpp:509:12
    #7 0x7f5c738209e0 in godot_core::registry::method::ClassMethodInfo::register_nonvirtual_class_method::h6f44d6b7c80457d4 /home/runner/work/gdext/gdext/godot-core/src/registry/method.rs:157:13
    #8 0x7f5c7381fff8 in godot_core::registry::method::ClassMethodInfo::register_extension_class_method::hfb75fa1a2544ea5f /home/runner/work/gdext/gdext/godot-core/src/registry/method.rs:147:13
    #9 0x7f5c730ae1e2 in itest::register_tests::func_test::_::__init::__inner_init::_$u7b$$u7b$closure$u7d$$u7d$::h9ae97532f88693d3 /home/runner/work/gdext/gdext/itest/rust/src/register_tests/func_test.rs:79:1
    #10 0x7f5c72dbfffd in core::ops::function::FnOnce::call_once::h0a8edd5c08646abf /rustc/f3d1d47fd84dfcf7f513be1dbad356e74c8f3b2b/library/core/src/ops/function.rs:250:5
    #11 0x7f5c72c230a2 in godot_core::registry::callbacks::register_user_methods_constants::ha0604185de96699d /home/runner/work/gdext/gdext/godot-core/src/registry/callbacks.rs:392:5
    #12 0x7f5c7381756d in godot_core::registry::class::auto_register_classes::h71cfbbdffdad7470 /home/runner/work/gdext/gdext/godot-core/src/registry/class.rs:228:9
    #13 0x7f5c7386886c in godot_core::init::gdext_on_level_init::h1d4172dd01e950e9 /home/runner/work/gdext/gdext/godot-core/src/init/mod.rs:161:5
    #14 0x7f5c72fd3a5a in godot_core::init::ffi_initialize_layer::_$u7b$$u7b$closure$u7d$$u7d$::hdf873678b0f13b97 /home/runner/work/gdext/gdext/godot-core/src/init/mod.rs:107:9
    #15 0x7f5c71290641 in godot_core::private::handle_panic_with_print::h4b774d0a828afef5 /home/runner/work/gdext/gdext/godot-core/src/private.rs:377:17
    #16 0x7f5c70cf8878 in godot_core::private::handle_panic::h129b9f0e49e1359a /home/runner/work/gdext/gdext/godot-core/src/private.rs:275:5
    #17 0x55ba5895bbdd in GDExtension::initialize_library(GDExtension::InitializationLevel) /home/runner/work/godot4-nightly/godot4-nightly/core/extension/gdextension.cpp:745:2
    #18 0x55ba589f5fb1 in GDExtensionManager::initialize_extensions(GDExtension::InitializationLevel) /home/runner/work/godot4-nightly/godot4-nightly/core/extension/gdextension_manager.cpp:228:12
    #19 0x55ba364c1f2b in Main::setup2(bool) /home/runner/work/godot4-nightly/godot4-nightly/main/main.cpp:3381:40
    #20 0x55ba364aed45 in Main::setup(char const*, int, char**, bool) /home/runner/work/godot4-nightly/godot4-nightly/main/main.cpp:2676:14
    #21 0x55ba36068f93 in main /home/runner/work/godot4-nightly/godot4-nightly/platform/linuxbsd/godot_linuxbsd.cpp:74:14
    #22 0x7f5c7be29d8f  (/lib/x86_64-linux-gnu/libc.so.6+0x29d8f) (BuildId: 490fef8403240c91833978d494d39e537409b92e)

Indirect leak of 20 byte(s) in 1 object(s) allocated from:
    #0 0x55ba3602b62e in malloc (/home/runner/work/_temp/godot_bin/godot.linuxbsd.editor.dev.x86_64.llvm.san+0x21c9c62e) (BuildId: 874db2a84ccfa9b3)
    #1 0x55ba55e654e6 in Memory::alloc_static(unsigned long, bool) /home/runner/work/godot4-nightly/godot4-nightly/core/os/memory.cpp:106:14
    #2 0x55ba3957bf2d in Variant::Type* memnew_arr_template<Variant::Type>(unsigned long) /home/runner/work/godot4-nightly/godot4-nightly/./core/os/memory.h:180:28
    #3 0x55ba58b24fc5 in MethodBind::_generate_argument_types(int) /home/runner/work/godot4-nightly/godot4-nightly/core/object/method_bind.cpp:104:24
    #4 0x55ba5898964e in GDExtensionMethodBind::update(GDExtensionClassMethodInfo const*) /home/runner/work/godot4-nightly/godot4-nightly/core/extension/gdextension.cpp:223:3
    #5 0x55ba5896bf65 in GDExtensionMethodBind::GDExtensionMethodBind(GDExtensionClassMethodInfo const*) /home/runner/work/godot4-nightly/godot4-nightly/core/extension/gdextension.cpp:237:3
    #6 0x55ba589539b7 in GDExtension::_register_extension_class_method(void*, void const*, GDExtensionClassMethodInfo const*) /home/runner/work/godot4-nightly/godot4-nightly/core/extension/gdextension.cpp:509:12
    #7 0x7f5c738209e0 in godot_core::registry::method::ClassMethodInfo::register_nonvirtual_class_method::h6f44d6b7c80457d4 /home/runner/work/gdext/gdext/godot-core/src/registry/method.rs:157:13
    #8 0x7f5c7381fff8 in godot_core::registry::method::ClassMethodInfo::register_extension_class_method::hfb75fa1a2544ea5f /home/runner/work/gdext/gdext/godot-core/src/registry/method.rs:147:13
    #9 0x7f5c730ae00f in itest::register_tests::func_test::_::__init::__inner_init::_$u7b$$u7b$closure$u7d$$u7d$::h9ae97532f88693d3 /home/runner/work/gdext/gdext/itest/rust/src/register_tests/func_test.rs:79:1
    #10 0x7f5c72dbfffd in core::ops::function::FnOnce::call_once::h0a8edd5c08646abf /rustc/f3d1d47fd84dfcf7f513be1dbad356e74c8f3b2b/library/core/src/ops/function.rs:250:5
    #11 0x7f5c72c230a2 in godot_core::registry::callbacks::register_user_methods_constants::ha0604185de96699d /home/runner/work/gdext/gdext/godot-core/src/registry/callbacks.rs:392:5
    #12 0x7f5c738[1756](https://github.com/godot-rust/gdext/actions/runs/12881698201/job/35912681146#step:3:1772)d in godot_core::registry::class::auto_register_classes::h71cfbbdffdad7470 /home/runner/work/gdext/gdext/godot-core/src/registry/class.rs:228:9
    #13 0x7f5c7386886c in godot_core::init::gdext_on_level_init::h1d4172dd01e950e9 /home/runner/work/gdext/gdext/godot-core/src/init/mod.rs:161:5
    #14 0x7f5c72fd3a5a in godot_core::init::ffi_initialize_layer::_$u7b$$u7b$closure$u7d$$u7d$::hdf873678b0f13b97 /home/runner/work/gdext/gdext/godot-core/src/init/mod.rs:107:9
    #15 0x7f5c71290641 in godot_core::private::handle_panic_with_print::h4b774d0a828afef5 /home/runner/work/gdext/gdext/godot-core/src/private.rs:377:17
    #16 0x7f5c70cf8878 in godot_core::private::handle_panic::h129b9f0e49e1359a /home/runner/work/gdext/gdext/godot-core/src/private.rs:275:5
    #17 0x55ba5895bbdd in GDExtension::initialize_library(GDExtension::InitializationLevel) /home/runner/work/godot4-nightly/godot4-nightly/core/extension/gdextension.cpp:745:2
    #18 0x55ba589f5fb1 in GDExtensionManager::initialize_extensions(GDExtension::InitializationLevel) /home/runner/work/godot4-nightly/godot4-nightly/core/extension/gdextension_manager.cpp:228:12
    #19 0x55ba364c1f2b in Main::setup2(bool) /home/runner/work/godot4-nightly/godot4-nightly/main/main.cpp:3381:40
    #20 0x55ba364aed45 in Main::setup(char const*, int, char**, bool) /home/runner/work/godot4-nightly/godot4-nightly/main/main.cpp:2676:14
    #21 0x55ba36068f93 in main /home/runner/work/godot4-nightly/godot4-nightly/platform/linuxbsd/godot_linuxbsd.cpp:74:14
    #22 0x7f5c7be29d8f  (/lib/x86_64-linux-gnu/libc.so.6+0x29d8f) (BuildId: 490fef8403240c91833978d494d39e537409b92e)

SUMMARY: AddressSanitizer: 504 byte(s) leaked in 4 allocation(s).

@0x53A
Copy link
Contributor Author

0x53A commented Jan 21, 2025

regarding the memory leak,

the memory is allocated here: https://github.com/godotengine/godot/blob/1b7b009674e05b566be11a5377b935f5d3d6c0ee/core/extension/gdextension.cpp#L509

and I could see it being leaked, if it returns with an error here, in case the method name is duplicated: https://github.com/godotengine/godot/blob/master/core/object/class_db.cpp#L1881

why would the method name be duplicated? no idea

@0x53A
Copy link
Contributor Author

0x53A commented Jan 21, 2025

After looking into this, I believe it's a bug in godot: godotengine/godot#101870

It's triggered because in mod.rs#244, it doesn't handle cfg_attr

pub(crate) fn extract_cfg_attrs(
    attrs: &[venial::Attribute],
) -> impl IntoIterator<Item = &venial::Attribute> {
    attrs.iter().filter(|attr| {
        attr.get_single_path_segment()
            .is_some_and(|name| name == "cfg")
    })
}

and I added a test case that uses cfg_attr, therefore the method is registered multiple times with godot, hitting the memory leak there

…ue.to_token_stream().to_string().contains("cfg("))
@0x53A 0x53A changed the title in #[var]s, handle renamed #[func]s [WIP] in #[var]s, handle renamed #[func]s Jan 21, 2025
@Bromeon
Copy link
Member

Bromeon commented Jan 21, 2025

Quick tip: instead of renaming the title to [WIP], you can mark the PR as a draft. This also prevents accidental merging.

@Bromeon Bromeon added bug c: register Register classes, functions and other symbols to GDScript labels Jan 21, 2025
@0x53A 0x53A marked this pull request as draft January 21, 2025 09:25
@@ -84,6 +86,8 @@ pub fn transform_inherent_impl(
let (funcs, signals) = process_godot_fns(&class_name, &mut impl_block, meta.secondary)?;
let consts = process_godot_constants(&mut impl_block)?;

let func_export_name_constants = make_function_registered_name_constants(&funcs, &class_name);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's fine if the local variables are more terse/concise than the function names. Context matters, and if you see the

let func_name_constants = make_function_registered_name_constants(...);

once, then you know what func_name_constants refers to. I think using overly long variable names makes expressions harder to read; the "registered" doesn't really add useful information to understand the codegen.

There's also the point of scope: global function names are visible in their entire module, while variable names are limited to the block/function in which they are declared. It's much easier to mentally keep track of a block, than it is of a module; and there's less potential for collisions.

@@ -119,6 +125,32 @@ pub fn transform_inherent_impl(
});
};

let class_functions_name = format_function_registered_name_struct_name(&class_name);
let class_functions_path: Path = match impl_block.self_ty.as_path().unwrap().segments.as_slice()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The way you use unwrap here indicates that it's infallible. In general, this would need a brief comment documenting why.

Here, it might be better to pass in previously evaluated values via parameters, making the extraction unnecessary. This especially holds true here since as_path() isn't cheap to compute, even less so with some upcoming validations in venial.

Might also make sense to extract the match expression into its own variable 🙂

@@ -184,6 +191,9 @@ pub fn transform_inherent_impl(
let result = quote! {
#impl_block
#fill_storage
impl #class_name{
#( #func_export_name_constants )*
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you very much!

Makes total sense, could you maybe add 1 line of comments next to each substitution?

// Getter/setter generated from struct fields: place constant next to function in impl.

// User-defined functions: collectively place constants in dedicated impl block.

Comment on lines +329 to +337
let cfg_attributes = extract_cfg_attrs(&func.external_attributes)
.into_iter()
.collect();
make_function_registered_name_constant(
class_name,
&func.signature_info.method_name,
&func.registered_name,
&cfg_attributes,
)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Empty line to separate 2 multi-line statements:

Suggested change
let cfg_attributes = extract_cfg_attrs(&func.external_attributes)
.into_iter()
.collect();
make_function_registered_name_constant(
class_name,
&func.signature_info.method_name,
&func.registered_name,
&cfg_attributes,
)
let cfg_attributes = extract_cfg_attrs(&func.external_attributes)
.into_iter()
.collect();
make_function_registered_name_constant(
class_name,
&func.signature_info.method_name,
&func.registered_name,
&cfg_attributes,
)

Comment on lines +349 to +350
registered_name: &Option<String>,
attributes: &Vec<&Attribute>,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
registered_name: &Option<String>,
attributes: &Vec<&Attribute>,
registered_name: Option<&String>,
attributes: &[&Attribute],

This signatures are more flexible. The Vec derefs to slices, while Option::as_ref() converts &Option<String> to Option<&String>.

This also makes the call-site less awkward (both &None and &vec[] are unusual).

};

let doc_comment = format!(
"The rust function `{}` is registered with godot as `{}`.",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Capitalize proper names:

Suggested change
"The rust function `{}` is registered with godot as `{}`.",
"The Rust function `{}` is registered with Godot as `{}`.",

};

quote! {
#(#attributes)*
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that would only be relevant for the special case #[cfg_attr(/**/, cfg)], right?

You're right, this probably makes no sense to handle. No one writes code like this 😀

class_name: &Ident,
func_name: &Ident,
) -> Ident {
format_ident!("__gdext_func_{}_{}", class_name, func_name)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
format_ident!("__gdext_func_{}_{}", class_name, func_name)
format_ident!("__gdext_func_{class_name}_{func_name}")


# Assert specific names
assert(gdext_props.has("int_val"), "should have a property named 'int_val'")
assert(gdext_props.has("RenamedFunc"), "should have a property named 'RenamedFunc'") # godot automatically adds a property of the class name
Copy link
Member

@Bromeon Bromeon Jan 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
assert(gdext_props.has("RenamedFunc"), "should have a property named 'RenamedFunc'") # godot automatically adds a property of the class name
# Godot automatically adds a property of the class name (acts as the top-level category in the inspector).
assert(gdext_props.has("RenamedFunc"), "should have a property named 'RenamedFunc'")

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Godot automatically adds a property of the class name.

Do you know more about this? Some Godot quirk, or why is the class name added as a property? 🤔

Copy link
Contributor

@Yarwin Yarwin Jan 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is category – Godot adds property with PropertyFlagUsage of value 128 to any object as a very first element. Thanks to this one is able to see object class name in the top bar of the inspector.

While undocumented it has been stable since… hmm, Godot 3? and shouldn't be changed until someone do major rewrite/refactor of properties (which is unlikely? This parts works well)

Copy link
Contributor

@Yarwin Yarwin Jan 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have more pressing question – what does this assert check for 🤔? Validity of get_property_list?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shape of the object, that it exports the two expected functions (and only them, nothing extra)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adjusted my suggestion above to briefly mention this.



func test_RenamedFunc_shape():
# note: RenamedFunc is in property_test.rs
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please everywhere start with uppercase and end with period 🙂

Suggested change
# note: RenamedFunc is in property_test.rs
# Note: RenamedFunc is located in property_test.rs.

@Bromeon
Copy link
Member

Bromeon commented Jan 21, 2025

proposal: to not pollute the struct namespace with all these constants, I could create a dummy struct.

That sounds like a good idea!
It could be called {ClassName}_Funcs (to express that it contains the #[func] data).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug c: register Register classes, functions and other symbols to GDScript
Projects
None yet
Development

Successfully merging this pull request may close these issues.

#[var(set = function_name)] is not compatible with #[func(rename = gdscript_name)]
4 participants