Repository: facebook/react
Pull request: [compiler] Switch to track setStates by aliasing and id instead of identifier names


Summary:
This makes the setState usage logic much more robust. We no longer rely on identifierName.

Now we track when a setState is loaded into a new promoted identifier variable and track this in a map `setStateLoaded` map.

For other types of instructions we consider the setState to be being used. In this case we record its usage into the `setStateUsages` map.



Test Plan:
We expect no changes in behavior for the current tests

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34973).
* #35044
* #35020
* __->__ #34973
* #34972

Changed files:
filename: compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoDerivedComputationsInEffects_exp.ts
patch: @@ -21,7 +21,6 @@ import {
   isUseStateType,
   BasicBlock,
   isUseRefType,
-  GeneratedSource,
   SourceLocation,
 } from '../HIR';
 import {eachInstructionLValue, eachInstructionOperand} from '../HIR/visitors';
@@ -42,8 +41,8 @@ type ValidationContext = {
   readonly errors: CompilerError;
   readonly derivationCache: DerivationCache;
   readonly effects: Set<HIRFunction>;
-  readonly setStateCache: Map<string | undefined | null, Array<Place>>;
-  readonly effectSetStateCache: Map<string | undefined | null, Array<Place>>;
+  readonly setStateLoads: Map<IdentifierId, IdentifierId | null>;
+  readonly setStateUsages: Map<IdentifierId, Set<SourceLocation>>;
 };
 
 class DerivationCache {
@@ -180,19 +179,16 @@ export function validateNoDerivedComputationsInEffects_exp(
   const errors = new CompilerError();
   const effects: Set<HIRFunction> = new Set();
 
-  const setStateCache: Map<string | undefined | null, Array<Place>> = new Map();
-  const effectSetStateCache: Map<
-    string | undefined | null,
-    Array<Place>
-  > = new Map();
+  const setStateLoads: Map<IdentifierId, IdentifierId> = new Map();
+  const setStateUsages: Map<IdentifierId, Set<SourceLocation>> = new Map();
 
   const context: ValidationContext = {
     functions,
     errors,
     derivationCache,
     effects,
-    setStateCache,
-    effectSetStateCache,
+    setStateLoads,
+    setStateUsages,
   };
 
   if (fn.fnType === 'Hook') {
@@ -281,11 +277,61 @@ function joinValue(
   return 'fromPropsAndState';
 }
 
+function getRootSetState(
+  key: IdentifierId,
+  loads: Map<IdentifierId, IdentifierId | null>,
+  visited: Set<IdentifierId> = new Set(),
+): IdentifierId | null {
+  if (visited.has(key)) {
+    return null;
+  }
+  visited.add(key);
+
+  const parentId = loads.get(key);
+
+  if (parentId === undefined) {
+    return null;
+  }
+
+  if (parentId === null) {
+    return key;
+  }
+
+  return getRootSetState(parentId, loads, visited);
+}
+
+function maybeRecordSetState(
+  instr: Instruction,
+  loads: Map<IdentifierId, IdentifierId | null>,
+  usages: Map<IdentifierId, Set<SourceLocation>>,
+): void {
+  for (const operand of eachInstructionLValue(instr)) {
+    if (
+      instr.value.kind === 'LoadLocal' &&
+      loads.has(instr.value.place.identifier.id)
+    ) {
+      loads.set(operand.identifier.id, instr.value.place.identifier.id);
+    } else {
+      if (isSetStateType(operand.identifier)) {
+        // this is a root setState
+        loads.set(operand.identifier.id, null);
+      }
+    }
+
+    const rootSetState = getRootSetState(operand.identifier.id, loads);
+    if (rootSetState !== null && usages.get(rootSetState) === undefined) {
+      usages.set(rootSetState, new Set([operand.loc]));
+    }
+  }
+}
+
 function recordInstructionDerivations(
   instr: Instruction,
   context: ValidationContext,
   isFirstPass: boolean,
 ): void {
+  maybeRecordSetState(instr, context.setStateLoads, context.setStateUsages);
+
   let typeOfValue: TypeOfValue = 'ignored';
   let isSource: boolean = false;
   const sources: Set<IdentifierId> = new Set();
@@ -318,15 +364,13 @@ function recordInstructionDerivations(
   }
 
   for (const operand of eachInstructionOperand(instr)) {
-    if (
-      isSetStateType(operand.identifier) &&
-      operand.loc !== GeneratedSource &&
-      isFirstPass
-    ) {
-      if (context.setStateCache.has(operand.loc.identifierName)) {
-        context.setStateCache.get(operand.loc.identifierName)!.push(operand);
-      } else {
-        context.setStateCache.set(operand.loc.identifierName, [operand]);
+    if (context.setStateLoads.has(operand.identifier.id)) {
+      const rootSetStateId = getRootSetState(
+        operand.identifier.id,
+        context.setStateLoads,
+      );
+      if (rootSetStateId !== null) {
+        context.setStateUsages.get(rootSetStateId)?.add(operand.loc);
       }
     }
 
@@ -532,11 +576,16 @@ function validateEffect(
 
   const effectDerivedSetStateCalls: Array<{
     value: CallExpression;
-    loc: SourceLocation;
+    id: IdentifierId;
     sourceIds: Set<IdentifierId>;
     typeOfValue: TypeOfValue;
   }> = [];
 
+  const effectSetStateUsages: Map<
+    IdentifierId,
+    Set<SourceLocation>
+  > = new Map();
+
   const globals: Set<IdentifierId> = new Set();
   for (const block of effectFunction.body.blocks.values()) {
     for (const pred of block.preds) {
@@ -552,19 +601,16 @@ function validateEffect(
         return;
       }
 
+      maybeRecordSetState(instr, context.setStateLoads, effectSetStateUsages);
+
       for (const operand of eachInstructionOperand(instr)) {
-        if (
-          isSetStateType(operand.identifier) &&
-          operand.loc !== GeneratedSource
-        ) {
-          if (context.effectSetStateCache.has(operand.loc.identifierName)) {
-            context.effectSetStateCache
-              .get(operand.loc.identifierName)!
-              .push(operand);
-          } else {
-            context.effectSetStateCache.set(operand.loc.identifierName, [
-              operand,
-            ]);
+        if (context.setStateLoads.has(operand.identifier.id)) {
+          const rootSetStateId = getRootSetState(
+            operand.identifier.id,
+            context.setStateLoads,
+          );
+          if (rootSetStateId !== null) {
+            effectSetStateUsages.get(rootSetStateId)?.add(operand.loc);
           }
         }
       }
@@ -582,7 +628,7 @@ function validateEffect(
         if (argMetadata !== undefined) {
           effectDerivedSetStateCalls.push({
             value: instr.value,
-            loc: instr.value.callee.loc,
+            id: instr.value.callee.identifier.id,
             sourceIds: argMetadata.sourcesIds,
             typeOfValue: argMetadata.typeOfValue,
           });
@@ -616,15 +662,17 @@ function validateEffect(
   }
 
   for (const derivedSetStateCall of effectDerivedSetStateCalls) {
+    const rootSetStateCall = getRootSetState(
+      derivedSetStateCall.id,
+      context.setStateLoads,
+    );
+
     if (
-      derivedSetStateCall.loc !== GeneratedSource &&
-      context.effectSetStateCache.has(derivedSetStateCall.loc.identifierName) &&
-      context.setStateCache.has(derivedSetStateCall.loc.identifierName) &&
-      context.effectSetStateCache.get(derivedSetStateCall.loc.identifierName)!
-        .length ===
-        context.setStateCache.get(derivedSetStateCall.loc.identifierName)!
-          .length -
-          1
+      rootSetStateCall !== null &&
+      effectSetStateUsages.has(rootSetStateCall) &&
+      context.setStateUsages.has(rootSetStateCall) &&
+      effectSetStateUsages.get(rootSetStateCall)!.size ===
+        context.setStateUsages.get(rootSetStateCall)!.size - 1
     ) {
       const propsSet = new Set<string>();
       const stateSet = new Set<string>();