@@ -424,6 +424,30 @@ where
424
424
425
425
// ----------------------------------------------------------------------------------------------------------------------------------------------
426
426
427
+ /// Expects either Some(quote! { () => A, () => B, ... }) or None as the 'tokens' parameter.
428
+ /// The idea is that the () => ... arms can be annotated by cfg attrs, so, if any of them compiles (and assuming the cfg
429
+ /// attrs only allow one arm to 'survive' compilation), their return value (Some(...)) will be prioritized over the
430
+ /// 'None' from the catch-all arm at the end. If, however, none of them compile, then None is returned from the last
431
+ /// match arm.
432
+ fn convert_to_match_expression_or_none ( tokens : Option < TokenStream > ) -> TokenStream {
433
+ if let Some ( tokens) = tokens {
434
+ quote ! {
435
+ {
436
+ // When one of the () => ... arms is present, the last arm intentionally won't ever match.
437
+ #[ allow( unreachable_patterns) ]
438
+ // Don't warn when only _ => None is present as all () => ... arms were removed from compilation.
439
+ #[ allow( clippy:: match_single_binding) ]
440
+ match ( ) {
441
+ #tokens
442
+ _ => None ,
443
+ }
444
+ }
445
+ }
446
+ } else {
447
+ quote ! { None }
448
+ }
449
+ }
450
+
427
451
/// Codegen for `#[godot_api] impl GodotExt for MyType`
428
452
fn transform_trait_impl ( original_impl : Impl ) -> Result < TokenStream , Error > {
429
453
let ( class_name, trait_name) = util:: validate_trait_impl_virtual ( & original_impl, "godot_api" ) ?;
@@ -434,13 +458,14 @@ fn transform_trait_impl(original_impl: Impl) -> Result<TokenStream, Error> {
434
458
let mut register_class_impl = TokenStream :: new ( ) ;
435
459
let mut on_notification_impl = TokenStream :: new ( ) ;
436
460
437
- let mut register_fn = quote ! { None } ;
438
- let mut create_fn = quote ! { None } ;
439
- let mut recreate_fn = quote ! { None } ;
440
- let mut to_string_fn = quote ! { None } ;
441
- let mut on_notification_fn = quote ! { None } ;
461
+ let mut register_fn = None ;
462
+ let mut create_fn = None ;
463
+ let mut recreate_fn = None ;
464
+ let mut to_string_fn = None ;
465
+ let mut on_notification_fn = None ;
442
466
443
467
let mut virtual_methods = vec ! [ ] ;
468
+ let mut virtual_method_cfg_attrs = vec ! [ ] ;
444
469
let mut virtual_method_names = vec ! [ ] ;
445
470
446
471
let prv = quote ! { :: godot:: private } ;
@@ -452,52 +477,99 @@ fn transform_trait_impl(original_impl: Impl) -> Result<TokenStream, Error> {
452
477
continue ;
453
478
} ;
454
479
480
+ // Transport #[cfg] attributes to the virtual method's FFI glue, to ensure it won't be
481
+ // registered in Godot if conditionally removed from compilation.
482
+ let cfg_attrs = util:: extract_cfg_attrs ( & method. attributes )
483
+ . into_iter ( )
484
+ . collect :: < Vec < _ > > ( ) ;
455
485
let method_name = method. name . to_string ( ) ;
456
486
match method_name. as_str ( ) {
457
487
"register_class" => {
488
+ // Implements the trait once for each implementation of this method, forwarding the cfg attrs of each
489
+ // implementation to the generated trait impl. If the cfg attrs allow for multiple implementations of
490
+ // this method to exist, then Rust will generate an error, so we don't have to worry about the multiple
491
+ // trait implementations actually generating an error, since that can only happen if multiple
492
+ // implementations of the same method are kept by #[cfg] (due to user error).
493
+ // Thus, by implementing the trait once for each possible implementation of this method (depending on
494
+ // what #[cfg] allows), forwarding the cfg attrs, we ensure this trait impl will remain in the code if
495
+ // at least one of the method impls are kept.
458
496
register_class_impl = quote ! {
497
+ #register_class_impl
498
+
499
+ #( #cfg_attrs) *
459
500
impl :: godot:: obj:: cap:: GodotRegisterClass for #class_name {
460
501
fn __godot_register_class( builder: & mut :: godot:: builder:: GodotBuilder <Self >) {
461
502
<Self as #trait_name>:: register_class( builder)
462
503
}
463
504
}
464
505
} ;
465
506
466
- register_fn = quote ! {
467
- Some ( #prv:: ErasedRegisterFn {
507
+ // Adds a match arm for each implementation of this method, transferring its respective cfg attrs to
508
+ // the corresponding match arm (see explanation for the match after this loop).
509
+ // In principle, the cfg attrs will allow only either 0 or 1 of a function with this name to exist,
510
+ // unless there are duplicate implementations for the same method, which should error anyway.
511
+ // Thus, in any correct program, the match arms (which are, in principle, identical) will be reduced to
512
+ // a single one at most, since we forward the cfg attrs. The idea here is precisely to keep this
513
+ // specific match arm 'alive' if at least one implementation of the method is also kept (hence why all
514
+ // the match arms are identical).
515
+ register_fn = Some ( quote ! {
516
+ #register_fn
517
+ #( #cfg_attrs) *
518
+ ( ) => Some ( #prv:: ErasedRegisterFn {
468
519
raw: #prv:: callbacks:: register_class_by_builder:: <#class_name>
469
- } )
470
- } ;
520
+ } ) ,
521
+ } ) ;
471
522
}
472
523
473
524
"init" => {
474
525
godot_init_impl = quote ! {
526
+ #godot_init_impl
527
+
528
+ #( #cfg_attrs) *
475
529
impl :: godot:: obj:: cap:: GodotInit for #class_name {
476
530
fn __godot_init( base: :: godot:: obj:: Base <Self :: Base >) -> Self {
477
531
<Self as #trait_name>:: init( base)
478
532
}
479
533
}
480
534
} ;
481
- create_fn = quote ! { Some ( #prv:: callbacks:: create:: <#class_name>) } ;
535
+ create_fn = Some ( quote ! {
536
+ #create_fn
537
+ #( #cfg_attrs) *
538
+ ( ) => Some ( #prv:: callbacks:: create:: <#class_name>) ,
539
+ } ) ;
482
540
if cfg ! ( since_api = "4.2" ) {
483
- recreate_fn = quote ! { Some ( #prv:: callbacks:: recreate:: <#class_name>) } ;
541
+ recreate_fn = Some ( quote ! {
542
+ #recreate_fn
543
+ #( #cfg_attrs) *
544
+ ( ) => Some ( #prv:: callbacks:: recreate:: <#class_name>) ,
545
+ } ) ;
484
546
}
485
547
}
486
548
487
549
"to_string" => {
488
550
to_string_impl = quote ! {
551
+ #to_string_impl
552
+
553
+ #( #cfg_attrs) *
489
554
impl :: godot:: obj:: cap:: GodotToString for #class_name {
490
555
fn __godot_to_string( & self ) -> :: godot:: builtin:: GodotString {
491
556
<Self as #trait_name>:: to_string( self )
492
557
}
493
558
}
494
559
} ;
495
560
496
- to_string_fn = quote ! { Some ( #prv:: callbacks:: to_string:: <#class_name>) } ;
561
+ to_string_fn = Some ( quote ! {
562
+ #to_string_fn
563
+ #( #cfg_attrs) *
564
+ ( ) => Some ( #prv:: callbacks:: to_string:: <#class_name>) ,
565
+ } ) ;
497
566
}
498
567
499
568
"on_notification" => {
500
569
on_notification_impl = quote ! {
570
+ #on_notification_impl
571
+
572
+ #( #cfg_attrs) *
501
573
impl :: godot:: obj:: cap:: GodotNotification for #class_name {
502
574
fn __godot_notification( & mut self , what: i32 ) {
503
575
if :: godot:: private:: is_class_inactive( Self :: __config( ) . is_tool) {
@@ -509,9 +581,11 @@ fn transform_trait_impl(original_impl: Impl) -> Result<TokenStream, Error> {
509
581
}
510
582
} ;
511
583
512
- on_notification_fn = quote ! {
513
- Some ( #prv:: callbacks:: on_notification:: <#class_name>)
514
- } ;
584
+ on_notification_fn = Some ( quote ! {
585
+ #on_notification_fn
586
+ #( #cfg_attrs) *
587
+ ( ) => Some ( #prv:: callbacks:: on_notification:: <#class_name>) ,
588
+ } ) ;
515
589
}
516
590
517
591
// Other virtual methods, like ready, process etc.
@@ -530,6 +604,11 @@ fn transform_trait_impl(original_impl: Impl) -> Result<TokenStream, Error> {
530
604
} else {
531
605
format ! ( "_{method_name}" )
532
606
} ;
607
+ // Note that, if the same method is implemented multiple times (with different cfg attr combinations),
608
+ // then there will be multiple match arms annotated with the same cfg attr combinations, thus they will
609
+ // be reduced to just one arm (at most, if the implementations aren't all removed from compilation) for
610
+ // each distinct method.
611
+ virtual_method_cfg_attrs. push ( cfg_attrs) ;
533
612
virtual_method_names. push ( virtual_method_name) ;
534
613
virtual_methods. push ( method) ;
535
614
}
@@ -541,6 +620,17 @@ fn transform_trait_impl(original_impl: Impl) -> Result<TokenStream, Error> {
541
620
. map ( |method| make_virtual_method_callback ( & class_name, method) )
542
621
. collect ( ) ;
543
622
623
+ // Use 'match' as a way to only emit 'Some(...)' if the given cfg attrs allow.
624
+ // This permits users to conditionally remove virtual method impls from compilation while also removing their FFI
625
+ // glue which would otherwise make them visible to Godot even if not really implemented.
626
+ // Needs '#[allow(unreachable_patterns)]' to avoid warnings about the last match arm.
627
+ // Also requires '#[allow(clippy::match_single_binding)]' for similar reasons.
628
+ let register_fn = convert_to_match_expression_or_none ( register_fn) ;
629
+ let create_fn = convert_to_match_expression_or_none ( create_fn) ;
630
+ let recreate_fn = convert_to_match_expression_or_none ( recreate_fn) ;
631
+ let to_string_fn = convert_to_match_expression_or_none ( to_string_fn) ;
632
+ let on_notification_fn = convert_to_match_expression_or_none ( on_notification_fn) ;
633
+
544
634
let result = quote ! {
545
635
#original_impl
546
636
#godot_init_impl
@@ -560,6 +650,7 @@ fn transform_trait_impl(original_impl: Impl) -> Result<TokenStream, Error> {
560
650
561
651
match name {
562
652
#(
653
+ #( #virtual_method_cfg_attrs) *
563
654
#virtual_method_names => #virtual_method_callbacks,
564
655
) *
565
656
_ => None ,
0 commit comments