4
4
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
5
5
*/
6
6
7
- use crate :: util:: { bail, ident, KvParser } ;
7
+ use crate :: util:: { bail, ident, string_lit_contents , KvParser , KvValue } ;
8
8
use crate :: ParseResult ;
9
9
use proc_macro2:: { Ident , Punct , TokenStream } ;
10
10
use quote:: { format_ident, quote} ;
@@ -68,7 +68,7 @@ fn parse_struct_attributes(class: &Struct) -> ParseResult<ClassAttributes> {
68
68
let mut base_ty = ident ( "RefCounted" ) ;
69
69
let mut has_generated_init = false ;
70
70
71
- // #[func ] attribute on struct
71
+ // #[class ] attribute on struct
72
72
if let Some ( mut parser) = KvParser :: parse ( & class. attributes , "class" ) ? {
73
73
if let Some ( base) = parser. handle_ident ( "base" ) ? {
74
74
base_ty = base;
@@ -174,11 +174,46 @@ impl Field {
174
174
175
175
struct ExportedField {
176
176
field : Field ,
177
- getter : String ,
178
- setter : String ,
177
+ getter : GetterSetter ,
178
+ setter : GetterSetter ,
179
179
hint : Option < ExportHint > ,
180
180
}
181
181
182
+ #[ derive( Clone , Debug , Eq , PartialEq ) ]
183
+ enum GetterSetter {
184
+ /// Getter/setter should be omitted, field is write/read only.
185
+ Omitted ,
186
+ /// Trivial getter/setter should be autogenerated.
187
+ Generated ,
188
+ /// Getter/setter is hand-written by the user, and here is its name.
189
+ Custom ( String ) ,
190
+ }
191
+
192
+ impl GetterSetter {
193
+ fn parse ( parser : & mut KvParser , key : & str ) -> ParseResult < Self > {
194
+ Ok ( match parser. handle_any ( key) {
195
+ // No `get` argument
196
+ None => GetterSetter :: Omitted ,
197
+ // `get` without value
198
+ Some ( KvValue :: None ) => GetterSetter :: Generated ,
199
+ // `get = literal`
200
+ Some ( KvValue :: Lit ( name_lit) ) => {
201
+ let name = string_lit_contents ( & name_lit) . ok_or_else ( || {
202
+ venial:: Error :: new ( format ! (
203
+ "argument to {key} must be a string literal, got: {name_lit}"
204
+ ) )
205
+ } ) ?;
206
+ GetterSetter :: Custom ( name)
207
+ }
208
+ Some ( KvValue :: Ident ( ident) ) => {
209
+ return Err ( venial:: Error :: new ( format ! (
210
+ "argument to {key} must be a string, got: {ident}"
211
+ ) ) ) ;
212
+ }
213
+ } )
214
+ }
215
+ }
216
+
182
217
#[ derive( Clone ) ]
183
218
struct ExportHint {
184
219
hint_type : Ident ,
@@ -196,8 +231,12 @@ impl ExportHint {
196
231
197
232
impl ExportedField {
198
233
pub fn new_from_kv ( field : Field , parser : & mut KvParser ) -> ParseResult < ExportedField > {
199
- let getter = parser. handle_lit_required ( "getter" ) ?;
200
- let setter = parser. handle_lit_required ( "setter" ) ?;
234
+ let mut getter = GetterSetter :: parse ( parser, "get" ) ?;
235
+ let mut setter = GetterSetter :: parse ( parser, "set" ) ?;
236
+ if getter == GetterSetter :: Omitted && setter == GetterSetter :: Omitted {
237
+ getter = GetterSetter :: Generated ;
238
+ setter = GetterSetter :: Generated ;
239
+ }
201
240
202
241
let hint = parser
203
242
. handle_ident ( "hint" ) ?
@@ -265,51 +304,109 @@ fn make_deref_impl(class_name: &Ident, fields: &Fields) -> TokenStream {
265
304
}
266
305
267
306
fn make_exports_impl ( class_name : & Ident , fields : & Fields ) -> TokenStream {
268
- let export_tokens = fields
269
- . exported_fields
270
- . iter ( )
271
- . map ( |exported_field : & ExportedField | {
272
- use std:: str:: FromStr ;
273
- let name = exported_field. field . name . to_string ( ) ;
274
- let getter = proc_macro2:: Literal :: from_str ( & exported_field. getter ) . unwrap ( ) ;
275
- let setter = proc_macro2:: Literal :: from_str ( & exported_field. setter ) . unwrap ( ) ;
276
- let field_type = exported_field. field . ty . clone ( ) ;
277
-
278
- let ExportHint {
279
- hint_type,
280
- description,
281
- } = exported_field. hint . clone ( ) . unwrap_or_else ( ExportHint :: none) ;
282
-
283
- // trims '"' and '\' from both ends of the hint description.
284
- let description = description. trim_matches ( |c| c == '\\' || c == '"' ) ;
285
-
286
- quote ! {
287
- use :: godot:: builtin:: meta:: VariantMetadata ;
288
-
289
- let class_name = :: godot:: builtin:: StringName :: from( #class_name:: CLASS_NAME ) ;
290
- let property_info = :: godot:: builtin:: meta:: PropertyInfo :: new(
291
- <#field_type>:: variant_type( ) ,
292
- :: godot:: builtin:: meta:: ClassName :: of:: <#class_name>( ) ,
293
- :: godot:: builtin:: StringName :: from( #name) ,
294
- :: godot:: engine:: global:: PropertyHint :: #hint_type,
295
- GodotString :: from( #description) ,
307
+ let mut getter_setter_impls = Vec :: new ( ) ;
308
+ let mut export_tokens = Vec :: with_capacity ( fields. exported_fields . len ( ) ) ;
309
+
310
+ for exported_field in & fields. exported_fields {
311
+ let field_name = exported_field. field . name . to_string ( ) ;
312
+ let field_ident = ident ( & field_name) ;
313
+ let field_type = exported_field. field . ty . clone ( ) ;
314
+
315
+ let ExportHint {
316
+ hint_type,
317
+ description,
318
+ } = exported_field. hint . clone ( ) . unwrap_or_else ( ExportHint :: none) ;
319
+
320
+ // trims '"' and '\' from both ends of the hint description.
321
+ let description = description. trim_matches ( |c| c == '\\' || c == '"' ) ;
322
+
323
+ let getter_name;
324
+ match & exported_field. getter {
325
+ GetterSetter :: Omitted => {
326
+ getter_name = "" . to_owned ( ) ;
327
+ }
328
+ GetterSetter :: Generated => {
329
+ getter_name = format ! ( "get_{field_name}" ) ;
330
+ let getter_ident = ident ( & getter_name) ;
331
+ let signature = quote ! {
332
+ fn #getter_ident( & self ) -> #field_type
333
+ } ;
334
+ getter_setter_impls. push ( quote ! {
335
+ pub #signature {
336
+ self . #field_ident
337
+ }
338
+ } ) ;
339
+ export_tokens. push ( quote ! {
340
+ :: godot:: private:: gdext_register_method!( #class_name, #signature) ;
341
+ } ) ;
342
+ }
343
+ GetterSetter :: Custom ( name) => {
344
+ getter_name = name. clone ( ) ;
345
+ let getter_ident = ident ( & getter_name) ;
346
+ export_tokens. push ( existence_check ( & getter_ident) ) ;
347
+ }
348
+ }
349
+
350
+ let setter_name;
351
+ match & exported_field. setter {
352
+ GetterSetter :: Omitted => {
353
+ setter_name = "" . to_owned ( ) ;
354
+ }
355
+ GetterSetter :: Generated => {
356
+ setter_name = format ! ( "set_{field_name}" ) ;
357
+ let setter_ident = ident ( & setter_name) ;
358
+ let signature = quote ! {
359
+ fn #setter_ident( & mut self , #field_ident: #field_type)
360
+ } ;
361
+ getter_setter_impls. push ( quote ! {
362
+ pub #signature {
363
+ self . #field_ident = #field_ident;
364
+ }
365
+ } ) ;
366
+ export_tokens. push ( quote ! {
367
+ :: godot:: private:: gdext_register_method!( #class_name, #signature) ;
368
+ } ) ;
369
+ }
370
+ GetterSetter :: Custom ( name) => {
371
+ setter_name = name. clone ( ) ;
372
+ let setter_ident = ident ( & setter_name) ;
373
+ export_tokens. push ( existence_check ( & setter_ident) ) ;
374
+ }
375
+ } ;
376
+
377
+ export_tokens. push ( quote ! {
378
+ use :: godot:: builtin:: meta:: VariantMetadata ;
379
+
380
+ let class_name = :: godot:: builtin:: StringName :: from( #class_name:: CLASS_NAME ) ;
381
+
382
+ let property_info = :: godot:: builtin:: meta:: PropertyInfo :: new(
383
+ <#field_type>:: variant_type( ) ,
384
+ :: godot:: builtin:: meta:: ClassName :: of:: <#class_name>( ) ,
385
+ :: godot:: builtin:: StringName :: from( #field_name) ,
386
+ :: godot:: engine:: global:: PropertyHint :: #hint_type,
387
+ GodotString :: from( #description) ,
388
+ ) ;
389
+ let property_info_sys = property_info. property_sys( ) ;
390
+
391
+ let getter_name = :: godot:: builtin:: StringName :: from( #getter_name) ;
392
+ let setter_name = :: godot:: builtin:: StringName :: from( #setter_name) ;
393
+ unsafe {
394
+ :: godot:: sys:: interface_fn!( classdb_register_extension_class_property) (
395
+ :: godot:: sys:: get_library( ) ,
396
+ class_name. string_sys( ) ,
397
+ std:: ptr:: addr_of!( property_info_sys) ,
398
+ setter_name. string_sys( ) ,
399
+ getter_name. string_sys( ) ,
296
400
) ;
297
- let property_info_sys = property_info. property_sys( ) ;
298
-
299
- let getter_string_name = :: godot:: builtin:: StringName :: from( #getter) ;
300
- let setter_string_name = :: godot:: builtin:: StringName :: from( #setter) ;
301
- unsafe {
302
- :: godot:: sys:: interface_fn!( classdb_register_extension_class_property) (
303
- :: godot:: sys:: get_library( ) ,
304
- class_name. string_sys( ) ,
305
- std:: ptr:: addr_of!( property_info_sys) ,
306
- setter_string_name. string_sys( ) ,
307
- getter_string_name. string_sys( ) ,
308
- ) ;
309
- }
310
401
}
311
402
} ) ;
403
+ }
404
+
312
405
quote ! {
406
+ impl #class_name {
407
+ #( #getter_setter_impls) *
408
+ }
409
+
313
410
impl :: godot:: obj:: cap:: ImplementsGodotExports for #class_name {
314
411
fn __register_exports( ) {
315
412
#(
@@ -321,3 +418,32 @@ fn make_exports_impl(class_name: &Ident, fields: &Fields) -> TokenStream {
321
418
}
322
419
}
323
420
}
421
+
422
+ /// Checks at compile time that a function with the given name exists on `Self`.
423
+ #[ must_use]
424
+ fn existence_check ( ident : & Ident ) -> TokenStream {
425
+ quote ! {
426
+ #[ allow( path_statements) ]
427
+ Self :: #ident;
428
+ }
429
+ }
430
+
431
+ /// Tests that the proc-macro doesn't panic. This is helpful because `RUST_BACKTRACE=1` does not
432
+ /// affect panics in macro invocations (e.g. in our integration tests), but it works fine in unit
433
+ /// tests.
434
+ #[ test]
435
+ fn does_not_panic ( ) {
436
+ let decl = venial:: parse_declaration ( quote ! {
437
+ #[ class( base=Node ) ]
438
+ struct HasProperty {
439
+ #[ base]
440
+ base: Base <Node >,
441
+ #[ export]
442
+ int_val_default: i32 ,
443
+ #[ export( get = "get_int_val" , set = "set_int_val" ) ]
444
+ int_val: i32 ,
445
+ }
446
+ } )
447
+ . unwrap ( ) ;
448
+ transform ( decl) . unwrap ( ) ;
449
+ }
0 commit comments