@@ -61,78 +61,112 @@ pub(crate) fn derive_native_class(derive_input: &DeriveInput) -> Result<TokenStr
61
61
. register_callback
62
62
. map ( |function_path| quote ! ( #function_path( builder) ; ) )
63
63
. unwrap_or ( quote ! ( { } ) ) ;
64
- let properties = data. properties . into_iter ( ) . map ( |( ident, config) | {
65
- let with_default = config
66
- . default
67
- . map ( |default_value| quote ! ( . with_default( #default_value) ) ) ;
68
- let with_hint = config. hint . map ( |hint_fn| quote ! ( . with_hint( #hint_fn( ) ) ) ) ;
69
- let with_usage = if config. no_editor {
70
- Some ( quote ! ( . with_usage( :: gdnative:: export:: PropertyUsage :: NOEDITOR ) ) )
71
- } else {
72
- None
73
- } ;
74
- // if both of them are not set, i.e. `#[property]`. implicitly use both getter/setter
75
- let ( get, set) = if config. get . is_none ( ) && config. set . is_none ( ) {
76
- ( Some ( PropertyGet :: Default ) , Some ( PropertySet :: Default ) )
77
- } else {
78
- ( config. get , config. set )
79
- } ;
80
- let before_get: Option < Stmt > = config
81
- . before_get
82
- . map ( |path_expr| parse_quote ! ( #path_expr( this, _owner) ; ) ) ;
83
- let after_get: Option < Stmt > = config
84
- . after_get
85
- . map ( |path_expr| parse_quote ! ( #path_expr( this, _owner) ; ) ) ;
86
- let with_getter = get. map ( |get| {
87
- let register_fn = match get {
88
- PropertyGet :: Owned ( _) => quote ! ( with_getter) ,
89
- _ => quote ! ( with_ref_getter) ,
64
+ let properties = data
65
+ . properties
66
+ . into_iter ( )
67
+ . map ( |( ident, config) | {
68
+ let with_default = config
69
+ . default
70
+ . map ( |default_value| quote ! ( . with_default( #default_value) ) ) ;
71
+ let with_hint = config. hint . map ( |hint_fn| quote ! ( . with_hint( #hint_fn( ) ) ) ) ;
72
+ let with_usage = if config. no_editor {
73
+ Some ( quote ! ( . with_usage( :: gdnative:: export:: PropertyUsage :: NOEDITOR ) ) )
74
+ } else {
75
+ None
90
76
} ;
91
- let get: Expr = match get {
92
- PropertyGet :: Default => parse_quote ! ( & this. #ident) ,
93
- PropertyGet :: Owned ( path_expr) | PropertyGet :: Ref ( path_expr) => {
94
- parse_quote ! ( #path_expr( this, _owner) )
95
- }
77
+ // check whether this property type is `Property<T>`. if so, extract T from it.
78
+ let property_ty = match config. ty {
79
+ Type :: Path ( ref path) => path
80
+ . path
81
+ . segments
82
+ . iter ( )
83
+ . last ( )
84
+ . filter ( |seg| seg. ident == "Property" )
85
+ . and_then ( |seg| match seg. arguments {
86
+ syn:: PathArguments :: AngleBracketed ( ref params) => params. args . first ( ) ,
87
+ _ => None ,
88
+ } )
89
+ . and_then ( |arg| match arg {
90
+ syn:: GenericArgument :: Type ( ref ty) => Some ( ty) ,
91
+ _ => None ,
92
+ } )
93
+ . map ( |ty| quote ! ( :: <#ty>) ) ,
94
+ _ => None ,
96
95
} ;
97
- quote ! (
98
- . #register_fn( |this: & #name, _owner: :: gdnative:: object:: TRef <Self :: Base >| {
99
- #before_get
100
- let res = #get;
101
- #after_get
102
- res
103
- } )
104
- )
105
- } ) ;
106
- let before_set: Option < Stmt > = config
107
- . before_set
108
- . map ( |path_expr| parse_quote ! ( #path_expr( this, _owner) ; ) ) ;
109
- let after_set: Option < Stmt > = config
110
- . after_set
111
- . map ( |path_expr| parse_quote ! ( #path_expr( this, _owner) ; ) ) ;
112
- let with_setter = set. map ( |set| {
113
- let set: Stmt = match set {
114
- PropertySet :: Default => parse_quote ! ( this. #ident = v; ) ,
115
- PropertySet :: WithPath ( path_expr) => parse_quote ! ( #path_expr( this, _owner, v) ; ) ,
96
+ // #[property] is not attached on `Property<T>`
97
+ if property_ty. is_none ( )
98
+ // custom getter used
99
+ && config. get . as_ref ( ) . map ( |get| !matches ! ( get, PropertyGet :: Default ) ) . unwrap_or ( false )
100
+ // custom setter used
101
+ && config. set . as_ref ( ) . map ( |set| !matches ! ( set, PropertySet :: Default ) ) . unwrap_or ( false )
102
+ {
103
+ return Err ( syn:: Error :: new (
104
+ ident. span ( ) ,
105
+ "The `#[property]` attribute can only be used on a field of type `Property`, \
106
+ if a path is provided for both get/set method(s)."
107
+ ) ) ;
108
+ }
109
+ // if both of them are not set, i.e. `#[property]`. implicitly use both getter/setter
110
+ let ( get, set) = if config. get . is_none ( ) && config. set . is_none ( ) {
111
+ ( Some ( PropertyGet :: Default ) , Some ( PropertySet :: Default ) )
112
+ } else {
113
+ ( config. get , config. set )
116
114
} ;
117
- quote ! (
118
- . with_setter( |this: & mut #name, _owner: :: gdnative:: object:: TRef <Self :: Base >, v| {
119
- #before_set
120
- #set
121
- #after_set
115
+ let before_get: Option < Stmt > = config
116
+ . before_get
117
+ . map ( |path_expr| parse_quote ! ( #path_expr( this, _owner) ; ) ) ;
118
+ let after_get: Option < Stmt > = config
119
+ . after_get
120
+ . map ( |path_expr| parse_quote ! ( #path_expr( this, _owner) ; ) ) ;
121
+ let with_getter = get. map ( |get| {
122
+ let register_fn = match get {
123
+ PropertyGet :: Owned ( _) => quote ! ( with_getter) ,
124
+ _ => quote ! ( with_ref_getter) ,
125
+ } ;
126
+ let get: Expr = match get {
127
+ PropertyGet :: Default => parse_quote ! ( & this. #ident) ,
128
+ PropertyGet :: Owned ( path_expr) | PropertyGet :: Ref ( path_expr) => parse_quote ! ( #path_expr( this, _owner) )
129
+ } ;
130
+ quote ! (
131
+ . #register_fn( |this: & #name, _owner: :: gdnative:: object:: TRef <Self :: Base >| {
132
+ #before_get
133
+ let res = #get;
134
+ #after_get
135
+ res
136
+ } )
137
+ )
138
+ } ) ;
139
+ let before_set: Option < Stmt > = config
140
+ . before_set
141
+ . map ( |path_expr| parse_quote ! ( #path_expr( this, _owner) ; ) ) ;
142
+ let after_set: Option < Stmt > = config
143
+ . after_set
144
+ . map ( |path_expr| parse_quote ! ( #path_expr( this, _owner) ; ) ) ;
145
+ let with_setter = set. map ( |set| {
146
+ let set: Stmt = match set {
147
+ PropertySet :: Default => parse_quote ! ( this. #ident = v; ) ,
148
+ PropertySet :: WithPath ( path_expr) => parse_quote ! ( #path_expr( this, _owner, v) ; ) ,
149
+ } ;
150
+ quote ! (
151
+ . with_setter( |this: & mut #name, _owner: :: gdnative:: object:: TRef <Self :: Base >, v| {
152
+ #before_set
153
+ #set
154
+ #after_set
155
+ } ) )
156
+ } ) ;
157
+
158
+ let label = config. path . unwrap_or_else ( || format ! ( "{}" , ident) ) ;
159
+ Ok ( quote ! ( {
160
+ builder. property#property_ty( #label)
161
+ #with_default
162
+ #with_hint
163
+ #with_usage
164
+ #with_getter
165
+ #with_setter
166
+ . done( ) ;
122
167
} ) )
123
- } ) ;
124
-
125
- let label = config. path . unwrap_or_else ( || format ! ( "{}" , ident) ) ;
126
- quote ! ( {
127
- builder. property( #label)
128
- #with_default
129
- #with_hint
130
- #with_usage
131
- #with_getter
132
- #with_setter
133
- . done( ) ;
134
168
} )
135
- } ) ;
169
+ . collect :: < Result < Vec < _ > , _ > > ( ) ? ;
136
170
137
171
let maybe_statically_named = data. godot_name . map ( |name_str| {
138
172
quote ! {
@@ -407,4 +441,23 @@ mod tests {
407
441
let input: DeriveInput = syn:: parse2 ( input) . unwrap ( ) ;
408
442
parse_derive_input ( & input) . unwrap ( ) ;
409
443
}
444
+
445
+ #[ test]
446
+ fn derive_property_require_to_be_used_on_property_without_default_accessor ( ) {
447
+ let input: TokenStream2 = syn:: parse_str (
448
+ r#"
449
+ #[inherit(Node)]
450
+ struct Foo {
451
+ #[property(get = "Self::get_bar", set = "Self::set_bar")]
452
+ bar: i64,
453
+ }"# ,
454
+ )
455
+ . unwrap ( ) ;
456
+ let input: DeriveInput = syn:: parse2 ( input) . unwrap ( ) ;
457
+ assert_eq ! (
458
+ derive_native_class( & input) . unwrap_err( ) . to_string( ) ,
459
+ "The `#[property]` attribute can only be used on a field of type `Property`, \
460
+ if a path is provided for both get/set method(s).",
461
+ ) ;
462
+ }
410
463
}
0 commit comments