@samitouri / QOS-React-2 / commits / 241a615732

[babel][contextvar] Patch context identifier babel logic; only use referenced identifiers

[babel][contextvar] Patch context identifier babel logic; only use referenced identifiers --- A few fixes for finding context identifiers: Previously, we counted every babel identifier as a reference. This is problematic because babel counts every string symbol as an identifier. ```js print(x); // x is an identifier as expected obj.x // x is.. also an identifier here {x: 2} // x is also an identifier here ``` This PR adds a check for `isReferencedIdentifier`. Note that only non-lval references pass this check ```js print(x); // isReferencedIdentifier(x) -> true obj.x // isReferencedIdentifier(x) -> false {x: 2} // isReferencedIdentifier(x) -> false x = 2 // isReferencedIdentifier(x) -> false ``` Which brings us to change #2. Previously, we counted assignments as references due to the identifier visiting + checking logic. The logic was roughly the following (from #1691) ```js contextVars = intersection(reassigned, referencedByInnerFn); ``` Now that assignments (lvals) and references (rvals) are tracked separately, the equivalent logic is this. Note that assignment to a context variable does not need to be modeled as a read (`console.log(x = 5)` always will evaluates and prints 5, regardless of the previous value of x). ``` contextVars = union(reassignedByInnerFn, intersection(reassigned, referencedByInnerFn)) ``` --- Note that variables that are never read do not need to be modeled as context variables, but this is unlikely to be a common pattern. ```js function fn() { let x = 2; const inner = () => { x = 3; } } ```

