dillon1000/react
Commit
Browse files [compiler] Count loop reassignments as the enclosing scope reassigning the variable (#36732)
A counter initialized before a memo scope and incremented inside it
(`a++` or `a = a + 1` in a `for` body) was emitted as a scope
*dependency*, compared at its pre-loop value (constant every render)
while the cache stored its post-loop value. The memo could never hit, so
the scope recomputed on every render.
Root cause: the phi-union rule in
`InferReactiveScopeVariables.findDisjointMutableValues` only unioned a
phi into the scope when the phi value was mutated after creation, which
range-extension only does for object mutation. Primitive reassignments
around a loop back-edge never extend ranges, so the counter's SSA
versions stayed outside the scope and downstream dependency propagation
classified the pre-loop read as a dep.
The fix unions a phi with its operands and declaration when any operand
is defined at or after the phi's block, i.e. the value is reassigned
around a loop back-edge. This matches the shape the compiler already
produced for non-primitive loop reassignment (`x = [...x, i]`).
Implemented identically in the TypeScript compiler and the Rust port.
Both `a++` and `a = a + 1` variants are pinned by fixtures; the first
commit documents the previously-wrong codegen, the second fixes it
(counter becomes a scope output, dep on `count` only).
Corpus delta beyond the new fixtures is 4 fixtures, all strict
improvements with byte-identical eval output: `for-in-statement-break`,
`for-in-statement-continue`, `for-in-statement-type-inference` (loops
previously re-ran every render, now memoized), and `sequence-expression`
(two memo blocks collapse into one).
Known limitation, unchanged from before: conditional reassignment in a
loop (`for (...) { if (c) a++; }`) routes through a join phi that this
rule cannot see; that shape behaves as it did before this change.
Verification: TS snap 1806/1806, Rust snap 1806/1806, cargo workspace
green, scoped TS-vs-Rust HIR parity harness green.
Closes #34971Changed paths10 files
First-parent comparisoncompiler/crates/react_compiler_inference/src/infer_reactive_scope_variables.rs ModifiedM compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/InferReactiveScopeVariables.ts ModifiedM compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/for-in-statement-break.expect.md ModifiedM compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/for-in-statement-continue.expect.md ModifiedM compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/for-in-statement-type-inference.expect.md ModifiedA compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/for-loop-counter-memoization-assignment.expect.md AddedA compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/for-loop-counter-memoization-assignment.js AddedA compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/for-loop-counter-memoization.expect.md AddedA compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/for-loop-counter-memoization.js AddedM compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/sequence-expression.expect.md ModifiedPatch
Files changed
Rendering syntax-highlighted changes…