diff --git a/godot-macros/src/class/data_models/field_var.rs b/godot-macros/src/class/data_models/field_var.rs index 173017a2e..9ec2271bd 100644 --- a/godot-macros/src/class/data_models/field_var.rs +++ b/godot-macros/src/class/data_models/field_var.rs @@ -18,6 +18,8 @@ use crate::{util, ParseResult}; /// Store info from `#[var]` attribute. #[derive(Clone, Debug)] pub struct FieldVar { + /// What name this variable should have in Godot, if `None` then the Rust name should be used. + pub rename: Option, pub getter: GetterSetter, pub setter: GetterSetter, pub hint: FieldHint, @@ -29,6 +31,7 @@ impl FieldVar { /// Parse a `#[var]` attribute to a `FieldVar` struct. /// /// Possible keys: + /// - `rename = ident` /// - `get = expr` /// - `set = expr` /// - `hint = ident` @@ -36,6 +39,7 @@ impl FieldVar { /// - `usage_flags = pub(crate) fn new_from_kv(parser: &mut KvParser) -> ParseResult { let span = parser.span(); + let rename = parser.handle_ident("rename")?; let getter = GetterSetter::parse(parser, "get")?; let setter = GetterSetter::parse(parser, "set")?; @@ -64,6 +68,7 @@ impl FieldVar { }; Ok(FieldVar { + rename, getter, setter, hint, @@ -84,6 +89,7 @@ impl FieldVar { impl Default for FieldVar { fn default() -> Self { Self { + rename: Default::default(), getter: Default::default(), setter: Default::default(), hint: Default::default(), @@ -131,11 +137,12 @@ impl GetterSetter { class_name: &Ident, kind: GetSet, field: &Field, + rename: &Option, ) -> Option { match self { GetterSetter::Omitted => None, GetterSetter::Generated => Some(GetterSetterImpl::from_generated_impl( - class_name, kind, field, + class_name, kind, field, rename, )), GetterSetter::Custom(function_name) => { Some(GetterSetterImpl::from_custom_impl(function_name)) @@ -173,14 +180,21 @@ pub struct GetterSetterImpl { } impl GetterSetterImpl { - fn from_generated_impl(class_name: &Ident, kind: GetSet, field: &Field) -> Self { + fn from_generated_impl( + class_name: &Ident, + kind: GetSet, + field: &Field, + rename: &Option, + ) -> Self { let Field { name: field_name, ty: field_type, .. } = field; - let function_name = format_ident!("{}{field_name}", kind.prefix()); + let var_name = rename.as_ref().unwrap_or(field_name); + + let function_name = format_ident!("{}{var_name}", kind.prefix()); let signature; let function_body; diff --git a/godot-macros/src/class/data_models/property.rs b/godot-macros/src/class/data_models/property.rs index 75671575e..b98bed71c 100644 --- a/godot-macros/src/class/data_models/property.rs +++ b/godot-macros/src/class/data_models/property.rs @@ -76,10 +76,8 @@ pub fn make_property_impl(class_name: &Ident, fields: &Fields) -> TokenStream { }; make_groups_registrations(group, subgroup, &mut export_tokens, class_name); - - let field_name = field_ident.to_string(); - let FieldVar { + rename, getter, setter, hint, @@ -87,6 +85,8 @@ pub fn make_property_impl(class_name: &Ident, fields: &Fields) -> TokenStream { .. } = var; + let field_name = rename.as_ref().unwrap_or(field_ident).to_string(); + let export_hint; let registration_fn; @@ -151,14 +151,14 @@ pub fn make_property_impl(class_name: &Ident, fields: &Fields) -> TokenStream { // Note: {getter,setter}_tokens can be either a path `Class_Functions::constant_name` or an empty string `""`. let getter_tokens = make_getter_setter( - getter.to_impl(class_name, GetSet::Get, field), + getter.to_impl(class_name, GetSet::Get, field, &rename), &mut getter_setter_impls, &mut func_name_consts, &mut export_tokens, class_name, ); let setter_tokens = make_getter_setter( - setter.to_impl(class_name, GetSet::Set, field), + setter.to_impl(class_name, GetSet::Set, field, &rename), &mut getter_setter_impls, &mut func_name_consts, &mut export_tokens, diff --git a/godot-macros/src/lib.rs b/godot-macros/src/lib.rs index 0f35ff4d5..3970094f5 100644 --- a/godot-macros/src/lib.rs +++ b/godot-macros/src/lib.rs @@ -206,7 +206,23 @@ use crate::util::{bail, ident, KvParser}; /// } /// ``` /// -/// To create a property without a backing field to store data, you can use [`PhantomVar`](../obj/struct.PhantomVar.html). +/// If you want the field to have a different name in Godot and Rust, you can use `rename`: +/// +/// ``` +/// # use godot::prelude::*; +/// #[derive(GodotClass)] +/// # #[class(init)] +/// struct MyStruct { +/// #[var(rename = my_godot_field)] +/// my_rust_field: i64, +/// } +/// ``` +/// +/// With this, you can access this field as `my_rust_field` in Rust code, however when accessing it from Godot you have to +/// use `my_godot_field` instead (including when using methods such as [`Object::get`](../classes/struct.Object.html#method.get)). +/// The generated getters and setters will also be named `get/set_my_godot_field`, instead of `get/set_my_rust_field`. +/// +/// To create a property without a backing field to store data, you can use [`PhantomVar`](../prelude/struct.PhantomVar.html). /// This disables autogenerated getters and setters for that field. /// /// ## Export properties -- `#[export]` diff --git a/itest/rust/src/object_tests/property_test.rs b/itest/rust/src/object_tests/property_test.rs index 1ef54c87a..e13b18848 100644 --- a/itest/rust/src/object_tests/property_test.rs +++ b/itest/rust/src/object_tests/property_test.rs @@ -53,6 +53,9 @@ struct HasProperty { #[var] packed_int_array: PackedInt32Array, + + #[var(rename = renamed_variable)] + unused_name: GString, } #[godot_api] @@ -147,10 +150,48 @@ impl INode for HasProperty { texture_val: OnEditor::default(), texture_val_rw: None, packed_int_array: PackedInt32Array::new(), + unused_name: GString::new(), } } } +#[itest] +fn test_renamed_var() { + let mut obj = HasProperty::new_alloc(); + + let prop_list = obj.get_property_list(); + assert!(prop_list + .iter_shared() + .any(|d| d.get("name") == Some("renamed_variable".to_variant()))); + assert!(!prop_list + .iter_shared() + .any(|d| d.get("name") == Some("unused_name".to_variant()))); + + assert_eq!(obj.get("renamed_variable"), GString::new().to_variant()); + assert_eq!(obj.get("unused_name"), Variant::nil()); + + let new_value = "variable changed".to_variant(); + obj.set("renamed_variable", &new_value); + obj.set("unused_name", &"something different".to_variant()); + assert_eq!(obj.get("renamed_variable"), new_value); + assert_eq!(obj.get("unused_name"), Variant::nil()); + + obj.free(); +} + +#[itest] +fn test_renamed_var_getter_setter() { + let obj = HasProperty::new_alloc(); + + assert!(obj.has_method("get_renamed_variable")); + assert!(obj.has_method("set_renamed_variable")); + assert!(!obj.has_method("get_unused_name")); + assert!(!obj.has_method("get_unused_name")); + assert_eq!(obj.bind().get_renamed_variable(), GString::new()); + + obj.free(); +} + #[derive(Default, Copy, Clone)] #[repr(i64)] enum SomeCStyleEnum {