@@ -20,6 +20,8 @@ import {
2020  isUseStateType , 
2121  BasicBlock , 
2222  isUseRefType , 
23+   GeneratedSource , 
24+   SourceLocation , 
2325}  from  '../HIR' ; 
2426import  { eachInstructionLValue ,  eachInstructionOperand }  from  '../HIR/visitors' ; 
2527import  { isMutable }  from  '../ReactiveScopes/InferReactiveScopeVariables' ; 
@@ -38,6 +40,8 @@ type ValidationContext = {
3840  readonly  errors : CompilerError ; 
3941  readonly  derivationCache : DerivationCache ; 
4042  readonly  effects : Set < HIRFunction > ; 
43+   readonly  setStateCache : Map < string  |  undefined  |  null ,  Array < Place > > ; 
44+   readonly  effectSetStateCache : Map < string  |  undefined  |  null ,  Array < Place > > ; 
4145} ; 
4246
4347class  DerivationCache  { 
@@ -148,11 +152,19 @@ export function validateNoDerivedComputationsInEffects_exp(
148152  const  errors  =  new  CompilerError ( ) ; 
149153  const  effects : Set < HIRFunction >  =  new  Set ( ) ; 
150154
155+   const  setStateCache : Map < string  |  undefined  |  null ,  Array < Place > >  =  new  Map ( ) ; 
156+   const  effectSetStateCache : Map < 
157+     string  |  undefined  |  null , 
158+     Array < Place > 
159+   >  =  new  Map ( ) ; 
160+ 
151161  const  context : ValidationContext  =  { 
152162    functions, 
153163    errors, 
154164    derivationCache, 
155165    effects, 
166+     setStateCache, 
167+     effectSetStateCache, 
156168  } ; 
157169
158170  if  ( fn . fnType  ===  'Hook' )  { 
@@ -178,13 +190,16 @@ export function validateNoDerivedComputationsInEffects_exp(
178190    } 
179191  } 
180192
193+   let  isFirstPass  =  true ; 
181194  do  { 
182195    for  ( const  block  of  fn . body . blocks . values ( ) )  { 
183196      recordPhiDerivations ( block ,  context ) ; 
184197      for  ( const  instr  of  block . instructions )  { 
185-         recordInstructionDerivations ( instr ,  context ) ; 
198+         recordInstructionDerivations ( instr ,  context ,   isFirstPass ) ; 
186199      } 
187200    } 
201+ 
202+     isFirstPass  =  false ; 
188203  }  while  ( context . derivationCache . snapshot ( ) ) ; 
189204
190205  for  ( const  effect  of  effects )  { 
@@ -239,6 +254,7 @@ function joinValue(
239254function  recordInstructionDerivations ( 
240255  instr : Instruction , 
241256  context : ValidationContext , 
257+   isFirstPass : boolean , 
242258) : void { 
243259  let  typeOfValue : TypeOfValue  =  'ignored' ; 
244260  const  sources : Set < IdentifierId >  =  new  Set ( ) ; 
@@ -247,7 +263,7 @@ function recordInstructionDerivations(
247263    context . functions . set ( lvalue . identifier . id ,  value ) ; 
248264    for  ( const  [ ,  block ]  of  value . loweredFunc . func . body . blocks )  { 
249265      for  ( const  instr  of  block . instructions )  { 
250-         recordInstructionDerivations ( instr ,  context ) ; 
266+         recordInstructionDerivations ( instr ,  context ,   isFirstPass ) ; 
251267      } 
252268    } 
253269  }  else  if  ( value . kind  ===  'CallExpression'  ||  value . kind  ===  'MethodCall' )  { 
@@ -273,6 +289,18 @@ function recordInstructionDerivations(
273289  } 
274290
275291  for  ( const  operand  of  eachInstructionOperand ( instr ) )  { 
292+     if  ( 
293+       isSetStateType ( operand . identifier )  && 
294+       operand . loc  !==  GeneratedSource  && 
295+       isFirstPass 
296+     )  { 
297+       if  ( context . setStateCache . has ( operand . loc . identifierName ) )  { 
298+         context . setStateCache . get ( operand . loc . identifierName ) ! . push ( operand ) ; 
299+       }  else  { 
300+         context . setStateCache . set ( operand . loc . identifierName ,  [ operand ] ) ; 
301+       } 
302+     } 
303+ 
276304    const  operandMetadata  =  context . derivationCache . cache . get ( 
277305      operand . identifier . id , 
278306    ) ; 
@@ -347,6 +375,7 @@ function validateEffect(
347375
348376  const  effectDerivedSetStateCalls : Array < { 
349377    value : CallExpression ; 
378+     loc : SourceLocation ; 
350379    sourceIds : Set < IdentifierId > ; 
351380  } >  =  [ ] ; 
352381
@@ -365,6 +394,23 @@ function validateEffect(
365394        return ; 
366395      } 
367396
397+       for  ( const  operand  of  eachInstructionOperand ( instr ) )  { 
398+         if  ( 
399+           isSetStateType ( operand . identifier )  && 
400+           operand . loc  !==  GeneratedSource 
401+         )  { 
402+           if  ( context . effectSetStateCache . has ( operand . loc . identifierName ) )  { 
403+             context . effectSetStateCache 
404+               . get ( operand . loc . identifierName ) ! 
405+               . push ( operand ) ; 
406+           }  else  { 
407+             context . effectSetStateCache . set ( operand . loc . identifierName ,  [ 
408+               operand , 
409+             ] ) ; 
410+           } 
411+         } 
412+       } 
413+ 
368414      if  ( 
369415        instr . value . kind  ===  'CallExpression'  && 
370416        isSetStateType ( instr . value . callee . identifier )  && 
@@ -378,6 +424,7 @@ function validateEffect(
378424        if  ( argMetadata  !==  undefined )  { 
379425          effectDerivedSetStateCalls . push ( { 
380426            value : instr . value , 
427+             loc : instr . value . callee . loc , 
381428            sourceIds : argMetadata . sourcesIds , 
382429          } ) ; 
383430        } 
@@ -410,13 +457,24 @@ function validateEffect(
410457  } 
411458
412459  for  ( const  derivedSetStateCall  of  effectDerivedSetStateCalls )  { 
413-     context . errors . push ( { 
414-       category : ErrorCategory . EffectDerivationsOfState , 
415-       reason :
416-         'Values derived from props and state should be calculated during render, not in an effect. (https://react.dev/learn/you-might-not-need-an-effect#updating-state-based-on-props-or-state)' , 
417-       description : null , 
418-       loc : derivedSetStateCall . value . callee . loc , 
419-       suggestions : null , 
420-     } ) ; 
460+     if  ( 
461+       derivedSetStateCall . loc  !==  GeneratedSource  && 
462+       context . effectSetStateCache . has ( derivedSetStateCall . loc . identifierName )  && 
463+       context . setStateCache . has ( derivedSetStateCall . loc . identifierName )  && 
464+       context . effectSetStateCache . get ( derivedSetStateCall . loc . identifierName ) ! 
465+         . length  === 
466+         context . setStateCache . get ( derivedSetStateCall . loc . identifierName ) ! 
467+           . length  - 
468+           1 
469+     )  { 
470+       context . errors . push ( { 
471+         category : ErrorCategory . EffectDerivationsOfState , 
472+         reason :
473+           'Values derived from props and state should be calculated during render, not in an effect. (https://react.dev/learn/you-might-not-need-an-effect#updating-state-based-on-props-or-state)' , 
474+         description : null , 
475+         loc : derivedSetStateCall . value . callee . loc , 
476+         suggestions : null , 
477+       } ) ; 
478+     } 
421479  } 
422480} 
0 commit comments