Skip to content

can't compile a macro_rules! macro that includes #[godot_api] proc macro with attempt to impl a godot node interface #996

@bcolloran

Description

@bcolloran

Per discord thread here:
https://discord.com/channels/723850269347283004/1192118392660963338/threads/1323828276720046102

Here is a small repro case:

use godot::prelude::*;

trait Registerable {
    fn print_id(&self);
}

fn register_id_on_tree_entry<T: Registerable>(node: &mut T) {
    node.print_id()
}

#[derive(GodotClass)]
#[class(init, base=Node3D)]
pub struct Enemy {
    #[base]
    base: Base<Node3D>,
    id: u32,
}

impl Registerable for Enemy {
    fn print_id(&self) {
        println!("id: {}", self.id);
    }
}

#[macro_export]
macro_rules! impl_register_on_tree_enter_FAILING {
    ($inner_type:ty, $node_interface:path) => {
        #[godot_api]
        impl $node_interface for $inner_type {
            fn enter_tree(&mut self) {
                register_id_on_tree_entry(self);
            }
        }
    };
}

impl_register_on_tree_enter_FAILING!(Enemy, INode3D);

Which fails to compile with this error:

error: invalid Self type for #[godot_api] impl
  --> src/ideas/macro_bug_repro.rs:29:9
   |
29 | /         impl $node_interface for $inner_type {
30 | |             fn enter_tree(&mut self) {
31 | |                 register_id_on_tree_entry(self);
32 | |             }
33 | |         }
   | |_________^
...
37 |   impl_register_on_tree_enter_FAILING!(Enemy, INode3D);
   |   ---------------------------------------------------- in this macro invocation
   |
   = note: this error originates in the macro `impl_register_on_tree_enter_FAILING` (in Nightly builds, run with -Z macro-backtrace for more info)

the error message error: invalid Self type for #[godot_api] impl can be traced to here:

return bail!(decl, "invalid Self type for #[godot_api] impl");

If you expand this in VSCode, and paste it in place of the macro invocation, it compiles without issue. Here is that expanded macro for reference:

// Recursive expansion of impl_register_on_tree_enter_FAILING! macro
// ==================================================================

impl INode3D for Enemy {
    fn enter_tree(&mut self) {
        register_id_on_tree_entry(self);
    }
}
impl ::godot::private::You_forgot_the_attribute__godot_api for Enemy {}

impl ::godot::obj::cap::ImplementsGodotVirtual for Enemy {
    fn __virtual_call(name: &str) -> ::godot::sys::GDExtensionClassCallVirtual {
        use ::godot::obj::UserClass as _;
        match name {
            "_enter_tree" => {
                use ::godot::sys;
                type Sig = ((),);
                unsafe extern "C" fn virtual_fn(
                    instance_ptr: sys::GDExtensionClassInstancePtr,
                    args_ptr: *const sys::GDExtensionConstTypePtr,
                    ret: sys::GDExtensionTypePtr,
                ) {
                    let call_ctx = ::godot::meta::CallContext::func("Enemy", "enter_tree");
                    let _success = ::godot::private::handle_ptrcall_panic(&call_ctx, || {
                        <Sig as ::godot::meta::PtrcallSignatureTuple>::in_ptrcall(
                            instance_ptr,
                            &call_ctx,
                            args_ptr,
                            ret,
                            |instance_ptr, params| {
                                let () = params;
                                let storage =
                                    unsafe { ::godot::private::as_storage::<Enemy>(instance_ptr) };
                                let mut instance = ::godot::private::Storage::get_mut(storage);
                                instance.enter_tree()
                            },
                            sys::PtrcallType::Virtual,
                        )
                    });
                }
                Some(virtual_fn)
            }
            "_ready" => {
                use ::godot::sys;
                type Sig = ((),);
                unsafe extern "C" fn virtual_fn(
                    instance_ptr: sys::GDExtensionClassInstancePtr,
                    args_ptr: *const sys::GDExtensionConstTypePtr,
                    ret: sys::GDExtensionTypePtr,
                ) {
                    let call_ctx = ::godot::meta::CallContext::func("Enemy", "ready");
                    let _success = ::godot::private::handle_ptrcall_panic(&call_ctx, || {
                        <Sig as ::godot::meta::PtrcallSignatureTuple>::in_ptrcall(
                            instance_ptr,
                            &call_ctx,
                            args_ptr,
                            ret,
                            |instance_ptr, params| {
                                let () = params;
                                let storage =
                                    unsafe { ::godot::private::as_storage::<Enemy>(instance_ptr) };
                                let mut instance = ::godot::private::Storage::get_mut(storage);
                                instance.__before_ready();
                            },
                            sys::PtrcallType::Virtual,
                        )
                    });
                }
                Some(virtual_fn)
            }
            _ => None,
        }
    }
}
const _: () = {
    #[allow(non_upper_case_globals)]
    #[used]
    #[cfg_attr(target_os = "windows", link_section = ".CRT$XCU")]
    #[cfg_attr(target_os = "ios", link_section = "__DATA,__mod_init_func")]
    #[cfg_attr(target_os = "macos", link_section = "__DATA,__mod_init_func")]
    #[cfg_attr(target_os = "android", link_section = ".init_array")]
    #[cfg_attr(target_os = "dragonfly", link_section = ".init_array")]
    #[cfg_attr(target_os = "freebsd", link_section = ".init_array")]
    #[cfg_attr(target_os = "linux", link_section = ".init_array")]
    #[cfg_attr(target_os = "netbsd", link_section = ".init_array")]
    #[cfg_attr(target_os = "openbsd", link_section = ".init_array")]
    static __init: extern "C" fn() = {
        #[cfg_attr(target_os = "android", link_section = ".text.startup")]
        #[cfg_attr(target_os = "linux", link_section = ".text.startup")]
        extern "C" fn __inner_init() {
            {
                let mut guard = ::godot::private::__godot_rust_plugin___GODOT_PLUGIN_REGISTRY
                    .lock()
                    .unwrap();
                guard.push(
                    (::godot::private::ClassPlugin {
                        class_name: <Enemy as ::godot::obj::GodotClass>::class_name(),
                        item: ::godot::private::PluginItem::ITraitImpl {
                            user_register_fn: None,
                            user_create_fn: None,
                            user_recreate_fn: None,
                            user_to_string_fn: None,
                            user_on_notification_fn: None,
                            user_set_fn: None,
                            user_get_fn: None,
                            user_get_property_list_fn: None,
                            user_free_property_list_fn: None,
                            user_property_get_revert_fn: None,
                            user_property_can_revert_fn: None,
                            get_virtual_fn: ::godot::private::callbacks::get_virtual::<Enemy>,
                        },
                        init_level: <Enemy as ::godot::obj::GodotClass>::INIT_LEVEL,
                    }),
                );
            }
        }
        __inner_init
    };
    #[cfg(target_family = "wasm")]
    godot_ffi::gensym! {
        godot_ffi::plugin_execute_pre_main_wasm!()
    }
};

@Bromeon also recommended trying the macro without using :ty and :path, and indeed, using :ident instead does compile:

// (... other code from above ...)

#[macro_export]
macro_rules! impl_register_on_tree_enter_WORKING {
    ($inner_type:ident, $node_interface:ident) => {
        #[godot_api]
        impl $node_interface for $inner_type {
            fn enter_tree(&mut self) {
                register_id_on_tree_entry(self);
            }
        }
    };
}

impl_register_on_tree_enter_WORKING!(Enemy, INode3D);

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugc: registerRegister classes, functions and other symbols to GDScriptstatus: upstreamDepending on upstream fix (typically Godot)

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions