@@ -61,78 +61,112 @@ pub(crate) fn derive_native_class(derive_input: &DeriveInput) -> Result<TokenStr
6161 . register_callback
6262 . map ( |function_path| quote ! ( #function_path( builder) ; ) )
6363 . 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
9076 } ;
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 ,
9695 } ;
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 )
116114 } ;
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( ) ;
122167 } ) )
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( ) ;
134168 } )
135- } ) ;
169+ . collect :: < Result < Vec < _ > , _ > > ( ) ? ;
136170
137171 let maybe_statically_named = data. godot_name . map ( |name_str| {
138172 quote ! {
@@ -407,4 +441,23 @@ mod tests {
407441 let input: DeriveInput = syn:: parse2 ( input) . unwrap ( ) ;
408442 parse_derive_input ( & input) . unwrap ( ) ;
409443 }
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+ }
410463}
0 commit comments