Reset scopes from AnalyzeFunctions in InferReactiveScopeVariables
Updates InferReactiveScopeVariables to first prune scopes attached during AnalyzeFunctions. This ensures that after this pass the only scopes that exist on identifiers in the outer program are those that the pass explicitly inferred, and not accidentally leftover.
Joe Savona committed
Mar 25, 2024 at 15:31 UTC
48ecaf92d51813a1b1f1bcba1cdcbf48a501f2c6
2 files changed
+47
-55
compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/InferReactiveScopeVariables.ts
+24
-34
@@ -9,7 +9,6 @@ import { Environment } from "../HIR";
9
import {
10
HIRFunction,
11
Identifier,
12
- IdentifierId,
12
Instruction,
13
makeInstructionId,
14
Place,
@@ -17,8 +16,10 @@ import {
16
} from "../HIR/HIR";
17
import {
18
doesPatternContainSpreadElement,
19
+ eachInstructionLValue,
20
eachInstructionOperand,
21
eachPatternOperand,
22
+ eachTerminalOperand,
23
} from "../HIR/visitors";
24
import DisjointSet from "../Utils/DisjointSet";
25
import { assertExhaustive } from "../Utils/utils";
@@ -80,6 +81,27 @@ import { assertExhaustive } from "../Utils/utils";
81
* ```
82
*/
83
export function inferReactiveScopeVariables(fn: HIRFunction): void {
84
+ // First reset any scopes that may have been created from inner functions
85
+ for (const [, block] of fn.body.blocks) {
86
+ for (const phi of block.phis) {
87
+ phi.id.scope = null;
88
+ for (const [, operand] of phi.operands) {
89
+ operand.scope = null;
90
+ }
91
+ }
92
+ for (const instr of block.instructions) {
93
+ for (const lvalue of eachInstructionLValue(instr)) {
94
+ lvalue.identifier.scope = null;
95
+ }
96
+ for (const operand of eachInstructionOperand(instr)) {
97
+ operand.identifier.scope = null;
98
+ }
99
+ }
100
+ for (const operand of eachTerminalOperand(block.terminal)) {
101
+ operand.identifier.scope = null;
102
+ }
103
+ }
104
+
105
/*
106
* Represents the set of reactive scopes as disjoint sets of identifiers
107
* that mutate together.
@@ -189,10 +211,6 @@ export function findDisjointMutableValues(
211
fn: HIRFunction
212
): DisjointSet<Identifier> {
213
const scopeIdentifiers = new DisjointSet<Identifier>();
192
- const declarations: Map<IdentifierId, Place> | null = fn.env.config
193
- .enableForest
194
- ? new Map()
195
- : null;
214
for (const [_, block] of fn.body.blocks) {
215
/*
216
* If a phi is mutated after creation, then we need to alias all of its operands such that they
@@ -221,14 +239,7 @@ export function findDisjointMutableValues(
239
if (range.end > range.start + 1 || mayAllocate(fn.env, instr)) {
240
operands.push(instr.lvalue!.identifier);
241
}
224
- if (instr.value.kind === "DeclareLocal") {
225
- if (declarations !== null) {
226
- declarations.set(
227
- instr.value.lvalue.place.identifier.id,
228
- instr.value.lvalue.place
229
- );
230
- }
231
- } else if (
242
+ if (
243
instr.value.kind === "StoreLocal" ||
244
instr.value.kind === "StoreContext"
245
) {
@@ -244,27 +255,6 @@ export function findDisjointMutableValues(
255
) {
256
operands.push(instr.value.value.identifier);
257
}
247
- if (declarations !== null) {
248
- const declaration = declarations.get(
249
- instr.value.lvalue.place.identifier.id
250
- );
251
- if (declaration !== undefined) {
252
- declaration.identifier.mutableRange.end = makeInstructionId(
253
- Math.max(
254
- declaration.identifier.mutableRange.end,
255
- instr.value.lvalue.place.identifier.mutableRange.end
256
- )
257
- );
258
- instr.value.lvalue.place.identifier.mutableRange.start =
259
- makeInstructionId(
260
- Math.min(
261
- declaration.identifier.mutableRange.start,
262
- instr.value.lvalue.place.identifier.mutableRange.start
263
- )
264
- );
265
- operands.push(declaration.identifier);
266
- }
267
- }
258
} else if (instr.value.kind === "Destructure") {
259
for (const place of eachPatternOperand(instr.value.lvalue.pattern)) {
260
if (
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/repro-mutable-range-shared-inner-outer-function.expect.md
+23
-21
@@ -34,34 +34,36 @@ export const FIXTURE_ENTRYPOINT = {
34
import { unstable_useMemoCache as useMemoCache } from "react"; // @enableAssumeHooksFollowRulesOfReact @enableTransitivelyFreezeFunctionExpressions
35
let cond = true;
36
function Component(props) {
37
- const $ = useMemoCache(1);
37
+ const $ = useMemoCache(2);
38
+ let a;
39
+ let b;
40
let t0;
41
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
40
- t0 = Symbol.for("react.early_return_sentinel");
41
- bb8: {
42
- let a;
43
- let b;
44
- const f = () => {
45
- if (cond) {
46
- a = {};
47
- b = [];
48
- } else {
49
- a = {};
50
- b = [];
51
- }
52
- a.property = true;
53
- b.push(false);
54
- };
55
- t0 = <div onClick={f} />;
56
- break bb8;
57
- }
42
+ t0 = () => {
43
+ if (cond) {
44
+ a = {};
45
+ b = [];
46
+ } else {
47
+ a = {};
48
+ b = [];
49
+ }
50
+
51
+ a.property = true;
52
+ b.push(false);
53
+ };
54
$[0] = t0;
55
} else {
56
t0 = $[0];
57
}
62
- if (t0 !== Symbol.for("react.early_return_sentinel")) {
63
- return t0;
58
+ const f = t0;
59
+ let t1;
60
+ if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
61
+ t1 = <div onClick={f} />;
62
+ $[1] = t1;
63
+ } else {
64
+ t1 = $[1];
65
}
66
+ return t1;
67
}
68
69
export const FIXTURE_ENTRYPOINT = {