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,45 @@ 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 Some ( name) = string_lit_contents ( & name_lit) else {
202
+ return bail ( format ! ( "argument to {key} must be a string literal, got: {name_lit}" ) , parser. span ( ) ) ;
203
+ } ;
204
+ GetterSetter :: Custom ( name)
205
+ }
206
+ Some ( KvValue :: Ident ( ident) ) => {
207
+ return bail (
208
+ format ! ( "argument to {key} must be a string, got: {ident}" ) ,
209
+ parser. span ( ) ,
210
+ ) ;
211
+ }
212
+ } )
213
+ }
214
+ }
215
+
182
216
#[ derive( Clone ) ]
183
217
struct ExportHint {
184
218
hint_type : Ident ,
@@ -196,8 +230,12 @@ impl ExportHint {
196
230
197
231
impl ExportedField {
198
232
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" ) ?;
233
+ let mut getter = GetterSetter :: parse ( parser, "get" ) ?;
234
+ let mut setter = GetterSetter :: parse ( parser, "set" ) ?;
235
+ if getter == GetterSetter :: Omitted && setter == GetterSetter :: Omitted {
236
+ getter = GetterSetter :: Generated ;
237
+ setter = GetterSetter :: Generated ;
238
+ }
201
239
202
240
let hint = parser
203
241
. handle_ident ( "hint" ) ?
@@ -265,51 +303,109 @@ fn make_deref_impl(class_name: &Ident, fields: &Fields) -> TokenStream {
265
303
}
266
304
267
305
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) ,
306
+ let mut getter_setter_impls = Vec :: new ( ) ;
307
+ let mut export_tokens = Vec :: with_capacity ( fields. exported_fields . len ( ) ) ;
308
+
309
+ for exported_field in & fields. exported_fields {
310
+ let field_name = exported_field. field . name . to_string ( ) ;
311
+ let field_ident = ident ( & field_name) ;
312
+ let field_type = exported_field. field . ty . clone ( ) ;
313
+
314
+ let ExportHint {
315
+ hint_type,
316
+ description,
317
+ } = exported_field. hint . clone ( ) . unwrap_or_else ( ExportHint :: none) ;
318
+
319
+ // trims '"' and '\' from both ends of the hint description.
320
+ let description = description. trim_matches ( |c| c == '\\' || c == '"' ) ;
321
+
322
+ let getter_name;
323
+ match & exported_field. getter {
324
+ GetterSetter :: Omitted => {
325
+ getter_name = "" . to_owned ( ) ;
326
+ }
327
+ GetterSetter :: Generated => {
328
+ getter_name = format ! ( "get_{field_name}" ) ;
329
+ let getter_ident = ident ( & getter_name) ;
330
+ let signature = quote ! {
331
+ fn #getter_ident( & self ) -> #field_type
332
+ } ;
333
+ getter_setter_impls. push ( quote ! {
334
+ pub #signature {
335
+ self . #field_ident
336
+ }
337
+ } ) ;
338
+ export_tokens. push ( quote ! {
339
+ :: godot:: private:: gdext_register_method!( #class_name, #signature) ;
340
+ } ) ;
341
+ }
342
+ GetterSetter :: Custom ( name) => {
343
+ getter_name = name. clone ( ) ;
344
+ let getter_ident = ident ( & getter_name) ;
345
+ export_tokens. push ( make_existence_check ( & getter_ident) ) ;
346
+ }
347
+ }
348
+
349
+ let setter_name;
350
+ match & exported_field. setter {
351
+ GetterSetter :: Omitted => {
352
+ setter_name = "" . to_owned ( ) ;
353
+ }
354
+ GetterSetter :: Generated => {
355
+ setter_name = format ! ( "set_{field_name}" ) ;
356
+ let setter_ident = ident ( & setter_name) ;
357
+ let signature = quote ! {
358
+ fn #setter_ident( & mut self , #field_ident: #field_type)
359
+ } ;
360
+ getter_setter_impls. push ( quote ! {
361
+ pub #signature {
362
+ self . #field_ident = #field_ident;
363
+ }
364
+ } ) ;
365
+ export_tokens. push ( quote ! {
366
+ :: godot:: private:: gdext_register_method!( #class_name, #signature) ;
367
+ } ) ;
368
+ }
369
+ GetterSetter :: Custom ( name) => {
370
+ setter_name = name. clone ( ) ;
371
+ let setter_ident = ident ( & setter_name) ;
372
+ export_tokens. push ( make_existence_check ( & setter_ident) ) ;
373
+ }
374
+ } ;
375
+
376
+ export_tokens. push ( quote ! {
377
+ use :: godot:: builtin:: meta:: VariantMetadata ;
378
+
379
+ let class_name = :: godot:: builtin:: StringName :: from( #class_name:: CLASS_NAME ) ;
380
+
381
+ let property_info = :: godot:: builtin:: meta:: PropertyInfo :: new(
382
+ <#field_type>:: variant_type( ) ,
383
+ :: godot:: builtin:: meta:: ClassName :: of:: <#class_name>( ) ,
384
+ :: godot:: builtin:: StringName :: from( #field_name) ,
385
+ :: godot:: engine:: global:: PropertyHint :: #hint_type,
386
+ :: godot:: builtin:: GodotString :: from( #description) ,
387
+ ) ;
388
+ let property_info_sys = property_info. property_sys( ) ;
389
+
390
+ let getter_name = :: godot:: builtin:: StringName :: from( #getter_name) ;
391
+ let setter_name = :: godot:: builtin:: StringName :: from( #setter_name) ;
392
+ unsafe {
393
+ :: godot:: sys:: interface_fn!( classdb_register_extension_class_property) (
394
+ :: godot:: sys:: get_library( ) ,
395
+ class_name. string_sys( ) ,
396
+ std:: ptr:: addr_of!( property_info_sys) ,
397
+ setter_name. string_sys( ) ,
398
+ getter_name. string_sys( ) ,
296
399
) ;
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
400
}
311
401
} ) ;
402
+ }
403
+
312
404
quote ! {
405
+ impl #class_name {
406
+ #( #getter_setter_impls) *
407
+ }
408
+
313
409
impl :: godot:: obj:: cap:: ImplementsGodotExports for #class_name {
314
410
fn __register_exports( ) {
315
411
#(
@@ -321,3 +417,12 @@ fn make_exports_impl(class_name: &Ident, fields: &Fields) -> TokenStream {
321
417
}
322
418
}
323
419
}
420
+
421
+ /// Checks at compile time that a function with the given name exists on `Self`.
422
+ #[ must_use]
423
+ fn make_existence_check ( ident : & Ident ) -> TokenStream {
424
+ quote ! {
425
+ #[ allow( path_statements) ]
426
+ Self :: #ident;
427
+ }
428
+ }
0 commit comments