@samitouri / QOS-React / commits / f76c3617e0

[compiler] Switch to track setStates by aliasing and id instead of identifier names (#34973)

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

Jorge Cabiedes committed Nov 10, 2025 at 12:16 UTC f76c3617e0f00c98656a36d1b4083b397c7638f2
1 file changed +89 -41
compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoDerivedComputationsInEffects_exp.ts
+89 -41
@@ -21,7 +21,6 @@ import {
21 isUseStateType,
22 BasicBlock,
23 isUseRefType,
24 - GeneratedSource,
24 SourceLocation,
25 } from '../HIR';
26 import {eachInstructionLValue, eachInstructionOperand} from '../HIR/visitors';
@@ -42,8 +41,8 @@ type ValidationContext = {
41 readonly errors: CompilerError;
42 readonly derivationCache: DerivationCache;
43 readonly effects: Set<HIRFunction>;
45 - readonly setStateCache: Map<string | undefined | null, Array<Place>>;
46 - readonly effectSetStateCache: Map<string | undefined | null, Array<Place>>;
44 + readonly setStateLoads: Map<IdentifierId, IdentifierId | null>;
45 + readonly setStateUsages: Map<IdentifierId, Set<SourceLocation>>;
46 };
47
48 class DerivationCache {
@@ -180,19 +179,16 @@ export function validateNoDerivedComputationsInEffects_exp(
179 const errors = new CompilerError();
180 const effects: Set<HIRFunction> = new Set();
181
183 - const setStateCache: Map<string | undefined | null, Array<Place>> = new Map();
184 - const effectSetStateCache: Map<
185 - string | undefined | null,
186 - Array<Place>
187 - > = new Map();
182 + const setStateLoads: Map<IdentifierId, IdentifierId> = new Map();
183 + const setStateUsages: Map<IdentifierId, Set<SourceLocation>> = new Map();
184
185 const context: ValidationContext = {
186 functions,
187 errors,
188 derivationCache,
189 effects,
194 - setStateCache,
195 - effectSetStateCache,
190 + setStateLoads,
191 + setStateUsages,
192 };
193
194 if (fn.fnType === 'Hook') {
@@ -281,11 +277,61 @@ function joinValue(
277 return 'fromPropsAndState';
278 }
279
280 +function getRootSetState(
281 + key: IdentifierId,
282 + loads: Map<IdentifierId, IdentifierId | null>,
283 + visited: Set<IdentifierId> = new Set(),
284 +): IdentifierId | null {
285 + if (visited.has(key)) {
286 + return null;
287 + }
288 + visited.add(key);
289 +
290 + const parentId = loads.get(key);
291 +
292 + if (parentId === undefined) {
293 + return null;
294 + }
295 +
296 + if (parentId === null) {
297 + return key;
298 + }
299 +
300 + return getRootSetState(parentId, loads, visited);
301 +}
302 +
303 +function maybeRecordSetState(
304 + instr: Instruction,
305 + loads: Map<IdentifierId, IdentifierId | null>,
306 + usages: Map<IdentifierId, Set<SourceLocation>>,
307 +): void {
308 + for (const operand of eachInstructionLValue(instr)) {
309 + if (
310 + instr.value.kind === 'LoadLocal' &&
311 + loads.has(instr.value.place.identifier.id)
312 + ) {
313 + loads.set(operand.identifier.id, instr.value.place.identifier.id);
314 + } else {
315 + if (isSetStateType(operand.identifier)) {
316 + // this is a root setState
317 + loads.set(operand.identifier.id, null);
318 + }
319 + }
320 +
321 + const rootSetState = getRootSetState(operand.identifier.id, loads);
322 + if (rootSetState !== null && usages.get(rootSetState) === undefined) {
323 + usages.set(rootSetState, new Set([operand.loc]));
324 + }
325 + }
326 +}
327 +
328 function recordInstructionDerivations(
329 instr: Instruction,
330 context: ValidationContext,
331 isFirstPass: boolean,
332 ): void {
333 + maybeRecordSetState(instr, context.setStateLoads, context.setStateUsages);
334 +
335 let typeOfValue: TypeOfValue = 'ignored';
336 let isSource: boolean = false;
337 const sources: Set<IdentifierId> = new Set();
@@ -318,15 +364,13 @@ function recordInstructionDerivations(
364 }
365
366 for (const operand of eachInstructionOperand(instr)) {
321 - if (
322 - isSetStateType(operand.identifier) &&
323 - operand.loc !== GeneratedSource &&
324 - isFirstPass
325 - ) {
326 - if (context.setStateCache.has(operand.loc.identifierName)) {
327 - context.setStateCache.get(operand.loc.identifierName)!.push(operand);
328 - } else {
329 - context.setStateCache.set(operand.loc.identifierName, [operand]);
367 + if (context.setStateLoads.has(operand.identifier.id)) {
368 + const rootSetStateId = getRootSetState(
369 + operand.identifier.id,
370 + context.setStateLoads,
371 + );
372 + if (rootSetStateId !== null) {
373 + context.setStateUsages.get(rootSetStateId)?.add(operand.loc);
374 }
375 }
376
@@ -532,11 +576,16 @@ function validateEffect(
576
577 const effectDerivedSetStateCalls: Array<{
578 value: CallExpression;
535 - loc: SourceLocation;
579 + id: IdentifierId;
580 sourceIds: Set<IdentifierId>;
581 typeOfValue: TypeOfValue;
582 }> = [];
583
584 + const effectSetStateUsages: Map<
585 + IdentifierId,
586 + Set<SourceLocation>
587 + > = new Map();
588 +
589 const globals: Set<IdentifierId> = new Set();
590 for (const block of effectFunction.body.blocks.values()) {
591 for (const pred of block.preds) {
@@ -552,19 +601,16 @@ function validateEffect(
601 return;
602 }
603
604 + maybeRecordSetState(instr, context.setStateLoads, effectSetStateUsages);
605 +
606 for (const operand of eachInstructionOperand(instr)) {
556 - if (
557 - isSetStateType(operand.identifier) &&
558 - operand.loc !== GeneratedSource
559 - ) {
560 - if (context.effectSetStateCache.has(operand.loc.identifierName)) {
561 - context.effectSetStateCache
562 - .get(operand.loc.identifierName)!
563 - .push(operand);
564 - } else {
565 - context.effectSetStateCache.set(operand.loc.identifierName, [
566 - operand,
567 - ]);
607 + if (context.setStateLoads.has(operand.identifier.id)) {
608 + const rootSetStateId = getRootSetState(
609 + operand.identifier.id,
610 + context.setStateLoads,
611 + );
612 + if (rootSetStateId !== null) {
613 + effectSetStateUsages.get(rootSetStateId)?.add(operand.loc);
614 }
615 }
616 }
@@ -582,7 +628,7 @@ function validateEffect(
628 if (argMetadata !== undefined) {
629 effectDerivedSetStateCalls.push({
630 value: instr.value,
585 - loc: instr.value.callee.loc,
631 + id: instr.value.callee.identifier.id,
632 sourceIds: argMetadata.sourcesIds,
633 typeOfValue: argMetadata.typeOfValue,
634 });
@@ -616,15 +662,17 @@ function validateEffect(
662 }
663
664 for (const derivedSetStateCall of effectDerivedSetStateCalls) {
665 + const rootSetStateCall = getRootSetState(
666 + derivedSetStateCall.id,
667 + context.setStateLoads,
668 + );
669 +
670 if (
620 - derivedSetStateCall.loc !== GeneratedSource &&
621 - context.effectSetStateCache.has(derivedSetStateCall.loc.identifierName) &&
622 - context.setStateCache.has(derivedSetStateCall.loc.identifierName) &&
623 - context.effectSetStateCache.get(derivedSetStateCall.loc.identifierName)!
624 - .length ===
625 - context.setStateCache.get(derivedSetStateCall.loc.identifierName)!
626 - .length -
627 - 1
671 + rootSetStateCall !== null &&
672 + effectSetStateUsages.has(rootSetStateCall) &&
673 + context.setStateUsages.has(rootSetStateCall) &&
674 + effectSetStateUsages.get(rootSetStateCall)!.size ===
675 + context.setStateUsages.get(rootSetStateCall)!.size - 1
676 ) {
677 const propsSet = new Set<string>();
678 const stateSet = new Set<string>();