Mofei Zhang committed Jan 18, 2024 at 18:29 UTC 241a615732a71e8da90e17812d855e4c40f0d3b9
26 files changed +656 -73
compiler/packages/babel-plugin-react-forget/src/HIR/FindContextIdentifiers.ts
+65 -23
@@ -8,9 +8,20 @@
8 import type { NodePath } from "@babel/traverse";
9 import type * as t from "@babel/types";
10 import { CompilerError } from "../CompilerError";
11 -import { Set_union } from "../Utils/utils";
11 +import { getOrInsertDefault } from "../Utils/utils";
12 import { GeneratedSource } from "./HIR";
13
14 +type IdentifierInfo = {
15 + reassigned: boolean;
16 + reassignedByInnerFn: boolean;
17 + referencedByInnerFn: boolean;
18 +};
19 +const DEFAULT_IDENTIFIER_INFO: IdentifierInfo = {
20 + reassigned: false,
21 + reassignedByInnerFn: false,
22 + referencedByInnerFn: false,
23 +};
24 +
25 type BabelFunction =
26 | NodePath<t.FunctionDeclaration>
27 | NodePath<t.FunctionExpression>
@@ -18,8 +29,7 @@ type BabelFunction =
29 | NodePath<t.ObjectMethod>;
30 type FindContextIdentifierState = {
31 currentFn: Array<BabelFunction>;
21 - reassigned: Set<t.Identifier>;
22 - referenced: Set<t.Identifier>;
32 + identifiers: Map<t.Identifier, IdentifierInfo>;
33 };
34
35 const withFunctionScope = {
@@ -39,8 +49,7 @@ export function findContextIdentifiers(
49 ): Set<t.Identifier> {
50 const state: FindContextIdentifierState = {
51 currentFn: [],
42 - reassigned: new Set(),
43 - referenced: new Set(),
52 + identifiers: new Map(),
53 };
54
55 func.traverse<FindContextIdentifierState>(
@@ -54,38 +63,59 @@ export function findContextIdentifiers(
63 state: FindContextIdentifierState
64 ): void {
65 const left = path.get("left");
57 - handleAssignment(state.reassigned, left);
66 + const currentFn = state.currentFn.at(-1) ?? null;
67 + handleAssignment(currentFn, state.identifiers, left);
68 },
69 Identifier(
70 path: NodePath<t.Identifier>,
71 state: FindContextIdentifierState
72 ): void {
63 - const currentFn = state.currentFn.at(-1);
64 - if (currentFn !== undefined)
65 - handleIdentifier(currentFn, state.referenced, path);
73 + const currentFn = state.currentFn.at(-1) ?? null;
74 + if (path.isReferencedIdentifier()) {
75 + handleIdentifier(currentFn, state.identifiers, path);
76 + }
77 },
78 },
79 state
80 );
70 - return Set_union(state.reassigned, state.referenced);
81 +
82 + const result = new Set<t.Identifier>();
83 + for (const [id, info] of state.identifiers.entries()) {
84 + if (info.reassignedByInnerFn) {
85 + result.add(id);
86 + } else if (info.reassigned && info.referencedByInnerFn) {
87 + result.add(id);
88 + }
89 + }
90 + return result;
91 }
92
93 function handleIdentifier(
74 - currentFn: BabelFunction,
75 - referenced: Set<t.Identifier>,
94 + currentFn: BabelFunction | null,
95 + identifiers: Map<t.Identifier, IdentifierInfo>,
96 path: NodePath<t.Identifier>
97 ): void {
98 const name = path.node.name;
99 const binding = path.scope.getBinding(name);
80 - const bindingAboveLambdaScope = currentFn.scope.parent.getBinding(name);
100 + if (binding == null) {
101 + return;
102 + }
103 + const identifier = getOrInsertDefault(identifiers, binding.identifier, {
104 + ...DEFAULT_IDENTIFIER_INFO,
105 + });
106
82 - if (binding != null && binding === bindingAboveLambdaScope) {
83 - referenced.add(binding.identifier);
107 + if (currentFn != null) {
108 + const bindingAboveLambdaScope = currentFn.scope.parent.getBinding(name);
109 +
110 + if (binding === bindingAboveLambdaScope) {
111 + identifier.referencedByInnerFn = true;
112 + }
113 }
114 }
115
116 function handleAssignment(
88 - reassigned: Set<t.Identifier>,
117 + currentFn: BabelFunction | null,
118 + identifiers: Map<t.Identifier, IdentifierInfo>,
119 lvalPath: NodePath<t.LVal>
120 ): void {
121 /*
@@ -98,8 +128,20 @@ function handleAssignment(
128 const path = lvalPath as NodePath<t.Identifier>;
129 const name = path.node.name;
130 const binding = path.scope.getBinding(name);
101 - if (binding != null) {
102 - reassigned.add(binding.identifier);
131 + if (binding == null) {
132 + break;
133 + }
134 + const state = getOrInsertDefault(identifiers, binding.identifier, {
135 + ...DEFAULT_IDENTIFIER_INFO,
136 + });
137 + state.reassigned = true;
138 +
139 + if (currentFn != null) {
140 + const bindingAboveLambdaScope = currentFn.scope.parent.getBinding(name);
141 +
142 + if (binding === bindingAboveLambdaScope) {
143 + state.reassignedByInnerFn = true;
144 + }
145 }
146 break;
147 }
@@ -107,7 +149,7 @@ function handleAssignment(
149 const path = lvalPath as NodePath<t.ArrayPattern>;
150 for (const element of path.get("elements")) {
151 if (nonNull(element)) {
110 - handleAssignment(reassigned, element);
152 + handleAssignment(currentFn, identifiers, element);
153 }
154 }
155 break;
@@ -123,7 +165,7 @@ function handleAssignment(
165 loc: valuePath.node.loc ?? GeneratedSource,
166 suggestions: null,
167 });
126 - handleAssignment(reassigned, valuePath);
168 + handleAssignment(currentFn, identifiers, valuePath);
169 } else {
170 CompilerError.invariant(property.isRestElement(), {
171 reason: `[FindContextIdentifiers] Invalid assumptions for babel types.`,
@@ -131,7 +173,7 @@ function handleAssignment(
173 loc: property.node.loc ?? GeneratedSource,
174 suggestions: null,
175 });
134 - handleAssignment(reassigned, property);
176 + handleAssignment(currentFn, identifiers, property);
177 }
178 }
179 break;
@@ -139,12 +181,12 @@ function handleAssignment(
181 case "AssignmentPattern": {
182 const path = lvalPath as NodePath<t.AssignmentPattern>;
183 const left = path.get("left");
142 - handleAssignment(reassigned, left);
184 + handleAssignment(currentFn, identifiers, left);
185 break;
186 }
187 case "RestElement": {
188 const path = lvalPath as NodePath<t.RestElement>;
147 - handleAssignment(reassigned, path.get("argument"));
189 + handleAssignment(currentFn, identifiers, path.get("argument"));
190 break;
191 }
192 case "MemberExpression": {
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-3.expect.md
+11 -5
@@ -17,8 +17,10 @@ function bar(a, b) {
17
18 export const FIXTURE_ENTRYPOINT = {
19 fn: bar,
20 - params: ["TodoAdd"],
21 - isComponent: "TodoAdd",
20 + params: [
21 + [1, 2],
22 + [2, 3],
23 + ],
24 };
25
26 ```
@@ -52,9 +54,13 @@ function bar(a, b) {
54
55 export const FIXTURE_ENTRYPOINT = {
56 fn: bar,
55 - params: ["TodoAdd"],
56 - isComponent: "TodoAdd",
57 + params: [
58 + [1, 2],
59 + [2, 3],
60 + ],
61 };
62
63 ```
60 -
\ No newline at end of file
64 +
65 +### Eval output
66 +(kind: ok) 2
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-3.js
+4 -2
@@ -13,6 +13,8 @@ function bar(a, b) {
13
14 export const FIXTURE_ENTRYPOINT = {
15 fn: bar,
16 - params: ["TodoAdd"],
17 - isComponent: "TodoAdd",
16 + params: [
17 + [1, 2],
18 + [2, 3],
19 + ],
20 };
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/chained-assignment-context-variable.expect.md
+19 -3
@@ -2,22 +2,31 @@
2 ## Input
3
4 ```javascript
5 +import { makeArray } from "shared-runtime";
6 +
7 function Component() {
8 let x,
9 y = (x = {});
10 const foo = () => {
9 - x = getObject();
11 + x = makeArray();
12 };
13 foo();
14 return [y, x];
15 }
16
17 +export const FIXTURE_ENTRYPOINT = {
18 + fn: Component,
19 + params: [{}],
20 +};
21 +
22 ```
23
24 ## Code
25
26 ```javascript
27 import { unstable_useMemoCache as useMemoCache } from "react";
28 +import { makeArray } from "shared-runtime";
29 +
30 function Component() {
31 const $ = useMemoCache(3);
32 let x;
@@ -26,7 +35,7 @@ function Component() {
35 y = x = {};
36
37 const foo = () => {
29 - x = getObject();
38 + x = makeArray();
39 };
40
41 foo();
@@ -46,5 +55,12 @@ function Component() {
55 return t0;
56 }
57
58 +export const FIXTURE_ENTRYPOINT = {
59 + fn: Component,
60 + params: [{}],
61 +};
62 +
63 ```
50 -
\ No newline at end of file
64 +
65 +### Eval output
66 +(kind: ok) [{},[]]
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/chained-assignment-context-variable.js
+8 -1
@@ -1,9 +1,16 @@
1 +import { makeArray } from "shared-runtime";
2 +
3 function Component() {
4 let x,
5 y = (x = {});
6 const foo = () => {
5 - x = getObject();
7 + x = makeArray();
8 };
9 foo();
10 return [y, x];
11 }
12 +
13 +export const FIXTURE_ENTRYPOINT = {
14 + fn: Component,
15 + params: [{}],
16 +};
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/constant-prop-colliding-identifier.expect.md new
+44
@@ -0,0 +1,44 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +import { invoke } from "shared-runtime";
6 +
7 +function Component() {
8 + let x = 2;
9 + const fn = () => {
10 + return { x: "value" };
11 + };
12 + invoke(fn);
13 + x = 3;
14 + return x;
15 +}
16 +
17 +export const FIXTURE_ENTRYPOINT = {
18 + fn: Component,
19 + params: [{}],
20 +};
21 +
22 +```
23 +
24 +## Code
25 +
26 +```javascript
27 +import { invoke } from "shared-runtime";
28 +
29 +function Component() {
30 + const fn = () => ({ x: "value" });
31 +
32 + invoke(fn);
33 + return 3;
34 +}
35 +
36 +export const FIXTURE_ENTRYPOINT = {
37 + fn: Component,
38 + params: [{}],
39 +};
40 +
41 +```
42 +
43 +### Eval output
44 +(kind: ok) 3
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/constant-prop-colliding-identifier.js new
+16
@@ -0,0 +1,16 @@
1 +import { invoke } from "shared-runtime";
2 +
3 +function Component() {
4 + let x = 2;
5 + const fn = () => {
6 + return { x: "value" };
7 + };
8 + invoke(fn);
9 + x = 3;
10 + return x;
11 +}
12 +
13 +export const FIXTURE_ENTRYPOINT = {
14 + fn: Component,
15 + params: [{}],
16 +};
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/context-variable-only-chained-assign.expect.md new
+65
@@ -0,0 +1,65 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +import { identity, invoke } from "shared-runtime";
6 +
7 +function foo() {
8 + let x = 2;
9 + const fn1 = () => {
10 + const copy1 = (x = 3);
11 + return identity(copy1);
12 + };
13 + const fn2 = () => {
14 + const copy2 = (x = 4);
15 + return [invoke(fn1), copy2, identity(copy2)];
16 + };
17 + return invoke(fn2);
18 +}
19 +
20 +export const FIXTURE_ENTRYPOINT = {
21 + fn: foo,
22 + params: [],
23 +};
24 +
25 +```
26 +
27 +## Code
28 +
29 +```javascript
30 +import { unstable_useMemoCache as useMemoCache } from "react";
31 +import { identity, invoke } from "shared-runtime";
32 +
33 +function foo() {
34 + const $ = useMemoCache(1);
35 + let t0;
36 + if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
37 + let x;
38 + x = 2;
39 + const fn1 = () => {
40 + const copy1 = (x = 3);
41 + return identity(copy1);
42 + };
43 +
44 + const fn2 = () => {
45 + const copy2 = (x = 4);
46 + return [invoke(fn1), copy2, identity(copy2)];
47 + };
48 +
49 + t0 = invoke(fn2);
50 + $[0] = t0;
51 + } else {
52 + t0 = $[0];
53 + }
54 + return t0;
55 +}
56 +
57 +export const FIXTURE_ENTRYPOINT = {
58 + fn: foo,
59 + params: [],
60 +};
61 +
62 +```
63 +
64 +### Eval output
65 +(kind: ok) [3,4,4]
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/context-variable-only-chained-assign.js new
+19
@@ -0,0 +1,19 @@
1 +import { identity, invoke } from "shared-runtime";
2 +
3 +function foo() {
4 + let x = 2;
5 + const fn1 = () => {
6 + const copy1 = (x = 3);
7 + return identity(copy1);
8 + };
9 + const fn2 = () => {
10 + const copy2 = (x = 4);
11 + return [invoke(fn1), copy2, identity(copy2)];
12 + };
13 + return invoke(fn2);
14 +}
15 +
16 +export const FIXTURE_ENTRYPOINT = {
17 + fn: foo,
18 + params: [],
19 +};
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/context-variable-reactive-explicit-control-flow.expect.md new
+63
@@ -0,0 +1,63 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +import { invoke } from "shared-runtime";
6 +
7 +function Component({ shouldReassign }) {
8 + let x = null;
9 + const reassign = () => {
10 + if (shouldReassign) {
11 + x = 2;
12 + }
13 + };
14 + invoke(reassign);
15 + return x;
16 +}
17 +
18 +export const FIXTURE_ENTRYPOINT = {
19 + fn: Component,
20 + params: [{ shouldReassign: true }],
21 + sequentialRenders: [{ shouldReassign: false }, { shouldReassign: true }],
22 +};
23 +
24 +```
25 +
26 +## Code
27 +
28 +```javascript
29 +import { unstable_useMemoCache as useMemoCache } from "react";
30 +import { invoke } from "shared-runtime";
31 +
32 +function Component(t21) {
33 + const $ = useMemoCache(2);
34 + const { shouldReassign } = t21;
35 + let x;
36 + if ($[0] !== shouldReassign) {
37 + x = null;
38 + const reassign = () => {
39 + if (shouldReassign) {
40 + x = 2;
41 + }
42 + };
43 +
44 + invoke(reassign);
45 + $[0] = shouldReassign;
46 + $[1] = x;
47 + } else {
48 + x = $[1];
49 + }
50 + return x;
51 +}
52 +
53 +export const FIXTURE_ENTRYPOINT = {
54 + fn: Component,
55 + params: [{ shouldReassign: true }],
56 + sequentialRenders: [{ shouldReassign: false }, { shouldReassign: true }],
57 +};
58 +
59 +```
60 +
61 +### Eval output
62 +(kind: ok) null
63 +2
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/context-variable-reactive-explicit-control-flow.js new
+18
@@ -0,0 +1,18 @@
1 +import { invoke } from "shared-runtime";
2 +
3 +function Component({ shouldReassign }) {
4 + let x = null;
5 + const reassign = () => {
6 + if (shouldReassign) {
7 + x = 2;
8 + }
9 + };
10 + invoke(reassign);
11 + return x;
12 +}
13 +
14 +export const FIXTURE_ENTRYPOINT = {
15 + fn: Component,
16 + params: [{ shouldReassign: true }],
17 + sequentialRenders: [{ shouldReassign: false }, { shouldReassign: true }],
18 +};
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/context-variable-reactive-implicit-control-flow.expect.md new
+65
@@ -0,0 +1,65 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +import { conditionalInvoke } from "shared-runtime";
6 +
7 +// same as context-variable-reactive-explicit-control-flow.js, but make
8 +// the control flow implicit
9 +
10 +function Component({ shouldReassign }) {
11 + let x = null;
12 + const reassign = () => {
13 + x = 2;
14 + };
15 + conditionalInvoke(shouldReassign, reassign);
16 + return x;
17 +}
18 +
19 +export const FIXTURE_ENTRYPOINT = {
20 + fn: Component,
21 + params: [{ shouldReassign: true }],
22 + sequentialRenders: [{ shouldReassign: false }, { shouldReassign: true }],
23 +};
24 +
25 +```
26 +
27 +## Code
28 +
29 +```javascript
30 +import { unstable_useMemoCache as useMemoCache } from "react";
31 +import { conditionalInvoke } from "shared-runtime";
32 +
33 +// same as context-variable-reactive-explicit-control-flow.js, but make
34 +// the control flow implicit
35 +
36 +function Component(t20) {
37 + const $ = useMemoCache(2);
38 + const { shouldReassign } = t20;
39 + let x;
40 + if ($[0] !== shouldReassign) {
41 + x = null;
42 + const reassign = () => {
43 + x = 2;
44 + };
45 +
46 + conditionalInvoke(shouldReassign, reassign);
47 + $[0] = shouldReassign;
48 + $[1] = x;
49 + } else {
50 + x = $[1];
51 + }
52 + return x;
53 +}
54 +
55 +export const FIXTURE_ENTRYPOINT = {
56 + fn: Component,
57 + params: [{ shouldReassign: true }],
58 + sequentialRenders: [{ shouldReassign: false }, { shouldReassign: true }],
59 +};
60 +
61 +```
62 +
63 +### Eval output
64 +(kind: ok) null
65 +2
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/context-variable-reactive-implicit-control-flow.js new
+19
@@ -0,0 +1,19 @@
1 +import { conditionalInvoke } from "shared-runtime";
2 +
3 +// same as context-variable-reactive-explicit-control-flow.js, but make
4 +// the control flow implicit
5 +
6 +function Component({ shouldReassign }) {
7 + let x = null;
8 + const reassign = () => {
9 + x = 2;
10 + };
11 + conditionalInvoke(shouldReassign, reassign);
12 + return x;
13 +}
14 +
15 +export const FIXTURE_ENTRYPOINT = {
16 + fn: Component,
17 + params: [{ shouldReassign: true }],
18 + sequentialRenders: [{ shouldReassign: false }, { shouldReassign: true }],
19 +};
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/context-variable-reassigned-outside-of-lambda.expect.md
+26 -10
@@ -2,40 +2,48 @@
2 ## Input
3
4 ```javascript
5 -// @debug
5 +import { Stringify } from "shared-runtime";
6 +
7 function Component(props) {
8 let x = null;
8 - const onChange = (e) => {
9 + const callback = () => {
10 console.log(x);
11 };
12 x = {};
12 - return <Foo onChange={onChange} />;
13 + return <Stringify callback={callback} shouldInvokeFns={true} />;
14 }
15
16 +export const FIXTURE_ENTRYPOINT = {
17 + fn: Component,
18 + params: [{}],
19 +};
20 +
21 ```
22
23 ## Code
24
25 ```javascript
20 -import { unstable_useMemoCache as useMemoCache } from "react"; // @debug
26 +import { unstable_useMemoCache as useMemoCache } from "react";
27 +import { Stringify } from "shared-runtime";
28 +
29 function Component(props) {
30 const $ = useMemoCache(2);
23 - let onChange;
31 + let callback;
32 if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
33 let x;
34 x = null;
27 - onChange = (e) => {
35 + callback = () => {
36 console.log(x);
37 };
38
39 x = {};
32 - $[0] = onChange;
40 + $[0] = callback;
41 } else {
34 - onChange = $[0];
42 + callback = $[0];
43 }
44 let t0;
45 if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
38 - t0 = <Foo onChange={onChange} />;
46 + t0 = <Stringify callback={callback} shouldInvokeFns={true} />;
47 $[1] = t0;
48 } else {
49 t0 = $[1];
@@ -43,5 +51,13 @@ function Component(props) {
51 return t0;
52 }
53
54 +export const FIXTURE_ENTRYPOINT = {
55 + fn: Component,
56 + params: [{}],
57 +};
58 +
59 ```
47 -
\ No newline at end of file
60 +
61 +### Eval output
62 +(kind: ok) <div>{"callback":{"kind":"Function"},"shouldInvokeFns":true}</div>
63 +logs: [{}]
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/context-variable-reassigned-outside-of-lambda.js
+9 -3
@@ -1,9 +1,15 @@
1 -// @debug
1 +import { Stringify } from "shared-runtime";
2 +
3 function Component(props) {
4 let x = null;
4 - const onChange = (e) => {
5 + const callback = () => {
6 console.log(x);
7 };
8 x = {};
8 - return <Foo onChange={onChange} />;
9 + return <Stringify callback={callback} shouldInvokeFns={true} />;
10 }
11 +
12 +export const FIXTURE_ENTRYPOINT = {
13 + fn: Component,
14 + params: [{}],
15 +};
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/context-variable-reassigned-reactive-capture.expect.md new
+59
@@ -0,0 +1,59 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +import { invoke } from "shared-runtime";
6 +
7 +function Component({ value }) {
8 + let x = null;
9 + const reassign = () => {
10 + x = value;
11 + };
12 + invoke(reassign);
13 + return x;
14 +}
15 +
16 +export const FIXTURE_ENTRYPOINT = {
17 + fn: Component,
18 + params: [{ value: 2 }],
19 + sequentialRenders: [{ value: 2 }, { value: 4 }],
20 +};
21 +
22 +```
23 +
24 +## Code
25 +
26 +```javascript
27 +import { unstable_useMemoCache as useMemoCache } from "react";
28 +import { invoke } from "shared-runtime";
29 +
30 +function Component(t20) {
31 + const $ = useMemoCache(2);
32 + const { value } = t20;
33 + let x;
34 + if ($[0] !== value) {
35 + x = null;
36 + const reassign = () => {
37 + x = value;
38 + };
39 +
40 + invoke(reassign);
41 + $[0] = value;
42 + $[1] = x;
43 + } else {
44 + x = $[1];
45 + }
46 + return x;
47 +}
48 +
49 +export const FIXTURE_ENTRYPOINT = {
50 + fn: Component,
51 + params: [{ value: 2 }],
52 + sequentialRenders: [{ value: 2 }, { value: 4 }],
53 +};
54 +
55 +```
56 +
57 +### Eval output
58 +(kind: ok) 2
59 +4
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/context-variable-reassigned-reactive-capture.js new
+16
@@ -0,0 +1,16 @@
1 +import { invoke } from "shared-runtime";
2 +
3 +function Component({ value }) {
4 + let x = null;
5 + const reassign = () => {
6 + x = value;
7 + };
8 + invoke(reassign);
9 + return x;
10 +}
11 +
12 +export const FIXTURE_ENTRYPOINT = {
13 + fn: Component,
14 + params: [{ value: 2 }],
15 + sequentialRenders: [{ value: 2 }, { value: 4 }],
16 +};
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/context-variable-reassigned-two-lambdas.expect.md new
+78
@@ -0,0 +1,78 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +import { conditionalInvoke } from "shared-runtime";
6 +
7 +function Component({ doReassign1, doReassign2 }) {
8 + let x = {};
9 + const reassign1 = () => {
10 + x = 2;
11 + };
12 + const reassign2 = () => {
13 + x = 3;
14 + };
15 + conditionalInvoke(doReassign1, reassign1);
16 + conditionalInvoke(doReassign2, reassign2);
17 + return x;
18 +}
19 +
20 +export const FIXTURE_ENTRYPOINT = {
21 + fn: Component,
22 + params: [{ doReassign1: true, doReassign2: true }],
23 + sequentialRenders: [
24 + { doReassign1: true, doReassign2: true },
25 + { doReassign1: true, doReassign2: false },
26 + { doReassign1: false, doReassign2: false },
27 + ],
28 +};
29 +
30 +```
31 +
32 +## Code
33 +
34 +```javascript
35 +import { unstable_useMemoCache as useMemoCache } from "react";
36 +import { conditionalInvoke } from "shared-runtime";
37 +
38 +function Component(t32) {
39 + const $ = useMemoCache(3);
40 + const { doReassign1, doReassign2 } = t32;
41 + let x;
42 + if ($[0] !== doReassign1 || $[1] !== doReassign2) {
43 + x = {};
44 + const reassign1 = () => {
45 + x = 2;
46 + };
47 +
48 + const reassign2 = () => {
49 + x = 3;
50 + };
51 +
52 + conditionalInvoke(doReassign1, reassign1);
53 + conditionalInvoke(doReassign2, reassign2);
54 + $[0] = doReassign1;
55 + $[1] = doReassign2;
56 + $[2] = x;
57 + } else {
58 + x = $[2];
59 + }
60 + return x;
61 +}
62 +
63 +export const FIXTURE_ENTRYPOINT = {
64 + fn: Component,
65 + params: [{ doReassign1: true, doReassign2: true }],
66 + sequentialRenders: [
67 + { doReassign1: true, doReassign2: true },
68 + { doReassign1: true, doReassign2: false },
69 + { doReassign1: false, doReassign2: false },
70 + ],
71 +};
72 +
73 +```
74 +
75 +### Eval output
76 +(kind: ok) 3
77 +2
78 +{}
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/context-variable-reassigned-two-lambdas.js new
+24
@@ -0,0 +1,24 @@
1 +import { conditionalInvoke } from "shared-runtime";
2 +
3 +function Component({ doReassign1, doReassign2 }) {
4 + let x = {};
5 + const reassign1 = () => {
6 + x = 2;
7 + };
8 + const reassign2 = () => {
9 + x = 3;
10 + };
11 + conditionalInvoke(doReassign1, reassign1);
12 + conditionalInvoke(doReassign2, reassign2);
13 + return x;
14 +}
15 +
16 +export const FIXTURE_ENTRYPOINT = {
17 + fn: Component,
18 + params: [{ doReassign1: true, doReassign2: true }],
19 + sequentialRenders: [
20 + { doReassign1: true, doReassign2: true },
21 + { doReassign1: true, doReassign2: false },
22 + { doReassign1: false, doReassign2: false },
23 + ],
24 +};
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/function-declaration-reassign.expect.md
+5 -7
@@ -25,16 +25,14 @@ import { unstable_useMemoCache as useMemoCache } from "react";
25 function component() {
26 const $ = useMemoCache(1);
27 let x;
28 + let t0;
29 if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
29 - x = function x(a) {
30 - a.foo();
31 - };
32 -
33 - x = {};
34 - $[0] = x;
30 + t0 = {};
31 + $[0] = t0;
32 } else {
36 - x = $[0];
33 + t0 = $[0];
34 }
35 + x = t0;
36 return x;
37 }
38
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/reassign-object-in-context.expect.md
+2 -4
@@ -13,8 +13,7 @@ function Component(props) {
13
14 export const FIXTURE_ENTRYPOINT = {
15 fn: Component,
16 - params: ["TodoAdd"],
17 - isComponent: "TodoAdd",
16 + params: [{}],
17 };
18
19 ```
@@ -42,8 +41,7 @@ function Component(props) {
41
42 export const FIXTURE_ENTRYPOINT = {
43 fn: Component,
45 - params: ["TodoAdd"],
46 - isComponent: "TodoAdd",
44 + params: [{}],
45 };
46
47 ```
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/reassign-object-in-context.js
+1 -2
@@ -9,6 +9,5 @@ function Component(props) {
9
10 export const FIXTURE_ENTRYPOINT = {
11 fn: Component,
12 - params: ["TodoAdd"],
13 - isComponent: "TodoAdd",
12 + params: [{}],
13 };
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/reassign-primitive-in-context.expect.md
+5 -5
@@ -13,8 +13,7 @@ function Component(props) {
13
14 export const FIXTURE_ENTRYPOINT = {
15 fn: Component,
16 - params: ["TodoAdd"],
17 - isComponent: "TodoAdd",
16 + params: [{}],
17 };
18
19 ```
@@ -42,9 +41,10 @@ function Component(props) {
41
42 export const FIXTURE_ENTRYPOINT = {
43 fn: Component,
45 - params: ["TodoAdd"],
46 - isComponent: "TodoAdd",
44 + params: [{}],
45 };
46
47 ```
50 -
\ No newline at end of file
48 +
49 +### Eval output
50 +(kind: ok) {}
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/reassign-primitive-in-context.js
+1 -2
@@ -9,6 +9,5 @@ function Component(props) {
9
10 export const FIXTURE_ENTRYPOINT = {
11 fn: Component,
12 - params: ["TodoAdd"],
13 - isComponent: "TodoAdd",
12 + params: [{}],
13 };
compiler/packages/sprout/src/SproutTodoFilter.ts
-4
@@ -32,7 +32,6 @@ const skipFilter = new Set([
32 "capturing-func-mutate-nested",
33 "capturing-func-mutate",
34 "capturing-function-1",
35 - "capturing-function-alias-computed-load-3",
35 "capturing-function-alias-computed-load",
36 "capturing-function-decl",
37 "capturing-function-skip-computed-path",
@@ -123,7 +122,6 @@ const skipFilter = new Set([
122 "reactive-scopes",
123 "reactivity-analysis-interleaved-reactivity",
124 "reassign-object-in-context",
126 - "reassign-primitive-in-context",
125 "reassignment-separate-scopes",
126 "reduce-reactive-cond-memberexpr-join",
127 "reduce-reactive-uncond-deps-nonoverlap-descendant",
@@ -191,7 +189,6 @@ const skipFilter = new Set([
189 */
190 "alias-capture-in-method-receiver",
191 "alias-nested-member-path-mutate",
194 - "chained-assignment-context-variable",
192 "concise-arrow-expr",
193 "const-propagation-into-function-expression-global",
194 "declare-reassign-variable-in-function-declaration",
@@ -268,7 +265,6 @@ const skipFilter = new Set([
265 "computed-load-primitive-as-dependency",
266 "computed-store-alias",
267 "constant-propagation-into-function-expressions",
271 - "context-variable-reassigned-outside-of-lambda",
268 "destructuring-mixed-scope-declarations-and-locals",
269 "destructuring-property-inference",
270 "do-while-conditional-break",
compiler/packages/sprout/src/shared-runtime.ts
+14 -2
@@ -127,8 +127,8 @@ export function makeObject_Primitives(): StringKeyedObject {
127 return { a: 0, b: "value1", c: true };
128 }
129
130 -export function makeArray<T>(value: T): Array<T> {
131 - return [value];
130 +export function makeArray<T>(...values: Array<T>): Array<T> {
131 + return [...values];
132 }
133
134 export function addOne(value: number): number {
@@ -173,6 +173,18 @@ export function invoke<T extends Array<any>, ReturnType>(
173 return fn(...params);
174 }
175
176 +export function conditionalInvoke<T extends Array<any>, ReturnType>(
177 + shouldInvoke: boolean,
178 + fn: (...input: T) => ReturnType,
179 + ...params: T
180 +) {
181 + if (shouldInvoke) {
182 + return fn(...params);
183 + } else {
184 + return null;
185 + }
186 +}
187 +
188 /**
189 * React Components
190 */