@samitouri / QOS-React-2 / commits / 959c6685be

Merge consecutive reactive scopes that invalidate together

This is something we've wanted to do for a while, and which @sophiebits also brought up. The idea is to merge consecutive reactive scopes that will always invalidate together. There are two cases of this: * Both scopes have the exact same inputs * Or the inputs of the second scope are the outputs of the first In both these cases it's pure overhead to keep the scopes separate. In the first case where both scopes have the same inputs, merging allows us to check the inputs once instead of 2+ times. In the second case, we know that the second scope will invalidate when the first does so it's wasteful to recheck the outputs of the first scope for changes. This is already cutting down on memoization quite a bit in the fixtures. Note that there's an additional optimization we can do after merging the second category, which is to remove the first scope's declarations if they were only referenced by the second scope. I'll add that in a follow-up.

Joe Savona committed Sep 27, 2023 at 14:03 UTC 959c6685be0368cb9c545984a1a56d4b70a5cd08
55 files changed +668 -480
compiler/packages/babel-plugin-react-forget/src/Entrypoint/Pipeline.ts
+8
@@ -42,6 +42,7 @@ import {
42 flattenScopesWithHooks,
43 inferReactiveScopeVariables,
44 memoizeFbtOperandsInSameScope,
45 + mergeConsecutiveScopes,
46 mergeOverlappingReactiveScopes,
47 promoteUsedTemporaries,
48 propagateScopeDependencies,
@@ -261,6 +262,13 @@ export function* run(
262 value: reactiveFunction,
263 });
264
265 + mergeConsecutiveScopes(reactiveFunction);
266 + yield log({
267 + kind: "reactive",
268 + name: "MergeConsecutiveScopes",
269 + value: reactiveFunction,
270 + });
271 +
272 if (config?.enableForest) {
273 yield* lowerToForest(reactiveFunction);
274 }
compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/MergeConsecutiveScopes.ts new
+277
@@ -0,0 +1,277 @@
1 +/**
2 + * Copyright (c) Meta Platforms, Inc. and affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + */
7 +
8 +import {
9 + IdentifierId,
10 + InstructionId,
11 + Place,
12 + ReactiveBlock,
13 + ReactiveFunction,
14 + ReactiveScope,
15 + ReactiveScopeBlock,
16 + ReactiveScopeDependency,
17 +} from "../HIR";
18 +import { assertExhaustive } from "../Utils/utils";
19 +import {
20 + ReactiveFunctionTransform,
21 + ReactiveFunctionVisitor,
22 + visitReactiveFunction,
23 +} from "./visitors";
24 +
25 +/**
26 + * The primary goal of this pass is to reduce memoization overhead, specifically:
27 + * - Use fewer memo slots
28 + * - Reduce the number of comparisons and other memoization-related instructions
29 + *
30 + * This is achieved by merging consecutive reactive scopes when the two scopes
31 + * will always invalidate together. The idea is that if two scopes would always
32 + * invalidate together, it's more efficient to group the scopes together to save
33 + * on memoization overhead.
34 + *
35 + * This optimization is necessarily somewhat limited. First, we only merge
36 + * scopes that are in the same (reactive) block, ie we don't merge across
37 + * control-flow or block-scoping boundaries. Second, we can only merge scopes
38 + * so long as any intermediate instructions are safe to memoize — specifically,
39 + * as long as the values created by those instructions are only referenced by
40 + * the second scope and not elsewhere. This is to avoid changing control-flow
41 + * and to avoid increasing the number of scope outputs (which defeats the optimization).
42 + *
43 + * With that in mind we can apply the optimization in two cases. Given a block with
44 + * scope A, some safe-to-memoize instructions I, and scope B, we can merge scopes when:
45 + * - A and B have identical dependencies. This means they will invalidate together, so
46 + * by merging the scopes we can avoid duplicate cache slots and duplicate checks of
47 + * those dependencies.
48 + * - The output of A is the input to B. Any invalidation of A will change its output
49 + * which invalidates B, so we can similarly merge scopes. Note that this optimization
50 + * may not be beneficial if the outupts of A are not guaranteed to change if its input
51 + * changes, but in practice this is generally the case.
52 + *
53 + */
54 +export function mergeConsecutiveScopes(fn: ReactiveFunction): void {
55 + visitReactiveFunction(fn, new Transform(), undefined);
56 +}
57 +
58 +class Transform extends ReactiveFunctionTransform<void> {
59 + override visitBlock(block: ReactiveBlock, state: void): void {
60 + this.traverseBlock(block, state);
61 +
62 + // The current reactive scope which is a candidate for subsequent scopes
63 + // to be merged into
64 + let currentScope: {
65 + // The scope itself
66 + scope: ReactiveScopeBlock;
67 + // the starting index within `block` of this scope (inclusive)
68 + from: number;
69 + // the index within `block` of instructions which are merged into this
70 + // scope (exclusive)
71 + to: number;
72 + // Whether this block has been emitted yet onto `nextInstructions`
73 + merged: boolean;
74 + } | null = null;
75 +
76 + // Tracks the lvalues of instructions which occur between reactive scopes
77 + // We can't merge two scopes if their intervening instructions are needed
78 + // by subsequent code
79 + const lvalues: Set<IdentifierId> = new Set();
80 +
81 + // The updated set of instructions for the block. Stays null until
82 + // we make changes (ie merge scopes)
83 + let nextInstructions: ReactiveBlock | null = null;
84 +
85 + // Called when we find some instruction that cannot be merged into a
86 + // preceding scope, or we otherwise need to reset and not consider
87 + // the previous candidate scope to be mergeable anymore.
88 + function resetCurrentScope(index: number): void {
89 + if (nextInstructions !== null) {
90 + if (currentScope !== null && !currentScope.merged) {
91 + currentScope.merged = true;
92 + nextInstructions.push(block[currentScope.from]!);
93 + }
94 + if (currentScope !== null) {
95 + nextInstructions.push(...block.slice(currentScope.to, index));
96 + }
97 + if (index < block.length) {
98 + nextInstructions.push(block[index]!);
99 + }
100 + }
101 + currentScope = null;
102 + }
103 +
104 + for (let i = 0; i < block.length; i++) {
105 + const instr = block[i]!;
106 + if (instr.kind === "terminal") {
107 + // Don't merge scopes with terminals in between.
108 + // In theory we could allow certain types of terminals,
109 + // such as loops, but for simplicity we just skip all
110 + // cases with terminals
111 + resetCurrentScope(i);
112 + } else if (instr.kind === "instruction") {
113 + switch (instr.instruction.value.kind) {
114 + case "JSXText":
115 + case "Primitive":
116 + case "LoadLocal":
117 + case "PropertyLoad":
118 + case "ComputedLoad": {
119 + // Allow simple instructions between scopes
120 + if (currentScope === null && nextInstructions !== null) {
121 + nextInstructions.push(instr);
122 + } else if (
123 + currentScope !== null &&
124 + instr.instruction.lvalue !== null
125 + ) {
126 + lvalues.add(instr.instruction.lvalue.identifier.id);
127 + }
128 + break;
129 + }
130 + default: {
131 + // skip merging if there are complex intermediate instructions
132 + resetCurrentScope(i);
133 + }
134 + }
135 + } else {
136 + if (
137 + currentScope !== null &&
138 + canMergeScopes(currentScope.scope.scope, instr.scope) &&
139 + // If there are intermediate instructions, we can only merge the scopes
140 + // if those intermediate instructions are all used by the second scope.
141 + // if not, merging them would make those values unavailable to subsequent
142 + // code by moving them inside a different block scope in the output.
143 + usesAllLvalues(instr.instructions, lvalues)
144 + ) {
145 + const intermediateInstructions = block.slice(currentScope.to, i);
146 + currentScope.scope.instructions.push(...intermediateInstructions);
147 + currentScope.scope.instructions.push(...instr.instructions);
148 + for (const [key, value] of instr.scope.declarations) {
149 + currentScope.scope.scope.declarations.set(key, value);
150 + }
151 + if (nextInstructions === null) {
152 + nextInstructions = block.slice(0, currentScope.from);
153 + nextInstructions.push(currentScope.scope);
154 + currentScope.merged = true;
155 + }
156 + currentScope.to = i + 1;
157 + lvalues.clear();
158 + } else {
159 + resetCurrentScope(i - 1); // don't include the current scope
160 + currentScope = { scope: instr, from: i, to: i + 1, merged: false };
161 + lvalues.clear();
162 + }
163 + }
164 + }
165 + if (currentScope !== null && nextInstructions !== null) {
166 + nextInstructions.push(...block.slice(currentScope.to, block.length));
167 + }
168 +
169 + if (nextInstructions !== null) {
170 + block.length = 0;
171 + block.push(...nextInstructions);
172 + }
173 + }
174 +}
175 +
176 +function usesAllLvalues(
177 + block: ReactiveBlock,
178 + lvalues: Set<IdentifierId>
179 +): boolean {
180 + if (lvalues.size === 0) {
181 + return true;
182 + }
183 + const visitor = new OperandVisitor();
184 + visitor.traverseBlock(block, lvalues);
185 + return lvalues.size === 0;
186 +}
187 +
188 +class OperandVisitor extends ReactiveFunctionVisitor<Set<IdentifierId>> {
189 + override visitPlace(
190 + _id: InstructionId,
191 + place: Place,
192 + state: Set<IdentifierId>
193 + ): void {
194 + state.delete(place.identifier.id);
195 + }
196 +
197 + override traverseBlock(block: ReactiveBlock, state: Set<IdentifierId>): void {
198 + for (const instr of block) {
199 + if (state.size === 0) {
200 + return;
201 + }
202 + switch (instr.kind) {
203 + case "instruction": {
204 + this.visitInstruction(instr.instruction, state);
205 + break;
206 + }
207 + case "scope": {
208 + this.visitScope(instr, state);
209 + break;
210 + }
211 + case "terminal": {
212 + this.visitTerminal(instr, state);
213 + break;
214 + }
215 + default: {
216 + assertExhaustive(
217 + instr,
218 + `Unexpected instruction kind '${(instr as any).kind}'`
219 + );
220 + }
221 + }
222 + }
223 + }
224 +}
225 +
226 +function canMergeScopes(a: ReactiveScope, b: ReactiveScope): boolean {
227 + // Don't merge scopes with reassignments
228 + if (a.reassignments.size !== 0 || b.reassignments.size !== 0) {
229 + return false;
230 + }
231 + if (areEqualDependencies(a.dependencies, b.dependencies)) {
232 + return true;
233 + }
234 + if (
235 + areEqualDependencies(
236 + new Set(
237 + [...a.declarations.values()].map((declaration) => ({
238 + identifier: declaration.identifier,
239 + path: [],
240 + }))
241 + ),
242 + b.dependencies
243 + )
244 + ) {
245 + return true;
246 + }
247 + return false;
248 +}
249 +
250 +function areEqualDependencies(
251 + a: Set<ReactiveScopeDependency>,
252 + b: Set<ReactiveScopeDependency>
253 +): boolean {
254 + if (a.size !== b.size) {
255 + return false;
256 + }
257 + for (const aValue of a) {
258 + let found = false;
259 + for (const bValue of b) {
260 + if (
261 + aValue.identifier === bValue.identifier &&
262 + areEqualPaths(aValue.path, bValue.path)
263 + ) {
264 + found = true;
265 + break;
266 + }
267 + }
268 + if (!found) {
269 + return false;
270 + }
271 + }
272 + return true;
273 +}
274 +
275 +function areEqualPaths(a: Array<string>, b: Array<string>): boolean {
276 + return a.length === b.length && a.every((item, ix) => item === b[ix]);
277 +}
compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/index.ts
+1
@@ -17,6 +17,7 @@ export { flattenReactiveLoops } from "./FlattenReactiveLoops";
17 export { flattenScopesWithHooks } from "./FlattenScopesWithHooks";
18 export { inferReactiveScopeVariables } from "./InferReactiveScopeVariables";
19 export { memoizeFbtOperandsInSameScope } from "./MemoizeFbtOperandsInSameScope";
20 +export { mergeConsecutiveScopes } from "./MergeConsecutiveScopes";
21 export { mergeOverlappingReactiveScopes } from "./MergeOverlappingReactiveScopes";
22 export { printReactiveFunction } from "./PrintReactiveFunction";
23 export { promoteUsedTemporaries } from "./PromoteUsedTemporaries";
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/alias-capture-in-method-receiver-and-mutate.expect.md
+3 -6
@@ -34,6 +34,7 @@ function Component() {
34 const $ = useMemoCache(3);
35 let x;
36 let a;
37 + let t0;
38 if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
39 a = makeObject_Primitives();
40
@@ -41,17 +42,13 @@ function Component() {
42 x.push(a);
43
44 mutate(x);
45 + t0 = [x, a];
46 $[0] = x;
47 $[1] = a;
48 + $[2] = t0;
49 } else {
50 x = $[0];
51 a = $[1];
49 - }
50 - let t0;
51 - if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
52 - t0 = [x, a];
53 - $[2] = t0;
54 - } else {
52 t0 = $[2];
53 }
54 return t0;
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/alias-capture-in-method-receiver.expect.md
+4 -6
@@ -30,18 +30,16 @@ function Component() {
30 }
31 const a = t0;
32 let x;
33 + let t1;
34 if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
35 x = [];
36 x.push(a);
36 - $[1] = x;
37 - } else {
38 - x = $[1];
39 - }
40 - let t1;
41 - if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
37 +
38 t1 = [x, a];
39 + $[1] = x;
40 $[2] = t1;
41 } else {
42 + x = $[1];
43 t1 = $[2];
44 }
45 return t1;
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/alias-nested-member-path.expect.md
+3 -6
@@ -34,19 +34,16 @@ function component() {
34 }
35 const z = t0;
36 let y;
37 + let x;
38 if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
39 y = {};
40 y.z = z;
40 - $[1] = y;
41 - } else {
42 - y = $[1];
43 - }
44 - let x;
45 - if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
41 x = {};
42 x.y = y;
43 + $[1] = y;
44 $[2] = x;
45 } else {
46 + y = $[1];
47 x = $[2];
48 }
49 return x;
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/allocating-primitive-as-dep-nested-scope.expect.md
+10 -17
@@ -24,44 +24,37 @@ import { unstable_useMemoCache as useMemoCache } from "react"; // bar(props.b) i
24 // Correctness:
25 // - y depends on either bar(props.b) or bar(props.b) + 1
26 function AllocatingPrimitiveAsDepNested(props) {
27 - const $ = useMemoCache(9);
27 + const $ = useMemoCache(7);
28 const c_0 = $[0] !== props.b;
29 const c_1 = $[1] !== props.a;
30 let x;
31 let y;
32 + let t2;
33 if (c_0 || c_1) {
34 x = {};
35 mutate(x);
36 const t0 = bar(props.b) + 1;
36 - const c_4 = $[4] !== t0;
37 + const c_5 = $[5] !== t0;
38 let t1;
38 - if (c_4) {
39 + if (c_5) {
40 t1 = foo(t0);
40 - $[4] = t0;
41 - $[5] = t1;
41 + $[5] = t0;
42 + $[6] = t1;
43 } else {
43 - t1 = $[5];
44 + t1 = $[6];
45 }
46 y = t1;
47 mutate(x, props.a);
48 + t2 = [x, y];
49 $[0] = props.b;
50 $[1] = props.a;
51 $[2] = x;
52 $[3] = y;
53 + $[4] = t2;
54 } else {
55 x = $[2];
56 y = $[3];
54 - }
55 - const c_6 = $[6] !== x;
56 - const c_7 = $[7] !== y;
57 - let t2;
58 - if (c_6 || c_7) {
59 - t2 = [x, y];
60 - $[6] = x;
61 - $[7] = y;
62 - $[8] = t2;
63 - } else {
64 - t2 = $[8];
57 + t2 = $[4];
58 }
59 return t2;
60 }
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/array-access-assignment.expect.md
+10 -17
@@ -24,46 +24,39 @@ export const FIXTURE_ENTRYPOINT = {
24 ```javascript
25 import { unstable_useMemoCache as useMemoCache } from "react";
26 function foo(a, b, c) {
27 - const $ = useMemoCache(10);
27 + const $ = useMemoCache(8);
28 const c_0 = $[0] !== a;
29 const c_1 = $[1] !== b;
30 const c_2 = $[2] !== c;
31 let x;
32 let z;
33 + let t1;
34 if (c_0 || c_1 || c_2) {
35 x = [a];
35 - const c_5 = $[5] !== b;
36 + const c_6 = $[6] !== b;
37 let t0;
37 - if (c_5) {
38 + if (c_6) {
39 t0 = [null, b];
39 - $[5] = b;
40 - $[6] = t0;
40 + $[6] = b;
41 + $[7] = t0;
42 } else {
42 - t0 = $[6];
43 + t0 = $[7];
44 }
45 const y = t0;
46 z = [[], [], [c]];
47 x[0] = y[1];
48 z[0][0] = x[0];
49 + t1 = [x, z];
50 $[0] = a;
51 $[1] = b;
52 $[2] = c;
53 $[3] = x;
54 $[4] = z;
55 + $[5] = t1;
56 } else {
57 x = $[3];
58 z = $[4];
56 - }
57 - const c_7 = $[7] !== x;
58 - const c_8 = $[8] !== z;
59 - let t1;
60 - if (c_7 || c_8) {
61 - t1 = [x, z];
62 - $[7] = x;
63 - $[8] = z;
64 - $[9] = t1;
65 - } else {
66 - t1 = $[9];
59 + t1 = $[5];
60 }
61 return t1;
62 }
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/array-at-effect.expect.md
+17 -22
@@ -22,45 +22,40 @@ import { unstable_useMemoCache as useMemoCache } from "react"; // arrayInstance.
22 // - read on receiver
23 // - mutate on lvalue
24 function ArrayAtTest(props) {
25 - const $ = useMemoCache(9);
25 + const $ = useMemoCache(8);
26 const c_0 = $[0] !== props.x;
27 let t0;
28 + let t1;
29 if (c_0) {
30 t0 = foo(props.x);
31 + t1 = [t0];
32 $[0] = props.x;
33 $[1] = t0;
34 + $[2] = t1;
35 } else {
36 t0 = $[1];
34 - }
35 - const c_2 = $[2] !== t0;
36 - let t1;
37 - if (c_2) {
38 - t1 = [t0];
39 - $[2] = t0;
40 - $[3] = t1;
41 - } else {
42 - t1 = $[3];
37 + t1 = $[2];
38 }
39 const arr = t1;
45 - const c_4 = $[4] !== props.y;
46 - const c_5 = $[5] !== arr;
40 + const c_3 = $[3] !== props.y;
41 + const c_4 = $[4] !== arr;
42 let t3;
48 - if (c_4 || c_5) {
49 - const c_7 = $[7] !== props.y;
43 + if (c_3 || c_4) {
44 + const c_6 = $[6] !== props.y;
45 let t2;
51 - if (c_7) {
46 + if (c_6) {
47 t2 = bar(props.y);
53 - $[7] = props.y;
54 - $[8] = t2;
48 + $[6] = props.y;
49 + $[7] = t2;
50 } else {
56 - t2 = $[8];
51 + t2 = $[7];
52 }
53 t3 = arr.at(t2);
59 - $[4] = props.y;
60 - $[5] = arr;
61 - $[6] = t3;
54 + $[3] = props.y;
55 + $[4] = arr;
56 + $[5] = t3;
57 } else {
63 - t3 = $[6];
58 + t3 = $[5];
59 }
60 const result = t3;
61 return result;
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/array-at-mutate-after-capture.expect.md
+3 -6
@@ -23,17 +23,14 @@ import { unstable_useMemoCache as useMemoCache } from "react"; // x's mutable ra
23 function Component(props) {
24 const $ = useMemoCache(2);
25 let t0;
26 + let t1;
27 if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
28 t0 = {};
28 - $[0] = t0;
29 - } else {
30 - t0 = $[0];
31 - }
32 - let t1;
33 - if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
29 t1 = [42, t0];
30 + $[0] = t0;
31 $[1] = t1;
32 } else {
33 + t0 = $[0];
34 t1 = $[1];
35 }
36 const x = t1;
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/array-join.expect.md
+3 -6
@@ -18,17 +18,14 @@ import { unstable_useMemoCache as useMemoCache } from "react";
18 function Component(props) {
19 const $ = useMemoCache(8);
20 let t0;
21 + let t1;
22 if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
23 t0 = {};
23 - $[0] = t0;
24 - } else {
25 - t0 = $[0];
26 - }
27 - let t1;
28 - if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
24 t1 = [];
25 + $[0] = t0;
26 $[1] = t1;
27 } else {
28 + t0 = $[0];
29 t1 = $[1];
30 }
31 const c_2 = $[2] !== props.value;
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/assignment-variations-complex-lvalue-array.expect.md
+3 -6
@@ -24,17 +24,14 @@ import { unstable_useMemoCache as useMemoCache } from "react";
24 function foo() {
25 const $ = useMemoCache(2);
26 let t0;
27 + let t1;
28 if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
29 t0 = [1];
29 - $[0] = t0;
30 - } else {
31 - t0 = $[0];
32 - }
33 - let t1;
34 - if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
30 t1 = [t0];
31 + $[0] = t0;
32 $[1] = t1;
33 } else {
34 + t0 = $[0];
35 t1 = $[1];
36 }
37 const a = t1;
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/builtin-jsx-tag-lowered-between-mutations.expect.md
+3 -6
@@ -16,18 +16,15 @@ import { unstable_useMemoCache as useMemoCache } from "react";
16 function Component(props) {
17 const $ = useMemoCache(2);
18 let t0;
19 + let t1;
20 if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
21 const maybeMutable = new MaybeMutable();
22 t0 = maybeMutate(maybeMutable);
22 - $[0] = t0;
23 - } else {
24 - t0 = $[0];
25 - }
26 - let t1;
27 - if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
23 t1 = <div>{t0}</div>;
24 + $[0] = t0;
25 $[1] = t1;
26 } else {
27 + t0 = $[0];
28 t1 = $[1];
29 }
30 return t1;
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/call-with-independently-memoizable-arg.expect.md
+5 -10
@@ -19,28 +19,23 @@ function Component(props) {
19 ```javascript
20 import { unstable_useMemoCache as useMemoCache } from "react";
21 function Component(props) {
22 - const $ = useMemoCache(6);
22 + const $ = useMemoCache(5);
23 const c_0 = $[0] !== props;
24 let t2;
25 if (c_0) {
26 const x = makeFunction(props);
27 const c_2 = $[2] !== props.text;
28 let t0;
29 + let t1;
30 if (c_2) {
31 t0 = <span>{props.text}</span>;
32 + t1 = <div>{t0}</div>;
33 $[2] = props.text;
34 $[3] = t0;
35 + $[4] = t1;
36 } else {
37 t0 = $[3];
35 - }
36 - const c_4 = $[4] !== t0;
37 - let t1;
38 - if (c_4) {
39 - t1 = <div>{t0}</div>;
40 - $[4] = t0;
41 - $[5] = t1;
42 - } else {
43 - t1 = $[5];
38 + t1 = $[4];
39 }
40 t2 = x(t1);
41 $[0] = props;
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/call.expect.md
+3 -6
@@ -25,23 +25,20 @@ function Component(props) {
25 const $ = useMemoCache(3);
26 let a;
27 let b;
28 + let t0;
29 if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
30 a = [];
31 b = {};
32 foo(a, b);
33
34 foo(b);
35 + t0 = <div a={a} b={b} />;
36 $[0] = a;
37 $[1] = b;
38 + $[2] = t0;
39 } else {
40 a = $[0];
41 b = $[1];
39 - }
40 - let t0;
41 - if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
42 - t0 = <div a={a} b={b} />;
43 - $[2] = t0;
44 - } else {
42 t0 = $[2];
43 }
44 return t0;
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/capturing-nested-member-call.expect.md
+5 -10
@@ -23,24 +23,19 @@ export const FIXTURE_ENTRYPOINT = {
23 ```javascript
24 import { unstable_useMemoCache as useMemoCache } from "react";
25 function component(a) {
26 - const $ = useMemoCache(4);
26 + const $ = useMemoCache(3);
27 const c_0 = $[0] !== a;
28 let t0;
29 + let t1;
30 if (c_0) {
31 t0 = { a };
32 + t1 = { a: t0 };
33 $[0] = a;
34 $[1] = t0;
35 + $[2] = t1;
36 } else {
37 t0 = $[1];
35 - }
36 - const c_2 = $[2] !== t0;
37 - let t1;
38 - if (c_2) {
39 - t1 = { a: t0 };
40 - $[2] = t0;
41 - $[3] = t1;
42 - } else {
43 - t1 = $[3];
38 + t1 = $[2];
39 }
40 const z = t1;
41 return z;
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/capturing-nested-member-expr-in-nested-func.expect.md
+10 -15
@@ -25,38 +25,33 @@ export const FIXTURE_ENTRYPOINT = {
25 ```javascript
26 import { unstable_useMemoCache as useMemoCache } from "react";
27 function component(a) {
28 - const $ = useMemoCache(6);
28 + const $ = useMemoCache(5);
29 const c_0 = $[0] !== a;
30 let t0;
31 + let t1;
32 if (c_0) {
33 t0 = { a };
34 + t1 = { a: t0 };
35 $[0] = a;
36 $[1] = t0;
37 + $[2] = t1;
38 } else {
39 t0 = $[1];
37 - }
38 - const c_2 = $[2] !== t0;
39 - let t1;
40 - if (c_2) {
41 - t1 = { a: t0 };
42 - $[2] = t0;
43 - $[3] = t1;
44 - } else {
45 - t1 = $[3];
40 + t1 = $[2];
41 }
42 const z = t1;
48 - const c_4 = $[4] !== z.a.a;
43 + const c_3 = $[3] !== z.a.a;
44 let t2;
50 - if (c_4) {
45 + if (c_3) {
46 t2 = function () {
47 (function () {
48 console.log(z.a.a);
49 })();
50 };
56 - $[4] = z.a.a;
57 - $[5] = t2;
51 + $[3] = z.a.a;
52 + $[4] = t2;
53 } else {
59 - t2 = $[5];
54 + t2 = $[4];
55 }
56 const x = t2;
57 return x;
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/capturing-nested-member-expr.expect.md
+10 -15
@@ -23,36 +23,31 @@ export const FIXTURE_ENTRYPOINT = {
23 ```javascript
24 import { unstable_useMemoCache as useMemoCache } from "react";
25 function component(a) {
26 - const $ = useMemoCache(6);
26 + const $ = useMemoCache(5);
27 const c_0 = $[0] !== a;
28 let t0;
29 + let t1;
30 if (c_0) {
31 t0 = { a };
32 + t1 = { a: t0 };
33 $[0] = a;
34 $[1] = t0;
35 + $[2] = t1;
36 } else {
37 t0 = $[1];
35 - }
36 - const c_2 = $[2] !== t0;
37 - let t1;
38 - if (c_2) {
39 - t1 = { a: t0 };
40 - $[2] = t0;
41 - $[3] = t1;
42 - } else {
43 - t1 = $[3];
38 + t1 = $[2];
39 }
40 const z = t1;
46 - const c_4 = $[4] !== z.a.a;
41 + const c_3 = $[3] !== z.a.a;
42 let t2;
48 - if (c_4) {
43 + if (c_3) {
44 t2 = function () {
45 console.log(z.a.a);
46 };
52 - $[4] = z.a.a;
53 - $[5] = t2;
47 + $[3] = z.a.a;
48 + $[4] = t2;
49 } else {
55 - t2 = $[5];
50 + t2 = $[4];
51 }
52 const x = t2;
53 return x;
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/codegen-emit-make-read-only.expect.md
+7 -11
@@ -23,29 +23,25 @@ import { makeReadOnly } from "react-forget-runtime";
23 import { unstable_useMemoCache as useMemoCache } from "react"; // @enableEmitFreeze true
24
25 function MyComponentName(props) {
26 - const $ = useMemoCache(5);
26 + const $ = useMemoCache(4);
27 const c_0 = $[0] !== props.a;
28 const c_1 = $[1] !== props.b;
29 let x;
30 + let y;
31 if (c_0 || c_1) {
32 x = {};
33 foo(x, props.a);
34 foo(x, props.b);
35 +
36 + y = [];
37 + y.push(x);
38 $[0] = props.a;
39 $[1] = props.b;
40 $[2] = __DEV__ ? makeReadOnly(x, "MyComponentName") : x;
41 + $[3] = __DEV__ ? makeReadOnly(y, "MyComponentName") : y;
42 } else {
43 x = $[2];
39 - }
40 - const c_3 = $[3] !== x;
41 - let y;
42 - if (c_3) {
43 - y = [];
44 - y.push(x);
45 - $[3] = x;
46 - $[4] = __DEV__ ? makeReadOnly(y, "MyComponentName") : y;
47 - } else {
48 - y = $[4];
44 + y = $[3];
45 }
46 return y;
47 }
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/computed-call-evaluation-order.expect.md
+3 -6
@@ -42,21 +42,18 @@ function Component() {
42 }
43 const changeF = t0;
44 let t1;
45 + let x;
46 if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
47 t1 = () => console.log("original");
47 - $[1] = t1;
48 - } else {
49 - t1 = $[1];
50 - }
51 - let x;
52 - if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
48 x = { f: t1 };
49
50 (console.log("A"), x)[(console.log("B"), "f")](
51 (changeF(x), console.log("arg"), 1)
52 );
53 + $[1] = t1;
54 $[2] = x;
55 } else {
56 + t1 = $[1];
57 x = $[2];
58 }
59 return x;
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/constructor.expect.md
+3 -6
@@ -25,22 +25,19 @@ function Component(props) {
25 const $ = useMemoCache(3);
26 let a;
27 let b;
28 + let t0;
29 if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
30 a = [];
31 b = {};
32 new Foo(a, b);
33 new Foo(b);
34 + t0 = <div a={a} b={b} />;
35 $[0] = a;
36 $[1] = b;
37 + $[2] = t0;
38 } else {
39 a = $[0];
40 b = $[1];
38 - }
39 - let t0;
40 - if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
41 - t0 = <div a={a} b={b} />;
42 - $[2] = t0;
43 - } else {
41 t0 = $[2];
42 }
43 return t0;
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/fbt-template-string-same-scope.expect.md
+5 -10
@@ -29,30 +29,25 @@ import { unstable_useMemoCache as useMemoCache } from "react";
29 import fbt from "fbt";
30
31 export function Component(props) {
32 - const $ = useMemoCache(4);
32 + const $ = useMemoCache(3);
33 let count = 0;
34 if (props.items) {
35 count = props.items.length;
36 }
37 const c_0 = $[0] !== count;
38 let t0;
39 + let t1;
40 if (c_0) {
41 t0 = fbt._("for {count} experiences", [fbt._param("count", count)], {
42 hk: "nmYpm",
43 });
44 + t1 = <View>{t0}</View>;
45 $[0] = count;
46 $[1] = t0;
47 + $[2] = t1;
48 } else {
49 t0 = $[1];
47 - }
48 - const c_2 = $[2] !== t0;
49 - let t1;
50 - if (c_2) {
51 - t1 = <View>{t0}</View>;
52 - $[2] = t0;
53 - $[3] = t1;
54 - } else {
55 - t1 = $[3];
50 + t1 = $[2];
51 }
52 return t1;
53 }
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/fbtparam-text-must-use-expression-container.expect.md
+3 -6
@@ -27,17 +27,14 @@ import fbt from "fbt";
27 function Component(props) {
28 const $ = useMemoCache(2);
29 let t0;
30 + let t1;
31 if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
32 t0 = fbt._("{value}%", [fbt._param("value", "0")], { hk: "10F5Cc" });
32 - $[0] = t0;
33 - } else {
34 - t0 = $[0];
35 - }
36 - let t1;
37 - if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
33 t1 = <Foo value={t0} />;
34 + $[0] = t0;
35 $[1] = t1;
36 } else {
37 + t0 = $[0];
38 t1 = $[1];
39 }
40 return t1;
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/fbtparam-with-jsx-element-content.expect.md
+5 -10
@@ -29,12 +29,13 @@ import { unstable_useMemoCache as useMemoCache } from "react";
29 import fbt from "fbt";
30
31 function Component(t39) {
32 - const $ = useMemoCache(6);
32 + const $ = useMemoCache(5);
33 const { name, data, icon } = t39;
34 const c_0 = $[0] !== name;
35 const c_1 = $[1] !== icon;
36 const c_2 = $[2] !== data;
37 let t0;
38 + let t1;
39 if (c_0 || c_1 || c_2) {
40 t0 = fbt._(
41 "{item author}{icon}{=m2}",
@@ -60,21 +61,15 @@ function Component(t39) {
61 ],
62 { hk: "2HLm2j" }
63 );
64 + t1 = <Text type="body4">{t0}</Text>;
65 $[0] = name;
66 $[1] = icon;
67 $[2] = data;
68 $[3] = t0;
69 + $[4] = t1;
70 } else {
71 t0 = $[3];
69 - }
70 - const c_4 = $[4] !== t0;
71 - let t1;
72 - if (c_4) {
73 - t1 = <Text type="body4">{t0}</Text>;
74 - $[4] = t0;
75 - $[5] = t1;
76 - } else {
77 - t1 = $[5];
72 + t1 = $[4];
73 }
74 return t1;
75 }
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/fbtparam-with-jsx-fragment-value.expect.md
+5 -10
@@ -27,26 +27,21 @@ import fbt from "fbt";
27 import { identity } from "shared-runtime";
28
29 function Component(props) {
30 - const $ = useMemoCache(4);
30 + const $ = useMemoCache(3);
31 const c_0 = $[0] !== props.text;
32 let t0;
33 + let t1;
34 if (c_0) {
35 t0 = fbt._("{value}%", [fbt._param("value", <>{identity(props.text)}</>)], {
36 hk: "10F5Cc",
37 });
38 + t1 = <Foo value={t0} />;
39 $[0] = props.text;
40 $[1] = t0;
41 + $[2] = t1;
42 } else {
43 t0 = $[1];
41 - }
42 - const c_2 = $[2] !== t0;
43 - let t1;
44 - if (c_2) {
45 - t1 = <Foo value={t0} />;
46 - $[2] = t0;
47 - $[3] = t1;
48 - } else {
49 - t1 = $[3];
44 + t1 = $[2];
45 }
46 return t1;
47 }
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/for-in-statement.expect.md
+6 -10
@@ -22,27 +22,23 @@ export const FIXTURE_ENTRYPOINT = {
22 ```javascript
23 import { unstable_useMemoCache as useMemoCache } from "react";
24 function Component(props) {
25 - const $ = useMemoCache(4);
25 + const $ = useMemoCache(3);
26 const c_0 = $[0] !== props;
27 let items;
28 + let t0;
29 if (c_0) {
30 items = [];
31 for (const key in props) {
32 items.push(<div key={key}>{key}</div>);
33 }
34 +
35 + t0 = <div>{items}</div>;
36 $[0] = props;
37 $[1] = items;
38 + $[2] = t0;
39 } else {
40 items = $[1];
37 - }
38 - const c_2 = $[2] !== items;
39 - let t0;
40 - if (c_2) {
41 - t0 = <div>{items}</div>;
42 - $[2] = items;
43 - $[3] = t0;
44 - } else {
45 - t0 = $[3];
41 + t0 = $[2];
42 }
43 return t0;
44 }
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/for-of-mutate.expect.md
+4 -6
@@ -30,21 +30,19 @@ import { makeObject_Primitives, mutate, Stringify } from "shared-runtime";
30 function Component(_props) {
31 const $ = useMemoCache(2);
32 let results;
33 + let t0;
34 if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
35 const collection = [makeObject_Primitives()];
36 results = [];
37 for (const item of collection) {
38 results.push(<div key={Stringify(item)}>{Stringify(mutate(item))}</div>);
39 }
39 - $[0] = results;
40 - } else {
41 - results = $[0];
42 - }
43 - let t0;
44 - if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
40 +
41 t0 = <div>{results}</div>;
42 + $[0] = results;
43 $[1] = t0;
44 } else {
45 + results = $[0];
46 t0 = $[1];
47 }
48 return t0;
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/function-declaration-reassign.expect.md
+3 -6
@@ -25,21 +25,18 @@ import { unstable_useMemoCache as useMemoCache } from "react";
25 function component() {
26 const $ = useMemoCache(2);
27 let t0;
28 + let x;
29 if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
30 t0 = function x(a) {
31 a.foo();
32 };
32 - $[0] = t0;
33 - } else {
34 - t0 = $[0];
35 - }
36 - let x;
37 - if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
33 x = t0;
34
35 x = {};
36 + $[0] = t0;
37 $[1] = x;
38 } else {
39 + t0 = $[0];
40 x = $[1];
41 }
42 return x;
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/global-jsx-tag-lowered-between-mutations.expect.md
+3 -6
@@ -28,22 +28,19 @@ function Component(props) {
28 const $ = useMemoCache(3);
29 let T0;
30 let t1;
31 + let t2;
32 if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
33 const maybeMutable = new MaybeMutable();
34
35 T0 = View;
36 t1 = maybeMutate(maybeMutable);
37 + t2 = <T0>{t1}</T0>;
38 $[0] = T0;
39 $[1] = t1;
40 + $[2] = t2;
41 } else {
42 T0 = $[0];
43 t1 = $[1];
41 - }
42 - let t2;
43 - if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
44 - t2 = <T0>{t1}</T0>;
45 - $[2] = t2;
46 - } else {
44 t2 = $[2];
45 }
46 return t2;
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/hoisting-simple-const-declaration.expect.md
+3 -6
@@ -26,20 +26,17 @@ import { unstable_useMemoCache as useMemoCache } from "react";
26 function hoisting() {
27 const $ = useMemoCache(2);
28 let foo;
29 + let t0;
30 if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
31 foo = () => bar + baz;
32
33 const bar = 3;
34 const baz = 2;
34 - $[0] = foo;
35 - } else {
36 - foo = $[0];
37 - }
38 - let t0;
39 - if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
35 t0 = foo();
36 + $[0] = foo;
37 $[1] = t0;
38 } else {
39 + foo = $[0];
40 t0 = $[1];
41 }
42 return t0;
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/inner-memo-value-not-promoted-to-outer-scope-static.expect.md
+26 -35
@@ -29,6 +29,9 @@ function Component(props) {
29 let t4;
30 let T5;
31 let t6;
32 + let t7;
33 + let t8;
34 + let t9;
35 if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
36 const count = new MaybeMutable();
37
@@ -36,39 +39,15 @@ function Component(props) {
39 t6 = "\n ";
40 T2 = View;
41 t3 = "\n ";
39 - if ($[7] === Symbol.for("react.memo_cache_sentinel")) {
42 + if ($[10] === Symbol.for("react.memo_cache_sentinel")) {
43 t0 = <span>Text</span>;
41 - $[7] = t0;
44 + $[10] = t0;
45 } else {
43 - t0 = $[7];
46 + t0 = $[10];
47 }
48 t4 = "\n ";
49 t1 = maybeMutate(count);
47 - $[0] = t1;
48 - $[1] = T2;
49 - $[2] = t3;
50 - $[3] = t0;
51 - $[4] = t4;
52 - $[5] = T5;
53 - $[6] = t6;
54 - } else {
55 - t1 = $[0];
56 - T2 = $[1];
57 - t3 = $[2];
58 - t0 = $[3];
59 - t4 = $[4];
60 - T5 = $[5];
61 - t6 = $[6];
62 - }
63 - let t7;
64 - if ($[8] === Symbol.for("react.memo_cache_sentinel")) {
50 t7 = <span>{t1}</span>;
66 - $[8] = t7;
67 - } else {
68 - t7 = $[8];
69 - }
70 - let t8;
71 - if ($[9] === Symbol.for("react.memo_cache_sentinel")) {
51 t8 = (
52 <T2>
53 {t3}
@@ -77,21 +56,33 @@ function Component(props) {
56 {t7}
57 </T2>
58 );
80 - $[9] = t8;
81 - } else {
82 - t8 = $[9];
83 - }
84 - let t9;
85 - if ($[10] === Symbol.for("react.memo_cache_sentinel")) {
59 t9 = (
60 <T5>
61 {t6}
62 {t8}
63 </T5>
64 );
92 - $[10] = t9;
65 + $[0] = t1;
66 + $[1] = T2;
67 + $[2] = t3;
68 + $[3] = t0;
69 + $[4] = t4;
70 + $[5] = T5;
71 + $[6] = t6;
72 + $[7] = t7;
73 + $[8] = t8;
74 + $[9] = t9;
75 } else {
94 - t9 = $[10];
76 + t1 = $[0];
77 + T2 = $[1];
78 + t3 = $[2];
79 + t0 = $[3];
80 + t4 = $[4];
81 + T5 = $[5];
82 + t6 = $[6];
83 + t7 = $[7];
84 + t8 = $[8];
85 + t9 = $[9];
86 }
87 return t9;
88 }
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/jsx-fragment.expect.md
+3 -6
@@ -28,17 +28,14 @@ import { unstable_useMemoCache as useMemoCache } from "react";
28 function Foo(props) {
29 const $ = useMemoCache(4);
30 let t0;
31 + let t1;
32 if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
33 t0 = <>Text</>;
33 - $[0] = t0;
34 - } else {
35 - t0 = $[0];
36 - }
37 - let t1;
38 - if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
34 t1 = <div>{t0}</div>;
35 + $[0] = t0;
36 $[1] = t1;
37 } else {
38 + t0 = $[0];
39 t1 = $[1];
40 }
41 const c_2 = $[2] !== props.greeting;
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/jsx-member-expression-tag-grouping.expect.md
+3 -6
@@ -17,21 +17,18 @@ function Component(props) {
17 const $ = useMemoCache(3);
18 let T0;
19 let t1;
20 + let t2;
21 if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
22 const maybeMutable = new MaybeMutable();
23 T0 = Foo.Bar;
24 t1 = maybeMutate(maybeMutable);
25 + t2 = <T0>{t1}</T0>;
26 $[0] = T0;
27 $[1] = t1;
28 + $[2] = t2;
29 } else {
30 T0 = $[0];
31 t1 = $[1];
29 - }
30 - let t2;
31 - if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
32 - t2 = <T0>{t1}</T0>;
33 - $[2] = t2;
34 - } else {
32 t2 = $[2];
33 }
34 return t2;
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/jsx-member-expression.expect.md
+3 -6
@@ -19,17 +19,14 @@ import { unstable_useMemoCache as useMemoCache } from "react";
19 function Component(props) {
20 const $ = useMemoCache(2);
21 let t0;
22 + let t1;
23 if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
24 t0 = <Foo.Bar.Baz />;
24 - $[0] = t0;
25 - } else {
26 - t0 = $[0];
27 - }
28 - let t1;
29 - if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
25 t1 = <Sathya.Codes.Forget>{t0}</Sathya.Codes.Forget>;
26 + $[0] = t0;
27 $[1] = t1;
28 } else {
29 + t0 = $[0];
30 t1 = $[1];
31 }
32 return t1;
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/lambda-with-fbt.expect.md
+6 -12
@@ -72,24 +72,18 @@ function Component() {
72 }
73 const buttonLabel = t0;
74 let t1;
75 + let t2;
76 + let t3;
77 if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
78 t1 = buttonLabel();
77 - $[1] = t1;
78 - } else {
79 - t1 = $[1];
80 - }
81 - let t2;
82 - if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
79 t2 = <Button text={t1} />;
84 - $[2] = t2;
85 - } else {
86 - t2 = $[2];
87 - }
88 - let t3;
89 - if ($[3] === Symbol.for("react.memo_cache_sentinel")) {
80 t3 = <View>{t2}</View>;
81 + $[1] = t1;
82 + $[2] = t2;
83 $[3] = t3;
84 } else {
85 + t1 = $[1];
86 + t2 = $[2];
87 t3 = $[3];
88 }
89 return t3;
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/merge-consecutive-scopes.expect.md new
+90
@@ -0,0 +1,90 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +function Component() {
6 + let [state, setState] = useState(0);
7 + return (
8 + <div>
9 + <Title text="Counter" />
10 + <span>{state}</span>
11 + <button data-testid="button" onClick={() => setState(state + 1)}>
12 + increment
13 + </button>
14 + </div>
15 + );
16 +}
17 +
18 +export const FIXTURE_ENTRYPOINT = {
19 + fn: Component,
20 + params: [{ value: 42 }],
21 +};
22 +
23 +```
24 +
25 +## Code
26 +
27 +```javascript
28 +import { unstable_useMemoCache as useMemoCache } from "react";
29 +function Component() {
30 + const $ = useMemoCache(9);
31 + const [state, setState] = useState(0);
32 + let t0;
33 + if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
34 + t0 = <Title text="Counter" />;
35 + $[0] = t0;
36 + } else {
37 + t0 = $[0];
38 + }
39 + const c_1 = $[1] !== state;
40 + let t1;
41 + if (c_1) {
42 + t1 = <span>{state}</span>;
43 + $[1] = state;
44 + $[2] = t1;
45 + } else {
46 + t1 = $[2];
47 + }
48 + const c_3 = $[3] !== state;
49 + let t2;
50 + let t3;
51 + if (c_3) {
52 + t2 = () => setState(state + 1);
53 + t3 = (
54 + <button data-testid="button" onClick={t2}>
55 + increment
56 + </button>
57 + );
58 + $[3] = state;
59 + $[4] = t2;
60 + $[5] = t3;
61 + } else {
62 + t2 = $[4];
63 + t3 = $[5];
64 + }
65 + const c_6 = $[6] !== t1;
66 + const c_7 = $[7] !== t3;
67 + let t4;
68 + if (c_6 || c_7) {
69 + t4 = (
70 + <div>
71 + {t0}
72 + {t1}
73 + {t3}
74 + </div>
75 + );
76 + $[6] = t1;
77 + $[7] = t3;
78 + $[8] = t4;
79 + } else {
80 + t4 = $[8];
81 + }
82 + return t4;
83 +}
84 +export const FIXTURE_ENTRYPOINT = {
85 + fn: Component,
86 + params: [{ value: 42 }],
87 +};
88 +
89 +```
90 +
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/merge-consecutive-scopes.js new
+17
@@ -0,0 +1,17 @@
1 +function Component() {
2 + let [state, setState] = useState(0);
3 + return (
4 + <div>
5 + <Title text="Counter" />
6 + <span>{state}</span>
7 + <button data-testid="button" onClick={() => setState(state + 1)}>
8 + increment
9 + </button>
10 + </div>
11 + );
12 +}
13 +
14 +export const FIXTURE_ENTRYPOINT = {
15 + fn: Component,
16 + params: [{ value: 42 }],
17 +};
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/multi-arrow-expr-export-default-gating-test.expect.md
+3 -6
@@ -46,22 +46,19 @@ export default Renderer = isForgetEnabled_Fixtures()
46 t0 = $[0];
47 }
48 let t1;
49 + let t2;
50 if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
51 t1 = <ErrorView />;
51 - $[1] = t1;
52 - } else {
53 - t1 = $[1];
54 - }
55 - let t2;
56 - if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
52 t2 = (
53 <Foo>
54 {t0}
55 {t1}
56 </Foo>
57 );
58 + $[1] = t1;
59 $[2] = t2;
60 } else {
61 + t1 = $[1];
62 t2 = $[2];
63 }
64 return t2;
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/multi-arrow-expr-export-gating-test.expect.md
+3 -6
@@ -46,22 +46,19 @@ export const Renderer = isForgetEnabled_Fixtures()
46 t0 = $[0];
47 }
48 let t1;
49 + let t2;
50 if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
51 t1 = <ErrorView />;
51 - $[1] = t1;
52 - } else {
53 - t1 = $[1];
54 - }
55 - let t2;
56 - if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
52 t2 = (
53 <Foo>
54 {t0}
55 {t1}
56 </Foo>
57 );
58 + $[1] = t1;
59 $[2] = t2;
60 } else {
61 + t1 = $[1];
62 t2 = $[2];
63 }
64 return t2;
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/multi-arrow-expr-gating-test.expect.md
+3 -6
@@ -48,22 +48,19 @@ const Renderer = isForgetEnabled_Fixtures()
48 t0 = $[0];
49 }
50 let t1;
51 + let t2;
52 if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
53 t1 = <ErrorView />;
53 - $[1] = t1;
54 - } else {
55 - t1 = $[1];
56 - }
57 - let t2;
58 - if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
54 t2 = (
55 <Foo>
56 {t0}
57 {t1}
58 </Foo>
59 );
60 + $[1] = t1;
61 $[2] = t2;
62 } else {
63 + t1 = $[1];
64 t2 = $[2];
65 }
66 return t2;
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/mutable-lifetime-loops.expect.md
+3 -6
@@ -81,6 +81,7 @@ function testFunction(props) {
81 let b;
82 let c;
83 let d;
84 + let t0;
85 if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
86 a = {};
87 b = {};
@@ -107,21 +108,17 @@ function testFunction(props) {
108 }
109
110 mutate(d, null);
111 + t0 = { a, b, c, d };
112 $[0] = a;
113 $[1] = b;
114 $[2] = c;
115 $[3] = d;
116 + $[4] = t0;
117 } else {
118 a = $[0];
119 b = $[1];
120 c = $[2];
121 d = $[3];
119 - }
120 - let t0;
121 - if ($[4] === Symbol.for("react.memo_cache_sentinel")) {
122 - t0 = { a, b, c, d };
123 - $[4] = t0;
124 - } else {
122 t0 = $[4];
123 }
124 return t0;
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/nested-function-with-param-as-captured-dep.expect.md
+3 -6
@@ -26,6 +26,7 @@ import { unstable_useMemoCache as useMemoCache } from "react";
26 function Foo() {
27 const $ = useMemoCache(2);
28 let t0;
29 + let t1;
30 if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
31 t0 = function t() {
32 return function a(t25) {
@@ -33,15 +34,11 @@ function Foo() {
34 return x_0;
35 };
36 };
36 - $[0] = t0;
37 - } else {
38 - t0 = $[0];
39 - }
40 - let t1;
41 - if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
37 t1 = t0();
38 + $[0] = t0;
39 $[1] = t1;
40 } else {
41 + t0 = $[0];
42 t1 = $[1];
43 }
44 return t1;
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/phi-type-inference-array-push.expect.md
+6 -12
@@ -32,10 +32,11 @@ export const FIXTURE_ENTRYPOINT = {
32 ```javascript
33 import { unstable_useMemoCache as useMemoCache } from "react"; // @debug
34 function Component(props) {
35 - const $ = useMemoCache(6);
35 + const $ = useMemoCache(4);
36 const c_0 = $[0] !== props;
37 let x;
38 let y;
39 + let t0;
40 if (c_0) {
41 x = {};
42 y = undefined;
@@ -46,23 +47,16 @@ function Component(props) {
47 }
48
49 y.push(x);
50 +
51 + t0 = [x, y];
52 $[0] = props;
53 $[1] = x;
54 $[2] = y;
55 + $[3] = t0;
56 } else {
57 x = $[1];
58 y = $[2];
55 - }
56 - const c_3 = $[3] !== x;
57 - const c_4 = $[4] !== y;
58 - let t0;
59 - if (c_3 || c_4) {
60 - t0 = [x, y];
61 - $[3] = x;
62 - $[4] = y;
63 - $[5] = t0;
64 - } else {
65 - t0 = $[5];
59 + t0 = $[3];
60 }
61 return t0;
62 }
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/primitive-as-dep-nested-scope.expect.md
+10 -17
@@ -26,44 +26,37 @@ import { unstable_useMemoCache as useMemoCache } from "react"; // props.b + 1 is
26 // Correctness:
27 // y depends on either props.b or props.b + 1
28 function PrimitiveAsDepNested(props) {
29 - const $ = useMemoCache(9);
29 + const $ = useMemoCache(7);
30 const c_0 = $[0] !== props.b;
31 const c_1 = $[1] !== props.a;
32 let x;
33 let y;
34 + let t2;
35 if (c_0 || c_1) {
36 x = {};
37 mutate(x);
38 const t0 = props.b + 1;
38 - const c_4 = $[4] !== t0;
39 + const c_5 = $[5] !== t0;
40 let t1;
40 - if (c_4) {
41 + if (c_5) {
42 t1 = foo(t0);
42 - $[4] = t0;
43 - $[5] = t1;
43 + $[5] = t0;
44 + $[6] = t1;
45 } else {
45 - t1 = $[5];
46 + t1 = $[6];
47 }
48 y = t1;
49 mutate(x, props.a);
50 + t2 = [x, y];
51 $[0] = props.b;
52 $[1] = props.a;
53 $[2] = x;
54 $[3] = y;
55 + $[4] = t2;
56 } else {
57 x = $[2];
58 y = $[3];
56 - }
57 - const c_6 = $[6] !== x;
58 - const c_7 = $[7] !== y;
59 - let t2;
60 - if (c_6 || c_7) {
61 - t2 = [x, y];
62 - $[6] = x;
63 - $[7] = y;
64 - $[8] = t2;
65 - } else {
66 - t2 = $[8];
59 + t2 = $[4];
60 }
61 return t2;
62 }
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/property-call-evaluation-order.expect.md
+3 -6
@@ -42,19 +42,16 @@ function Component() {
42 }
43 const changeF = t0;
44 let t1;
45 + let x;
46 if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
47 t1 = () => console.log("original");
47 - $[1] = t1;
48 - } else {
49 - t1 = $[1];
50 - }
51 - let x;
52 - if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
48 x = { f: t1 };
49
50 (console.log("A"), x).f((changeF(x), console.log("arg"), 1));
51 + $[1] = t1;
52 $[2] = x;
53 } else {
54 + t1 = $[1];
55 x = $[2];
56 }
57 return x;
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/reactive-scopes.expect.md
+6 -10
@@ -26,10 +26,11 @@ export const FIXTURE_ENTRYPOINT = {
26 ```javascript
27 import { unstable_useMemoCache as useMemoCache } from "react";
28 function f(a, b) {
29 - const $ = useMemoCache(5);
29 + const $ = useMemoCache(4);
30 const c_0 = $[0] !== a.length;
31 const c_1 = $[1] !== b;
32 let x;
33 + let t0;
34 if (c_0 || c_1) {
35 x = [];
36 if (a.length === 1) {
@@ -37,20 +38,15 @@ function f(a, b) {
38 x.push(b);
39 }
40 }
41 +
42 + t0 = <div>{x}</div>;
43 $[0] = a.length;
44 $[1] = b;
45 $[2] = x;
46 + $[3] = t0;
47 } else {
48 x = $[2];
45 - }
46 - const c_3 = $[3] !== x;
47 - let t0;
48 - if (c_3) {
49 - t0 = <div>{x}</div>;
50 - $[3] = x;
51 - $[4] = t0;
52 - } else {
53 - t0 = $[4];
49 + t0 = $[3];
50 }
51 return t0;
52 }
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/reactivity-analysis-interleaved-reactivity.expect.md
+11 -15
@@ -35,37 +35,33 @@ export const FIXTURE_ENTRYPOINT = {
35 ```javascript
36 import { unstable_useMemoCache as useMemoCache } from "react";
37 function Component(props) {
38 - const $ = useMemoCache(6);
38 + const $ = useMemoCache(5);
39 const c_0 = $[0] !== props.b;
40 let a;
41 + let t0;
42 if (c_0) {
43 a = {};
44 const b = [];
45 b.push(props.b);
46 a.a = null;
47 +
48 + t0 = [a];
49 $[0] = props.b;
50 $[1] = a;
51 + $[2] = t0;
52 } else {
53 a = $[1];
50 - }
51 - const c_2 = $[2] !== a;
52 - let t0;
53 - if (c_2) {
54 - t0 = [a];
55 - $[2] = a;
56 - $[3] = t0;
57 - } else {
58 - t0 = $[3];
54 + t0 = $[2];
55 }
56 const c = t0;
61 - const c_4 = $[4] !== a;
57 + const c_3 = $[3] !== a;
58 let t1;
63 - if (c_4) {
59 + if (c_3) {
60 t1 = [c, a];
65 - $[4] = a;
66 - $[5] = t1;
61 + $[3] = a;
62 + $[4] = t1;
63 } else {
68 - t1 = $[5];
64 + t1 = $[4];
65 }
66 return t1;
67 }
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/reduce-reactive-deps-join-uncond-scopes-cond-deps.expect.md
+10 -17
@@ -52,42 +52,35 @@ import { unstable_useMemoCache as useMemoCache } from "react"; // This tests an
52 // }
53
54 function TestJoinCondDepsInUncondScopes(props) {
55 - const $ = useMemoCache(8);
55 + const $ = useMemoCache(6);
56 const c_0 = $[0] !== props.a.b;
57 let x;
58 let y;
59 + let t0;
60 if (c_0) {
61 y = {};
61 - const c_3 = $[3] !== props;
62 - if (c_3) {
62 + const c_4 = $[4] !== props;
63 + if (c_4) {
64 x = {};
65 if (foo) {
66 mutate1(x, props.a.b);
67 }
67 - $[3] = props;
68 - $[4] = x;
68 + $[4] = props;
69 + $[5] = x;
70 } else {
70 - x = $[4];
71 + x = $[5];
72 }
73
74 mutate2(y, props.a.b);
75 + t0 = [x, y];
76 $[0] = props.a.b;
77 $[1] = x;
78 $[2] = y;
79 + $[3] = t0;
80 } else {
81 x = $[1];
82 y = $[2];
80 - }
81 - const c_5 = $[5] !== x;
82 - const c_6 = $[6] !== y;
83 - let t0;
84 - if (c_5 || c_6) {
85 - t0 = [x, y];
86 - $[5] = x;
87 - $[6] = y;
88 - $[7] = t0;
89 - } else {
90 - t0 = $[7];
83 + t0 = $[3];
84 }
85 return t0;
86 }
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/ssa-call-jsx-2.expect.md
+3 -6
@@ -28,6 +28,7 @@ function Component(props) {
28 const $ = useMemoCache(3);
29 let a;
30 let b;
31 + let t0;
32 if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
33 a = [];
34 b = {};
@@ -36,17 +37,13 @@ function Component(props) {
37 }
38
39 foo(a, b);
40 + t0 = <div a={a} b={b} />;
41 $[0] = a;
42 $[1] = b;
43 + $[2] = t0;
44 } else {
45 a = $[0];
46 b = $[1];
44 - }
45 - let t0;
46 - if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
47 - t0 = <div a={a} b={b} />;
48 - $[2] = t0;
49 - } else {
47 t0 = $[2];
48 }
49 return t0;
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/ssa-call-jsx.expect.md
+3 -6
@@ -25,23 +25,20 @@ function Component(props) {
25 const $ = useMemoCache(3);
26 let a;
27 let b;
28 + let t0;
29 if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
30 a = [];
31 b = {};
32 foo(a, b);
33
34 foo(a, b);
35 + t0 = <div a={a} b={b} />;
36 $[0] = a;
37 $[1] = b;
38 + $[2] = t0;
39 } else {
40 a = $[0];
41 b = $[1];
39 - }
40 - let t0;
41 - if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
42 - t0 = <div a={a} b={b} />;
43 - $[2] = t0;
44 - } else {
42 t0 = $[2];
43 }
44 return t0;
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/temporary-accessed-outside-scope.expect.md
+5 -12
@@ -15,32 +15,25 @@ function Component(props) {
15 ```javascript
16 import { unstable_useMemoCache as useMemoCache } from "react";
17 function Component(props) {
18 - const $ = useMemoCache(6);
18 + const $ = useMemoCache(4);
19 const c_0 = $[0] !== props;
20 let t0;
21 let t1;
22 + let t2;
23 if (c_0) {
24 const maybeMutable = new MaybeMutable();
25 const x = props;
26 t0 = x;
27 t1 = maybeMutate(maybeMutable);
28 + t2 = [t0, t1];
29 $[0] = props;
30 $[1] = t0;
31 $[2] = t1;
32 + $[3] = t2;
33 } else {
34 t0 = $[1];
35 t1 = $[2];
33 - }
34 - const c_3 = $[3] !== t0;
35 - const c_4 = $[4] !== t1;
36 - let t2;
37 - if (c_3 || c_4) {
38 - t2 = [t0, t1];
39 - $[3] = t0;
40 - $[4] = t1;
41 - $[5] = t2;
42 - } else {
43 - t2 = $[5];
36 + t2 = $[3];
37 }
38 return t2;
39 }
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/temporary-property-load-accessed-outside-scope.expect.md
+5 -12
@@ -15,32 +15,25 @@ function Component(props) {
15 ```javascript
16 import { unstable_useMemoCache as useMemoCache } from "react";
17 function Component(props) {
18 - const $ = useMemoCache(6);
18 + const $ = useMemoCache(4);
19 const c_0 = $[0] !== props.value;
20 let t0;
21 let t1;
22 + let t2;
23 if (c_0) {
24 const maybeMutable = new MaybeMutable();
25 const x = props.value;
26 t0 = x;
27 t1 = maybeMutate(maybeMutable);
28 + t2 = [t0, t1];
29 $[0] = props.value;
30 $[1] = t0;
31 $[2] = t1;
32 + $[3] = t2;
33 } else {
34 t0 = $[1];
35 t1 = $[2];
33 - }
34 - const c_3 = $[3] !== t0;
35 - const c_4 = $[4] !== t1;
36 - let t2;
37 - if (c_3 || c_4) {
38 - t2 = [t0, t1];
39 - $[3] = t0;
40 - $[4] = t1;
41 - $[5] = t2;
42 - } else {
43 - t2 = $[5];
36 + t2 = $[3];
37 }
38 return t2;
39 }
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/type-test-field-load-binary-op.expect.md
+3 -6
@@ -30,17 +30,14 @@ function component() {
30 t0 = $[0];
31 }
32 let t1;
33 + let t2;
34 if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
35 t1 = makeSomePrimitive();
35 - $[1] = t1;
36 - } else {
37 - t1 = $[1];
38 - }
39 - let t2;
40 - if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
36 t2 = { u: t0, v: t1 };
37 + $[1] = t1;
38 $[2] = t2;
39 } else {
40 + t1 = $[1];
41 t2 = $[2];
42 }
43 const x = t2;
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/uninitialized-declaration-in-reactive-scope.expect.md
+3 -6
@@ -19,21 +19,18 @@ function Component(props) {
19 const $ = useMemoCache(3);
20 let y;
21 let x;
22 + let t0;
23 if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
24 x = mutate();
25
26 foo(x);
27 + t0 = [y, x];
28 $[0] = y;
29 $[1] = x;
30 + $[2] = t0;
31 } else {
32 y = $[0];
33 x = $[1];
31 - }
32 - let t0;
33 - if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
34 - t0 = [y, x];
35 - $[2] = t0;
36 - } else {
34 t0 = $[2];
35 }
36 return t0;
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/useEffect-arg-memoized.expect.md
+9 -13
@@ -26,7 +26,7 @@ function Component(props) {
26 ```javascript
27 import { unstable_useMemoCache as useMemoCache } from "react";
28 function Component(props) {
29 - const $ = useMemoCache(7);
29 + const $ = useMemoCache(6);
30 const dispatch = useDispatch();
31 useFreeze(dispatch);
32 const c_0 = $[0] !== dispatch;
@@ -43,31 +43,27 @@ function Component(props) {
43 const onUpdate = t0;
44 const c_2 = $[2] !== onUpdate;
45 let t1;
46 + let t2;
47 if (c_2) {
48 t1 = () => {
49 onUpdate();
50 };
51 + t2 = [onUpdate];
52 $[2] = onUpdate;
53 $[3] = t1;
54 + $[4] = t2;
55 } else {
56 t1 = $[3];
57 + t2 = $[4];
58 }
55 - const c_4 = $[4] !== onUpdate;
56 - let t2;
57 - if (c_4) {
58 - t2 = [onUpdate];
59 - $[4] = onUpdate;
60 - $[5] = t2;
61 - } else {
62 - t2 = $[5];
63 - }
59 + useEffect(t1, t2);
60 useEffect(t1, t2);
61 let t3;
66 - if ($[6] === Symbol.for("react.memo_cache_sentinel")) {
62 + if ($[5] === Symbol.for("react.memo_cache_sentinel")) {
63 t3 = <div />;
68 - $[6] = t3;
64 + $[5] = t3;
65 } else {
70 - t3 = $[6];
66 + t3 = $[5];
67 }
68 return t3;
69 }