@@ -12,7 +12,7 @@ import {
1212  CompilerErrorDetail , 
1313  ErrorSeverity , 
1414}  from  '../CompilerError' ; 
15- import  { ReactFunctionType }  from  '../HIR/Environment' ; 
15+ import  { ExternalFunction ,   ReactFunctionType }  from  '../HIR/Environment' ; 
1616import  { CodegenFunction }  from  '../ReactiveScopes' ; 
1717import  { isComponentDeclaration }  from  '../Utils/ComponentDeclaration' ; 
1818import  { isHookDeclaration }  from  '../Utils/HookDeclaration' ; 
@@ -31,6 +31,7 @@ import {
3131  suppressionsToCompilerError , 
3232}  from  './Suppression' ; 
3333import  { GeneratedSource }  from  '../HIR' ; 
34+ import  { Err ,  Ok ,  Result }  from  '../Utils/Result' ; 
3435
3536export  type  CompilerPass  =  { 
3637  opts : PluginOptions ; 
@@ -40,15 +41,24 @@ export type CompilerPass = {
4041} ; 
4142export  const  OPT_IN_DIRECTIVES  =  new  Set ( [ 'use forget' ,  'use memo' ] ) ; 
4243export  const  OPT_OUT_DIRECTIVES  =  new  Set ( [ 'use no forget' ,  'use no memo' ] ) ; 
44+ const  DYNAMIC_GATING_DIRECTIVE  =  new  RegExp ( '^use memo if\\(([^\\)]*)\\)$' ) ; 
4345
44- export  function  findDirectiveEnablingMemoization ( 
46+ export  function  tryFindDirectiveEnablingMemoization ( 
4547  directives : Array < t . Directive > , 
46- ) : t . Directive  |  null  { 
47-   return  ( 
48-     directives . find ( directive  => 
49-       OPT_IN_DIRECTIVES . has ( directive . value . value ) , 
50-     )  ??  null 
48+   opts : PluginOptions , 
49+ ) : Result < t . Directive  |  null ,  CompilerError >  { 
50+   const  optIn  =  directives . find ( directive  => 
51+     OPT_IN_DIRECTIVES . has ( directive . value . value ) , 
5152  ) ; 
53+   if  ( optIn  !=  null )  { 
54+     return  Ok ( optIn ) ; 
55+   } 
56+   const  dynamicGating  =  findDirectivesDynamicGating ( directives ,  opts ) ; 
57+   if  ( dynamicGating . isOk ( ) )  { 
58+     return  Ok ( dynamicGating . unwrap ( ) ?. directive  ??  null ) ; 
59+   }  else  { 
60+     return  Err ( dynamicGating . unwrapErr ( ) ) ; 
61+   } 
5262} 
5363
5464export  function  findDirectiveDisablingMemoization ( 
@@ -60,6 +70,64 @@ export function findDirectiveDisablingMemoization(
6070    )  ??  null 
6171  ) ; 
6272} 
73+ function  findDirectivesDynamicGating ( 
74+   directives : Array < t . Directive > , 
75+   opts : PluginOptions , 
76+ ) : Result < 
77+   { 
78+     gating : ExternalFunction ; 
79+     directive : t . Directive ; 
80+   }  |  null , 
81+   CompilerError 
82+ >  { 
83+   if  ( opts . dynamicGating  ===  null )  { 
84+     return  Ok ( null ) ; 
85+   } 
86+   const  errors  =  new  CompilerError ( ) ; 
87+   const  result : Array < { directive : t . Directive ;  match : string } >  =  [ ] ; 
88+ 
89+   for  ( const  directive  of  directives )  { 
90+     const  maybeMatch  =  DYNAMIC_GATING_DIRECTIVE . exec ( directive . value . value ) ; 
91+     if  ( maybeMatch  !=  null  &&  maybeMatch [ 1 ]  !=  null )  { 
92+       if  ( t . isValidIdentifier ( maybeMatch [ 1 ] ) )  { 
93+         result . push ( { directive,  match : maybeMatch [ 1 ] } ) ; 
94+       }  else  { 
95+         errors . push ( { 
96+           reason : `Dynamic gating directive is not a valid JavaScript identifier` , 
97+           description : `Found '${ directive . value . value }  , 
98+           severity : ErrorSeverity . InvalidReact , 
99+           loc : directive . loc  ??  null , 
100+           suggestions : null , 
101+         } ) ; 
102+       } 
103+     } 
104+   } 
105+   if  ( errors . hasErrors ( ) )  { 
106+     return  Err ( errors ) ; 
107+   }  else  if  ( result . length  >  1 )  { 
108+     const  error  =  new  CompilerError ( ) ; 
109+     error . push ( { 
110+       reason : `Multiple dynamic gating directives found` , 
111+       description : `Expected a single directive but found [${ result  
112+         . map ( r  =>  r . directive . value . value )  
113+         . join ( ', ' ) }  ]`, 
114+       severity : ErrorSeverity . InvalidReact , 
115+       loc : result [ 0 ] . directive . loc  ??  null , 
116+       suggestions : null , 
117+     } ) ; 
118+     return  Err ( error ) ; 
119+   }  else  if  ( result . length  ===  1 )  { 
120+     return  Ok ( { 
121+       gating : { 
122+         source : opts . dynamicGating . source , 
123+         importSpecifierName : result [ 0 ] . match , 
124+       } , 
125+       directive : result [ 0 ] . directive , 
126+     } ) ; 
127+   }  else  { 
128+     return  Ok ( null ) ; 
129+   } 
130+ } 
63131
64132function  isCriticalError ( err : unknown ) : boolean  { 
65133  return  ! ( err  instanceof  CompilerError )  ||  err . isCritical ( ) ; 
@@ -477,12 +545,32 @@ function processFn(
477545  fnType : ReactFunctionType , 
478546  programContext : ProgramContext , 
479547) : null  |  CodegenFunction  { 
480-   let  directives ; 
548+   let  directives : { 
549+     optIn : t . Directive  |  null ; 
550+     optOut : t . Directive  |  null ; 
551+   } ; 
481552  if  ( fn . node . body . type  !==  'BlockStatement' )  { 
482-     directives  =  { optIn : null ,  optOut : null } ; 
553+     directives  =  { 
554+       optIn : null , 
555+       optOut : null , 
556+     } ; 
483557  }  else  { 
558+     const  optIn  =  tryFindDirectiveEnablingMemoization ( 
559+       fn . node . body . directives , 
560+       programContext . opts , 
561+     ) ; 
562+     if  ( optIn . isErr ( ) )  { 
563+       /** 
564+        * If parsing opt-in directive fails, it's most likely that React Compiler 
565+        * was not tested or rolled out on this function. In that case, we handle 
566+        * the error and fall back to the safest option which is to not optimize 
567+        * the function. 
568+        */ 
569+       handleError ( optIn . unwrapErr ( ) ,  programContext ,  fn . node . loc  ??  null ) ; 
570+       return  null ; 
571+     } 
484572    directives  =  { 
485-       optIn : findDirectiveEnablingMemoization ( fn . node . body . directives ) , 
573+       optIn : optIn . unwrapOr ( null ) , 
486574      optOut : findDirectiveDisablingMemoization ( fn . node . body . directives ) , 
487575    } ; 
488576  } 
@@ -659,25 +747,31 @@ function applyCompiledFunctions(
659747  pass : CompilerPass , 
660748  programContext : ProgramContext , 
661749) : void { 
662-   const  referencedBeforeDeclared  = 
663-     pass . opts . gating  !=  null 
664-       ? getFunctionReferencedBeforeDeclarationAtTopLevel ( program ,  compiledFns ) 
665-       : null ; 
750+   let  referencedBeforeDeclared  =  null ; 
666751  for  ( const  result  of  compiledFns )  { 
667752    const  { kind,  originalFn,  compiledFn}  =  result ; 
668753    const  transformedFn  =  createNewFunctionNode ( originalFn ,  compiledFn ) ; 
669754    programContext . alreadyCompiled . add ( transformedFn ) ; 
670755
671-     if  ( referencedBeforeDeclared  !=  null  &&  kind  ===  'original' )  { 
672-       CompilerError . invariant ( pass . opts . gating  !=  null ,  { 
673-         reason : "Expected 'gating' import to be present" , 
674-         loc : null , 
675-       } ) ; 
756+     let  dynamicGating : ExternalFunction  |  null  =  null ; 
757+     if  ( originalFn . node . body . type  ===  'BlockStatement' )  { 
758+       const  result  =  findDirectivesDynamicGating ( 
759+         originalFn . node . body . directives , 
760+         pass . opts , 
761+       ) ; 
762+       if  ( result . isOk ( ) )  { 
763+         dynamicGating  =  result . unwrap ( ) ?. gating  ??  null ; 
764+       } 
765+     } 
766+     const  functionGating  =  dynamicGating  ??  pass . opts . gating ; 
767+     if  ( kind  ===  'original'  &&  functionGating  !=  null )  { 
768+       referencedBeforeDeclared  ??= 
769+         getFunctionReferencedBeforeDeclarationAtTopLevel ( program ,  compiledFns ) ; 
676770      insertGatedFunctionDeclaration ( 
677771        originalFn , 
678772        transformedFn , 
679773        programContext , 
680-         pass . opts . gating , 
774+         functionGating , 
681775        referencedBeforeDeclared . has ( result ) , 
682776      ) ; 
683777    }  else  { 
@@ -733,8 +827,13 @@ function getReactFunctionType(
733827) : ReactFunctionType  |  null  { 
734828  const  hookPattern  =  pass . opts . environment . hookPattern ; 
735829  if  ( fn . node . body . type  ===  'BlockStatement' )  { 
736-     if  ( findDirectiveEnablingMemoization ( fn . node . body . directives )  !=  null ) 
830+     const  optInDirectives  =  tryFindDirectiveEnablingMemoization ( 
831+       fn . node . body . directives , 
832+       pass . opts , 
833+     ) ; 
834+     if  ( optInDirectives . unwrapOr ( null )  !=  null )  { 
737835      return  getComponentOrHookLike ( fn ,  hookPattern )  ??  'Other' ; 
836+     } 
738837  } 
739838
740839  // Component and hook declarations are known components/hooks 
0 commit comments