@@ -381,6 +381,24 @@ where
381381
382382// ---------------------------------------------------------------------------------------------------------------------------------------------- 
383383
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+ 
384402/// Codegen for `#[godot_api] impl GodotExt for MyType` 
385403fn  transform_trait_impl ( original_impl :  Impl )  -> Result < TokenStream ,  Error >  { 
386404    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> {
391409    let  mut  register_class_impl = TokenStream :: new ( ) ; 
392410    let  mut  on_notification_impl = TokenStream :: new ( ) ; 
393411
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 ; 
399417
400418    let  mut  virtual_methods = vec ! [ ] ; 
419+     let  mut  virtual_method_cfg_attrs = vec ! [ ] ; 
401420    let  mut  virtual_method_names = vec ! [ ] ; 
402421
403422    let  prv = quote !  {  :: godot:: private } ; 
@@ -409,52 +428,99 @@ fn transform_trait_impl(original_impl: Impl) -> Result<TokenStream, Error> {
409428            continue ; 
410429        } ; 
411430
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 < _ > > ( ) ; 
412436        let  method_name = method. name . to_string ( ) ; 
413437        match  method_name. as_str ( )  { 
414438            "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. 
415447                register_class_impl = quote !  { 
448+                     #register_class_impl
449+ 
450+                     #( #cfg_attrs) * 
416451                    impl  :: godot:: obj:: cap:: GodotRegisterClass  for  #class_name { 
417452                        fn  __godot_register_class( builder:  & mut  :: godot:: builder:: GodotBuilder <Self >)  { 
418453                            <Self  as  #trait_name>:: register_class( builder) 
419454                        } 
420455                    } 
421456                } ; 
422457
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  { 
425470                        raw:  #prv:: callbacks:: register_class_by_builder:: <#class_name>
426-                     } ) 
427-                 } ; 
471+                     } ) , 
472+                 } ) ; 
428473            } 
429474
430475            "init"  => { 
431476                godot_init_impl = quote !  { 
477+                     #godot_init_impl
478+ 
479+                     #( #cfg_attrs) * 
432480                    impl  :: godot:: obj:: cap:: GodotInit  for  #class_name { 
433481                        fn  __godot_init( base:  :: godot:: obj:: Base <Self :: Base >)  -> Self  { 
434482                            <Self  as  #trait_name>:: init( base) 
435483                        } 
436484                    } 
437485                } ; 
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+                 } ) ; 
439491                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+                     } ) ; 
441497                } 
442498            } 
443499
444500            "to_string"  => { 
445501                to_string_impl = quote !  { 
502+                     #to_string_impl
503+ 
504+                     #( #cfg_attrs) * 
446505                    impl  :: godot:: obj:: cap:: GodotToString  for  #class_name { 
447506                        fn  __godot_to_string( & self )  -> :: godot:: builtin:: GodotString  { 
448507                            <Self  as  #trait_name>:: to_string( self ) 
449508                        } 
450509                    } 
451510                } ; 
452511
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+                 } ) ; 
454517            } 
455518
456519            "on_notification"  => { 
457520                on_notification_impl = quote !  { 
521+                     #on_notification_impl
522+ 
523+                     #( #cfg_attrs) * 
458524                    impl  :: godot:: obj:: cap:: GodotNotification  for  #class_name { 
459525                        fn  __godot_notification( & mut  self ,  what:  i32 )  { 
460526                            if  :: godot:: private:: is_class_inactive( Self :: __config( ) . is_tool)  { 
@@ -466,9 +532,11 @@ fn transform_trait_impl(original_impl: Impl) -> Result<TokenStream, Error> {
466532                    } 
467533                } ; 
468534
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+                 } ) ; 
472540            } 
473541
474542            // Other virtual methods, like ready, process etc. 
@@ -487,6 +555,11 @@ fn transform_trait_impl(original_impl: Impl) -> Result<TokenStream, Error> {
487555                }  else  { 
488556                    format ! ( "_{method_name}" ) 
489557                } ; 
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) ; 
490563                virtual_method_names. push ( virtual_method_name) ; 
491564                virtual_methods. push ( method) ; 
492565            } 
@@ -498,6 +571,17 @@ fn transform_trait_impl(original_impl: Impl) -> Result<TokenStream, Error> {
498571        . map ( |method| make_virtual_method_callback ( & class_name,  method) ) 
499572        . collect ( ) ; 
500573
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+ 
501585    let  result = quote !  { 
502586        #original_impl
503587        #godot_init_impl
@@ -517,6 +601,7 @@ fn transform_trait_impl(original_impl: Impl) -> Result<TokenStream, Error> {
517601
518602                match  name { 
519603                    #( 
604+                        #( #virtual_method_cfg_attrs) * 
520605                       #virtual_method_names => #virtual_method_callbacks, 
521606                    ) * 
522607                    _ => None , 
@@ -526,6 +611,8 @@ fn transform_trait_impl(original_impl: Impl) -> Result<TokenStream, Error> {
526611
527612        :: godot:: sys:: plugin_add!( __GODOT_PLUGIN_REGISTRY in #prv;  #prv:: ClassPlugin  { 
528613            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 
529616            component:  #prv:: PluginComponent :: UserVirtuals  { 
530617                user_register_fn:  #register_fn, 
531618                user_create_fn:  #create_fn, 
0 commit comments