diff --git a/godot-core/src/registry.rs b/godot-core/src/registry.rs index c4a374f7e..cd9aac365 100644 --- a/godot-core/src/registry.rs +++ b/godot-core/src/registry.rs @@ -227,6 +227,12 @@ fn fill_class_info(component: PluginComponent, c: &mut ClassRegistrationInfo) { fill_into( &mut c.godot_params.create_instance_func, generated_create_fn, + ) + .unwrap_or_else(|_| + panic!( + "Godot class `{}` is defined multiple times in Rust; you can rename them with #[class(rename=NewName)]", + c.class_name, + ) ); c.godot_params.free_instance_func = Some(free_fn); } @@ -245,7 +251,9 @@ fn fill_class_info(component: PluginComponent, c: &mut ClassRegistrationInfo) { get_virtual_fn, } => { c.user_register_fn = user_register_fn; - fill_into(&mut c.godot_params.create_instance_func, user_create_fn); + // this shouldn't panic since rustc will error if there's + // multiple `impl {Class}Virtual for Thing` definitions + fill_into(&mut c.godot_params.create_instance_func, user_create_fn).unwrap(); c.godot_params.to_string_func = user_to_string_fn; c.godot_params.notification_func = user_on_notification_fn; c.godot_params.get_virtual_func = Some(get_virtual_fn); @@ -260,12 +268,13 @@ fn fill_class_info(component: PluginComponent, c: &mut ClassRegistrationInfo) { } /// If `src` is occupied, it moves the value into `dst`, while ensuring that no previous value is present in `dst`. -fn fill_into(dst: &mut Option, src: Option) { +fn fill_into(dst: &mut Option, src: Option) -> Result<(), ()> { match (dst, src) { (dst @ None, src) => *dst = src, - (Some(_), Some(_)) => panic!("option already filled"), + (Some(_), Some(_)) => return Err(()), (Some(_), None) => { /* do nothing */ } } + Ok(()) } /// Registers a class with given the dynamic type information `info`. diff --git a/godot-macros/src/class/derive_godot_class.rs b/godot-macros/src/class/derive_godot_class.rs index ada6a8183..e8997d367 100644 --- a/godot-macros/src/class/derive_godot_class.rs +++ b/godot-macros/src/class/derive_godot_class.rs @@ -21,7 +21,10 @@ pub fn derive_godot_class(decl: Declaration) -> ParseResult { let fields = parse_fields(class)?; let class_name = &class.name; - let class_name_str = class.name.to_string(); + let class_name_str: String = struct_cfg + .rename + .map_or_else(|| class.name.clone(), |rename| rename) + .to_string(); let class_name_cstr = util::cstr_u8_slice(&class_name_str); let class_name_obj = util::class_name_obj(class_name); @@ -108,6 +111,7 @@ fn parse_struct_attributes(class: &Struct) -> ParseResult { let mut has_generated_init = false; let mut is_tool = false; let mut is_editor_plugin = false; + let mut rename: Option = None; // #[class] attribute on struct if let Some(mut parser) = KvParser::parse(&class.attributes, "class")? { @@ -127,6 +131,7 @@ fn parse_struct_attributes(class: &Struct) -> ParseResult { if parser.handle_alone_ident("editor_plugin")?.is_some() { is_editor_plugin = true; } + rename = parser.handle_ident("rename")?; parser.finish()?; } @@ -136,6 +141,7 @@ fn parse_struct_attributes(class: &Struct) -> ParseResult { has_generated_init, is_tool, is_editor_plugin, + rename, }) } @@ -216,6 +222,7 @@ struct ClassAttributes { has_generated_init: bool, is_tool: bool, is_editor_plugin: bool, + rename: Option, } fn make_godot_init_impl(class_name: &Ident, fields: Fields) -> TokenStream { diff --git a/godot-macros/src/lib.rs b/godot-macros/src/lib.rs index fd722a70b..bc986fbcf 100644 --- a/godot-macros/src/lib.rs +++ b/godot-macros/src/lib.rs @@ -332,6 +332,30 @@ use crate::util::ident; /// /// This should usually be combined with `#[class(tool)]` so that the code you write will actually run in the /// editor. +/// +/// # Class Renaming +/// +/// You may want to have structs with the same name. With Rust, this is allowed using `mod`. However in GDScript, +/// there are no modules, namespaces, or any such disambiguation. Therefore, you need to change the names before they +/// can get to Godot. You can use the `rename` key while defining your `GodotClass` for this. +/// +/// ``` +/// mod animal { +/// # use godot::prelude::*; +/// #[derive(GodotClass)] +/// #[class(init, rename=AnimalToad)] +/// pub struct Toad {} +/// } +/// +/// mod npc { +/// # use godot::prelude::*; +/// #[derive(GodotClass)] +/// #[class(init, rename=NpcToad)] +/// pub struct Toad {} +/// } +/// ``` +/// +/// These classes will appear in the Godot editor and GDScript as "AnimalToad" or "NpcToad". #[proc_macro_derive(GodotClass, attributes(class, base, var, export, init, signal))] pub fn derive_godot_class(input: TokenStream) -> TokenStream { translate(input, class::derive_godot_class) diff --git a/itest/rust/src/object_tests/class_rename_test.rs b/itest/rust/src/object_tests/class_rename_test.rs new file mode 100644 index 000000000..448af7de1 --- /dev/null +++ b/itest/rust/src/object_tests/class_rename_test.rs @@ -0,0 +1,33 @@ +/* + * 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::framework::itest; +use godot::prelude::*; + +pub mod dont_rename { + use super::*; + + #[derive(GodotClass)] + pub struct RepeatMe {} +} + +pub mod rename { + use super::*; + + #[derive(GodotClass)] + #[class(rename = NoRepeat)] + pub struct RepeatMe {} +} + +#[itest] +fn renaming_changes_the_name() { + assert_ne!( + dont_rename::RepeatMe::class_name(), + rename::RepeatMe::class_name() + ); + assert_eq!(dont_rename::RepeatMe::class_name().as_str(), "RepeatMe"); + assert_eq!(rename::RepeatMe::class_name().as_str(), "NoRepeat"); +} diff --git a/itest/rust/src/object_tests/mod.rs b/itest/rust/src/object_tests/mod.rs index 2a52061f6..03aa8404f 100644 --- a/itest/rust/src/object_tests/mod.rs +++ b/itest/rust/src/object_tests/mod.rs @@ -5,6 +5,7 @@ */ mod base_test; +mod class_rename_test; mod object_test; mod property_test; mod singleton_test;