@@ -424,6 +424,30 @@ where
424424
425425// ----------------------------------------------------------------------------------------------------------------------------------------------
426426
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+
427451/// Codegen for `#[godot_api] impl GodotExt for MyType`
428452fn transform_trait_impl ( original_impl : Impl ) -> Result < TokenStream , Error > {
429453 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> {
434458 let mut register_class_impl = TokenStream :: new ( ) ;
435459 let mut on_notification_impl = TokenStream :: new ( ) ;
436460
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 ;
442466
443467 let mut virtual_methods = vec ! [ ] ;
468+ let mut virtual_method_cfg_attrs = vec ! [ ] ;
444469 let mut virtual_method_names = vec ! [ ] ;
445470
446471 let prv = quote ! { :: godot:: private } ;
@@ -452,52 +477,99 @@ fn transform_trait_impl(original_impl: Impl) -> Result<TokenStream, Error> {
452477 continue ;
453478 } ;
454479
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 < _ > > ( ) ;
455485 let method_name = method. name . to_string ( ) ;
456486 match method_name. as_str ( ) {
457487 "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.
458496 register_class_impl = quote ! {
497+ #register_class_impl
498+
499+ #( #cfg_attrs) *
459500 impl :: godot:: obj:: cap:: GodotRegisterClass for #class_name {
460501 fn __godot_register_class( builder: & mut :: godot:: builder:: GodotBuilder <Self >) {
461502 <Self as #trait_name>:: register_class( builder)
462503 }
463504 }
464505 } ;
465506
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 {
468519 raw: #prv:: callbacks:: register_class_by_builder:: <#class_name>
469- } )
470- } ;
520+ } ) ,
521+ } ) ;
471522 }
472523
473524 "init" => {
474525 godot_init_impl = quote ! {
526+ #godot_init_impl
527+
528+ #( #cfg_attrs) *
475529 impl :: godot:: obj:: cap:: GodotInit for #class_name {
476530 fn __godot_init( base: :: godot:: obj:: Base <Self :: Base >) -> Self {
477531 <Self as #trait_name>:: init( base)
478532 }
479533 }
480534 } ;
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+ } ) ;
482540 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+ } ) ;
484546 }
485547 }
486548
487549 "to_string" => {
488550 to_string_impl = quote ! {
551+ #to_string_impl
552+
553+ #( #cfg_attrs) *
489554 impl :: godot:: obj:: cap:: GodotToString for #class_name {
490555 fn __godot_to_string( & self ) -> :: godot:: builtin:: GodotString {
491556 <Self as #trait_name>:: to_string( self )
492557 }
493558 }
494559 } ;
495560
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+ } ) ;
497566 }
498567
499568 "on_notification" => {
500569 on_notification_impl = quote ! {
570+ #on_notification_impl
571+
572+ #( #cfg_attrs) *
501573 impl :: godot:: obj:: cap:: GodotNotification for #class_name {
502574 fn __godot_notification( & mut self , what: i32 ) {
503575 if :: godot:: private:: is_class_inactive( Self :: __config( ) . is_tool) {
@@ -509,9 +581,11 @@ fn transform_trait_impl(original_impl: Impl) -> Result<TokenStream, Error> {
509581 }
510582 } ;
511583
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+ } ) ;
515589 }
516590
517591 // Other virtual methods, like ready, process etc.
@@ -530,6 +604,11 @@ fn transform_trait_impl(original_impl: Impl) -> Result<TokenStream, Error> {
530604 } else {
531605 format ! ( "_{method_name}" )
532606 } ;
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) ;
533612 virtual_method_names. push ( virtual_method_name) ;
534613 virtual_methods. push ( method) ;
535614 }
@@ -541,6 +620,17 @@ fn transform_trait_impl(original_impl: Impl) -> Result<TokenStream, Error> {
541620 . map ( |method| make_virtual_method_callback ( & class_name, method) )
542621 . collect ( ) ;
543622
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+
544634 let result = quote ! {
545635 #original_impl
546636 #godot_init_impl
@@ -560,6 +650,7 @@ fn transform_trait_impl(original_impl: Impl) -> Result<TokenStream, Error> {
560650
561651 match name {
562652 #(
653+ #( #virtual_method_cfg_attrs) *
563654 #virtual_method_names => #virtual_method_callbacks,
564655 ) *
565656 _ => None ,
0 commit comments