@@ -381,6 +381,24 @@ where
381
381
382
382
// ----------------------------------------------------------------------------------------------------------------------------------------------
383
383
384
+ /// Expects either Some(quote! { () => A, () => B, ... }) or None as the 'tokens' parameter.
385
+ /// The idea is that the () => ... arms can be annotated by cfg attrs, so, if any of them compiles (and assuming the cfg
386
+ /// attrs only allow one arm to 'survive' compilation), their return value (Some(...)) will be prioritized over the
387
+ /// 'None' from the catch-all arm at the end. If, however, none of them compile, then None is returned from the last
388
+ /// match arm.
389
+ fn convert_to_match_expression_or_none ( tokens : Option < TokenStream > ) -> TokenStream {
390
+ if let Some ( tokens) = tokens {
391
+ quote ! {
392
+ match ( ) {
393
+ #tokens
394
+ _ => None ,
395
+ }
396
+ }
397
+ } else {
398
+ quote ! { None }
399
+ }
400
+ }
401
+
384
402
/// Codegen for `#[godot_api] impl GodotExt for MyType`
385
403
fn transform_trait_impl ( original_impl : Impl ) -> Result < TokenStream , Error > {
386
404
let ( class_name, trait_name) = util:: validate_trait_impl_virtual ( & original_impl, "godot_api" ) ?;
@@ -391,13 +409,14 @@ fn transform_trait_impl(original_impl: Impl) -> Result<TokenStream, Error> {
391
409
let mut register_class_impl = TokenStream :: new ( ) ;
392
410
let mut on_notification_impl = TokenStream :: new ( ) ;
393
411
394
- let mut register_fn = quote ! { None } ;
395
- let mut create_fn = quote ! { None } ;
396
- let mut recreate_fn = quote ! { None } ;
397
- let mut to_string_fn = quote ! { None } ;
398
- let mut on_notification_fn = quote ! { None } ;
412
+ let mut register_fn = None ;
413
+ let mut create_fn = None ;
414
+ let mut recreate_fn = None ;
415
+ let mut to_string_fn = None ;
416
+ let mut on_notification_fn = None ;
399
417
400
418
let mut virtual_methods = vec ! [ ] ;
419
+ let mut virtual_method_cfg_attrs = vec ! [ ] ;
401
420
let mut virtual_method_names = vec ! [ ] ;
402
421
403
422
let prv = quote ! { :: godot:: private } ;
@@ -409,52 +428,99 @@ fn transform_trait_impl(original_impl: Impl) -> Result<TokenStream, Error> {
409
428
continue ;
410
429
} ;
411
430
431
+ // Transport #[cfg] attributes to the virtual method's FFI glue, to ensure it won't be
432
+ // registered in Godot if conditionally removed from compilation.
433
+ let cfg_attrs = util:: extract_cfg_attrs ( & method. attributes )
434
+ . into_iter ( )
435
+ . collect :: < Vec < _ > > ( ) ;
412
436
let method_name = method. name . to_string ( ) ;
413
437
match method_name. as_str ( ) {
414
438
"register_class" => {
439
+ // Implements the trait once for each implementation of this method, forwarding the cfg attrs of each
440
+ // implementation to the generated trait impl. If the cfg attrs allow for multiple implementations of
441
+ // this method to exist, then Rust will generate an error, so we don't have to worry about the multiple
442
+ // trait implementations actually generating an error, since that can only happen if multiple
443
+ // implementations of the same method are kept by #[cfg] (due to user error).
444
+ // Thus, by implementing the trait once for each possible implementation of this method (depending on
445
+ // what #[cfg] allows), forwarding the cfg attrs, we ensure this trait impl will remain in the code if
446
+ // at least one of the method impls are kept.
415
447
register_class_impl = quote ! {
448
+ #register_class_impl
449
+
450
+ #( #cfg_attrs) *
416
451
impl :: godot:: obj:: cap:: GodotRegisterClass for #class_name {
417
452
fn __godot_register_class( builder: & mut :: godot:: builder:: GodotBuilder <Self >) {
418
453
<Self as #trait_name>:: register_class( builder)
419
454
}
420
455
}
421
456
} ;
422
457
423
- register_fn = quote ! {
424
- Some ( #prv:: ErasedRegisterFn {
458
+ // Adds a match arm for each implementation of this method, transferring its respective cfg attrs to
459
+ // the corresponding match arm (see explanation for the match after this loop).
460
+ // In principle, the cfg attrs will allow only either 0 or 1 of a function with this name to exist,
461
+ // unless there are duplicate implementations for the same method, which should error anyway.
462
+ // Thus, in any correct program, the match arms (which are, in principle, identical) will be reduced to
463
+ // a single one at most, since we forward the cfg attrs. The idea here is precisely to keep this
464
+ // specific match arm 'alive' if at least one implementation of the method is also kept (hence why all
465
+ // the match arms are identical).
466
+ register_fn = Some ( quote ! {
467
+ #register_fn
468
+ #( #cfg_attrs) *
469
+ ( ) => Some ( #prv:: ErasedRegisterFn {
425
470
raw: #prv:: callbacks:: register_class_by_builder:: <#class_name>
426
- } )
427
- } ;
471
+ } ) ,
472
+ } ) ;
428
473
}
429
474
430
475
"init" => {
431
476
godot_init_impl = quote ! {
477
+ #godot_init_impl
478
+
479
+ #( #cfg_attrs) *
432
480
impl :: godot:: obj:: cap:: GodotInit for #class_name {
433
481
fn __godot_init( base: :: godot:: obj:: Base <Self :: Base >) -> Self {
434
482
<Self as #trait_name>:: init( base)
435
483
}
436
484
}
437
485
} ;
438
- create_fn = quote ! { Some ( #prv:: callbacks:: create:: <#class_name>) } ;
486
+ create_fn = Some ( quote ! {
487
+ #create_fn
488
+ #( #cfg_attrs) *
489
+ ( ) => Some ( #prv:: callbacks:: create:: <#class_name>) ,
490
+ } ) ;
439
491
if cfg ! ( since_api = "4.2" ) {
440
- recreate_fn = quote ! { Some ( #prv:: callbacks:: recreate:: <#class_name>) } ;
492
+ recreate_fn = Some ( quote ! {
493
+ #recreate_fn
494
+ #( #cfg_attrs) *
495
+ ( ) => Some ( #prv:: callbacks:: recreate:: <#class_name>) ,
496
+ } ) ;
441
497
}
442
498
}
443
499
444
500
"to_string" => {
445
501
to_string_impl = quote ! {
502
+ #to_string_impl
503
+
504
+ #( #cfg_attrs) *
446
505
impl :: godot:: obj:: cap:: GodotToString for #class_name {
447
506
fn __godot_to_string( & self ) -> :: godot:: builtin:: GodotString {
448
507
<Self as #trait_name>:: to_string( self )
449
508
}
450
509
}
451
510
} ;
452
511
453
- to_string_fn = quote ! { Some ( #prv:: callbacks:: to_string:: <#class_name>) } ;
512
+ to_string_fn = Some ( quote ! {
513
+ #to_string_fn
514
+ #( #cfg_attrs) *
515
+ ( ) => Some ( #prv:: callbacks:: to_string:: <#class_name>) ,
516
+ } ) ;
454
517
}
455
518
456
519
"on_notification" => {
457
520
on_notification_impl = quote ! {
521
+ #on_notification_impl
522
+
523
+ #( #cfg_attrs) *
458
524
impl :: godot:: obj:: cap:: GodotNotification for #class_name {
459
525
fn __godot_notification( & mut self , what: i32 ) {
460
526
if :: godot:: private:: is_class_inactive( Self :: __config( ) . is_tool) {
@@ -466,9 +532,11 @@ fn transform_trait_impl(original_impl: Impl) -> Result<TokenStream, Error> {
466
532
}
467
533
} ;
468
534
469
- on_notification_fn = quote ! {
470
- Some ( #prv:: callbacks:: on_notification:: <#class_name>)
471
- } ;
535
+ on_notification_fn = Some ( quote ! {
536
+ #on_notification_fn
537
+ #( #cfg_attrs) *
538
+ ( ) => Some ( #prv:: callbacks:: on_notification:: <#class_name>) ,
539
+ } ) ;
472
540
}
473
541
474
542
// Other virtual methods, like ready, process etc.
@@ -487,6 +555,11 @@ fn transform_trait_impl(original_impl: Impl) -> Result<TokenStream, Error> {
487
555
} else {
488
556
format ! ( "_{method_name}" )
489
557
} ;
558
+ // Note that, if the same method is implemented multiple times (with different cfg attr combinations),
559
+ // then there will be multiple match arms annotated with the same cfg attr combinations, thus they will
560
+ // be reduced to just one arm (at most, if the implementations aren't all removed from compilation) for
561
+ // each distinct method.
562
+ virtual_method_cfg_attrs. push ( cfg_attrs) ;
490
563
virtual_method_names. push ( virtual_method_name) ;
491
564
virtual_methods. push ( method) ;
492
565
}
@@ -498,6 +571,17 @@ fn transform_trait_impl(original_impl: Impl) -> Result<TokenStream, Error> {
498
571
. map ( |method| make_virtual_method_callback ( & class_name, method) )
499
572
. collect ( ) ;
500
573
574
+ // Use 'match' as a way to only emit 'Some(...)' if the given cfg attrs allow.
575
+ // This permits users to conditionally remove virtual method impls from compilation while also removing their FFI
576
+ // glue which would otherwise make them visible to Godot even if not really implemented.
577
+ // Needs '#[allow(unreachable_patterns)]' to avoid warnings about the last match arm.
578
+ // Also requires '#[allow(clippy::match_single_binding)]' for similar reasons.
579
+ let register_fn = convert_to_match_expression_or_none ( register_fn) ;
580
+ let create_fn = convert_to_match_expression_or_none ( create_fn) ;
581
+ let recreate_fn = convert_to_match_expression_or_none ( recreate_fn) ;
582
+ let to_string_fn = convert_to_match_expression_or_none ( to_string_fn) ;
583
+ let on_notification_fn = convert_to_match_expression_or_none ( on_notification_fn) ;
584
+
501
585
let result = quote ! {
502
586
#original_impl
503
587
#godot_init_impl
@@ -517,6 +601,7 @@ fn transform_trait_impl(original_impl: Impl) -> Result<TokenStream, Error> {
517
601
518
602
match name {
519
603
#(
604
+ #( #virtual_method_cfg_attrs) *
520
605
#virtual_method_names => #virtual_method_callbacks,
521
606
) *
522
607
_ => None ,
@@ -526,6 +611,8 @@ fn transform_trait_impl(original_impl: Impl) -> Result<TokenStream, Error> {
526
611
527
612
:: godot:: sys:: plugin_add!( __GODOT_PLUGIN_REGISTRY in #prv; #prv:: ClassPlugin {
528
613
class_name: #class_name_obj,
614
+ #[ allow( unreachable_patterns) ] // due to the cfg-based match statements
615
+ #[ allow( clippy:: match_single_binding) ] // avoid warning on single-arm matches
529
616
component: #prv:: PluginComponent :: UserVirtuals {
530
617
user_register_fn: #register_fn,
531
618
user_create_fn: #create_fn,
0 commit comments