Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 17 additions & 3 deletions godot-macros/src/class/data_models/field_var.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Ident>,
pub getter: GetterSetter,
pub setter: GetterSetter,
pub hint: FieldHint,
Expand All @@ -29,13 +31,15 @@ impl FieldVar {
/// Parse a `#[var]` attribute to a `FieldVar` struct.
///
/// Possible keys:
/// - `rename = ident`
/// - `get = expr`
/// - `set = expr`
/// - `hint = ident`
/// - `hint_string = expr`
/// - `usage_flags =
pub(crate) fn new_from_kv(parser: &mut KvParser) -> ParseResult<Self> {
let span = parser.span();
let rename = parser.handle_ident("rename")?;
let getter = GetterSetter::parse(parser, "get")?;
let setter = GetterSetter::parse(parser, "set")?;

Expand Down Expand Up @@ -64,6 +68,7 @@ impl FieldVar {
};

Ok(FieldVar {
rename,
getter,
setter,
hint,
Expand All @@ -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(),
Expand Down Expand Up @@ -131,11 +137,12 @@ impl GetterSetter {
class_name: &Ident,
kind: GetSet,
field: &Field,
rename: &Option<Ident>,
) -> Option<GetterSetterImpl> {
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))
Expand Down Expand Up @@ -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<Ident>,
) -> 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;
Expand Down
10 changes: 5 additions & 5 deletions godot-macros/src/class/data_models/property.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,17 +76,17 @@ 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,
mut usage_flags,
..
} = var;

let field_name = rename.as_ref().unwrap_or(field_ident).to_string();

let export_hint;
let registration_fn;

Expand Down Expand Up @@ -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,
Expand Down
18 changes: 17 additions & 1 deletion godot-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]`
Expand Down
41 changes: 41 additions & 0 deletions itest/rust/src/object_tests/property_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ struct HasProperty {

#[var]
packed_int_array: PackedInt32Array,

#[var(rename = renamed_variable)]
unused_name: GString,
}

#[godot_api]
Expand Down Expand Up @@ -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 {
Expand Down
Loading