Todo for early return within reactive scopes
Adds a new compiler pass that will eventually actually handle early returns within reactive scopes. For now it just detects them and throws a Todo error.
Joe Savona committed
Dec 20, 2023 at 13:52 UTC
4c68da2e60d2c5de46097eab914e6874806bf385
23 files changed
+492
-333
compiler/packages/babel-plugin-react-forget/src/Entrypoint/Pipeline.ts
+9
-1
@@ -49,6 +49,7 @@ import {
49
mergeOverlappingReactiveScopes,
50
mergeReactiveScopesThatInvalidateTogether,
51
promoteUsedTemporaries,
52
+ propagateEarlyReturns,
53
propagateScopeDependencies,
54
pruneAllReactiveScopes,
55
pruneHoistedContexts,
@@ -283,7 +284,7 @@ function* runWithEnvironment(
284
pruneNonEscapingScopes(reactiveFunction);
285
yield log({
286
kind: "reactive",
286
- name: "PruneNonEscapingDependencies",
287
+ name: "PruneNonEscapingScopes",
288
value: reactiveFunction,
289
});
290
@@ -294,6 +295,13 @@ function* runWithEnvironment(
295
value: reactiveFunction,
296
});
297
298
+ propagateEarlyReturns(reactiveFunction);
299
+ yield log({
300
+ kind: "reactive",
301
+ name: "PropagateEarlyReturns",
302
+ value: reactiveFunction,
303
+ });
304
+
305
pruneUnusedScopes(reactiveFunction);
306
yield log({
307
kind: "reactive",
compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/PropagateEarlyReturns.ts
new
+120
@@ -0,0 +1,120 @@
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 { visitReactiveFunction } from ".";
9
+import { CompilerError } from "..";
10
+import {
11
+ ReactiveFunction,
12
+ ReactiveScopeBlock,
13
+ ReactiveTerminalStatement,
14
+} from "../HIR";
15
+import { ReactiveFunctionVisitor } from "./visitors";
16
+
17
+/**
18
+ * TODO: Actualy propagate early return information, for now we throw a Todo bailout.
19
+ *
20
+ * This pass ensures that reactive blocks honor the control flow behavior of the
21
+ * original code including early return semantics. Specifically, if a reactive
22
+ * scope early returned during the previous execution and the inputs to that block
23
+ * have not changed, then the code should early return (with the same value) again.
24
+ *
25
+ * Example:
26
+ *
27
+ * ```javascript
28
+ * let x = [];
29
+ * if (props.cond) {
30
+ * x.push(12);
31
+ * return x;
32
+ * } else {
33
+ * return foo();
34
+ * }
35
+ * ```
36
+ *
37
+ * Imagine that this code is called twice in a row with props.cond = true. Both
38
+ * times it should return the same object (===), an array `[12]`.
39
+ *
40
+ * The compilation strategy is as follows. For each top-level reactive scope
41
+ * that contains (transitively) an early return:
42
+ *
43
+ * - Label the scope
44
+ * - Synthesize a new temporary, eg `t0`, and set it as a declaration of the scope.
45
+ * This will represent the possibly-unset return value for that scope.
46
+ * - Make the first instruction of the scope a reassignment of that temporary,
47
+ * assigning a sentinel value (can reuse the same symbol as we use for cache slots).
48
+ * This assignment ensures that if we don't take an early return, that the value
49
+ * is the sentinel.
50
+ * - Replace all `return` statements with:
51
+ * - An assignment of the temporary with the value being returned.
52
+ * - An assignment of the temporary into a cache slot, so it can be retrieved in the
53
+ * scope's "else" branch.
54
+ * - A `break` to the reactive scope's label.
55
+ * - Finally, add code _after_ the reactive scope that checks the temporary. If
56
+ * it equals the sentinel value do nothing; else return its value.
57
+ *
58
+ * For the above example that looks roughly like:
59
+ *
60
+ * ```javascript
61
+ * let t0; // temporary for early return;
62
+ * bb1: if (props.cond !== $[0]) {
63
+ * // reset the temporary
64
+ * t0 = Symbol.for('react.forget');
65
+ * // original code
66
+ * let x = [];
67
+ * if (props.cond) {
68
+ * x.push(12);
69
+ * // replace the early return w assignment and break
70
+ * t0 = x;
71
+ * $[2] = t0;
72
+ * break bb1
73
+ * } else {
74
+ * let t1;
75
+ * if ($[1] === Symbol.for('react.forget')) {
76
+ * t1 = foo();
77
+ * $[1] = t1;
78
+ * } else {
79
+ * t1 = $[1];
80
+ * }
81
+ * // Replace early return w assignment and break;
82
+ * t0 = t1;
83
+ * $[2] = t0;
84
+ * break bb1;
85
+ * }
86
+ * } else {
87
+ * t0 = $[2];
88
+ * }
89
+ * if (t0 !== Symbol.for('react.forget')) {
90
+ * return t0;
91
+ * }
92
+ * ```
93
+ */
94
+export function propagateEarlyReturns(fn: ReactiveFunction): void {
95
+ visitReactiveFunction(fn, new Visitor(), false);
96
+}
97
+
98
+class Visitor extends ReactiveFunctionVisitor<boolean> {
99
+ override visitScope(
100
+ scopeBlock: ReactiveScopeBlock,
101
+ _withinReactiveScope: boolean
102
+ ): void {
103
+ this.traverseScope(scopeBlock, true);
104
+ }
105
+
106
+ override visitTerminal(
107
+ stmt: ReactiveTerminalStatement,
108
+ withinReactiveScope: boolean
109
+ ): void {
110
+ if (withinReactiveScope && stmt.terminal.kind === "return") {
111
+ CompilerError.throwTodo({
112
+ reason: `Support early return within a reactive scope`,
113
+ loc: stmt.terminal.value.loc,
114
+ description: null,
115
+ suggestions: null,
116
+ });
117
+ }
118
+ this.traverseTerminal(stmt, withinReactiveScope);
119
+ }
120
+}
compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/PruneNonEscapingScopes.ts
+21
-5
@@ -872,19 +872,35 @@ class PruneScopesTransform extends ReactiveFunctionTransform<
872
Set<IdentifierId>
873
> {
874
override transformScope(
875
- scope: ReactiveScopeBlock,
875
+ scopeBlock: ReactiveScopeBlock,
876
state: Set<IdentifierId>
877
): Transformed<ReactiveStatement> {
878
- this.visitScope(scope, state);
878
+ this.visitScope(scopeBlock, state);
879
+
880
+ /**
881
+ * Scopes may initially appear "empty" because the value being memoized
882
+ * is early-returned from within the scope. For now we intentionaly keep
883
+ * these scopes, and let them get pruned later by PruneUnusedScopes
884
+ * _after_ handling the early-return case in PropagateEarlyReturns.
885
+ */
886
+ if (
887
+ scopeBlock.scope.declarations.size === 0 &&
888
+ scopeBlock.scope.reassignments.size === 0
889
+ ) {
890
+ return { kind: "keep" };
891
+ }
892
+
893
const hasMemoizedOutput =
880
- Array.from(scope.scope.declarations.keys()).some((id) => state.has(id)) ||
881
- Array.from(scope.scope.reassignments).some((identifier) =>
894
+ Array.from(scopeBlock.scope.declarations.keys()).some((id) =>
895
+ state.has(id)
896
+ ) ||
897
+ Array.from(scopeBlock.scope.reassignments).some((identifier) =>
898
state.has(identifier.id)
899
);
900
if (hasMemoizedOutput) {
901
return { kind: "keep" };
902
} else {
887
- return { kind: "replace-many", value: scope.instructions };
903
+ return { kind: "replace-many", value: scopeBlock.instructions };
904
}
905
}
906
}
compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/index.ts
+1
@@ -23,6 +23,7 @@ export { mergeOverlappingReactiveScopes } from "./MergeOverlappingReactiveScopes
23
export { mergeReactiveScopesThatInvalidateTogether } from "./MergeReactiveScopesThatInvalidateTogether";
24
export { printReactiveFunction } from "./PrintReactiveFunction";
25
export { promoteUsedTemporaries } from "./PromoteUsedTemporaries";
26
+export { propagateEarlyReturns } from "./PropagateEarlyReturns";
27
export { propagateScopeDependencies } from "./PropagateScopeDependencies";
28
export { pruneAllReactiveScopes } from "./PruneAllReactiveScopes";
29
export { pruneHoistedContexts } from "./PruneHoistedContexts";
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/conditional-break.expect.md
deleted
-158
@@ -1,158 +0,0 @@
1
-
2
-## Input
3
-
4
-```javascript
5
-/**
6
- * props.b does *not* influence `a`
7
- */
8
-function ComponentA(props) {
9
- const a_DEBUG = [];
10
- a_DEBUG.push(props.a);
11
- if (props.b) {
12
- return null;
13
- }
14
- a_DEBUG.push(props.d);
15
- return a_DEBUG;
16
-}
17
-
18
-/**
19
- * props.b *does* influence `a`
20
- */
21
-function ComponentB(props) {
22
- const a = [];
23
- a.push(props.a);
24
- if (props.b) {
25
- a.push(props.c);
26
- }
27
- a.push(props.d);
28
- return a;
29
-}
30
-
31
-/**
32
- * props.b *does* influence `a`, but only in a way that is never observable
33
- */
34
-function ComponentC(props) {
35
- const a = [];
36
- a.push(props.a);
37
- if (props.b) {
38
- a.push(props.c);
39
- return null;
40
- }
41
- a.push(props.d);
42
- return a;
43
-}
44
-
45
-/**
46
- * props.b *does* influence `a`
47
- */
48
-function ComponentD(props) {
49
- const a = [];
50
- a.push(props.a);
51
- if (props.b) {
52
- a.push(props.c);
53
- return a;
54
- }
55
- a.push(props.d);
56
- return a;
57
-}
58
-
59
-```
60
-
61
-## Code
62
-
63
-```javascript
64
-import { unstable_useMemoCache as useMemoCache } from "react";
65
-/**
66
- * props.b does *not* influence `a`
67
- */
68
-function ComponentA(props) {
69
- const $ = useMemoCache(4);
70
- let a_DEBUG;
71
- if ($[0] !== props.a || $[1] !== props.b || $[2] !== props.d) {
72
- a_DEBUG = [];
73
- a_DEBUG.push(props.a);
74
- if (props.b) {
75
- return null;
76
- }
77
-
78
- a_DEBUG.push(props.d);
79
- $[0] = props.a;
80
- $[1] = props.b;
81
- $[2] = props.d;
82
- $[3] = a_DEBUG;
83
- } else {
84
- a_DEBUG = $[3];
85
- }
86
- return a_DEBUG;
87
-}
88
-
89
-/**
90
- * props.b *does* influence `a`
91
- */
92
-function ComponentB(props) {
93
- const $ = useMemoCache(2);
94
- let a;
95
- if ($[0] !== props) {
96
- a = [];
97
- a.push(props.a);
98
- if (props.b) {
99
- a.push(props.c);
100
- }
101
-
102
- a.push(props.d);
103
- $[0] = props;
104
- $[1] = a;
105
- } else {
106
- a = $[1];
107
- }
108
- return a;
109
-}
110
-
111
-/**
112
- * props.b *does* influence `a`, but only in a way that is never observable
113
- */
114
-function ComponentC(props) {
115
- const $ = useMemoCache(2);
116
- let a;
117
- if ($[0] !== props) {
118
- a = [];
119
- a.push(props.a);
120
- if (props.b) {
121
- a.push(props.c);
122
- return null;
123
- }
124
-
125
- a.push(props.d);
126
- $[0] = props;
127
- $[1] = a;
128
- } else {
129
- a = $[1];
130
- }
131
- return a;
132
-}
133
-
134
-/**
135
- * props.b *does* influence `a`
136
- */
137
-function ComponentD(props) {
138
- const $ = useMemoCache(2);
139
- let a;
140
- if ($[0] !== props) {
141
- a = [];
142
- a.push(props.a);
143
- if (props.b) {
144
- a.push(props.c);
145
- return a;
146
- }
147
-
148
- a.push(props.d);
149
- $[0] = props;
150
- $[1] = a;
151
- } else {
152
- a = $[1];
153
- }
154
- return a;
155
-}
156
-
157
-```
158
-
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/error.todo-early-return--conditional-break.expect.md
new
+68
@@ -0,0 +1,68 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+/**
6
+ * props.b does *not* influence `a`
7
+ */
8
+function ComponentA(props) {
9
+ const a_DEBUG = [];
10
+ a_DEBUG.push(props.a);
11
+ if (props.b) {
12
+ return null;
13
+ }
14
+ a_DEBUG.push(props.d);
15
+ return a_DEBUG;
16
+}
17
+
18
+/**
19
+ * props.b *does* influence `a`
20
+ */
21
+function ComponentB(props) {
22
+ const a = [];
23
+ a.push(props.a);
24
+ if (props.b) {
25
+ a.push(props.c);
26
+ }
27
+ a.push(props.d);
28
+ return a;
29
+}
30
+
31
+/**
32
+ * props.b *does* influence `a`, but only in a way that is never observable
33
+ */
34
+function ComponentC(props) {
35
+ const a = [];
36
+ a.push(props.a);
37
+ if (props.b) {
38
+ a.push(props.c);
39
+ return null;
40
+ }
41
+ a.push(props.d);
42
+ return a;
43
+}
44
+
45
+/**
46
+ * props.b *does* influence `a`
47
+ */
48
+function ComponentD(props) {
49
+ const a = [];
50
+ a.push(props.a);
51
+ if (props.b) {
52
+ a.push(props.c);
53
+ return a;
54
+ }
55
+ a.push(props.d);
56
+ return a;
57
+}
58
+
59
+```
60
+
61
+
62
+## Error
63
+
64
+```
65
+[ReactForget] Todo: Support early return within a reactive scope (8:8)
66
+```
67
+
68
+
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/error.todo-early-return--conditional-break.js
renamed
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/error.todo-early-return--early-return-nested-early-return-within-reactive-scope.expect.md
new
+36
@@ -0,0 +1,36 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+function Component(props) {
6
+ let x = [];
7
+ if (props.cond) {
8
+ x.push(props.a);
9
+ if (props.b) {
10
+ const y = [props.b];
11
+ x.push(y);
12
+ // oops no memo!
13
+ return x;
14
+ }
15
+ // oops no memo!
16
+ return x;
17
+ } else {
18
+ return foo();
19
+ }
20
+}
21
+
22
+export const FIXTURE_ENTRYPOINT = {
23
+ fn: Component,
24
+ params: [{ cond: true, a: 42, b: 3.14 }],
25
+};
26
+
27
+```
28
+
29
+
30
+## Error
31
+
32
+```
33
+[ReactForget] Todo: Support early return within a reactive scope (9:9)
34
+```
35
+
36
+
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/error.todo-early-return--early-return-nested-early-return-within-reactive-scope.js
new
+21
@@ -0,0 +1,21 @@
1
+function Component(props) {
2
+ let x = [];
3
+ if (props.cond) {
4
+ x.push(props.a);
5
+ if (props.b) {
6
+ const y = [props.b];
7
+ x.push(y);
8
+ // oops no memo!
9
+ return x;
10
+ }
11
+ // oops no memo!
12
+ return x;
13
+ } else {
14
+ return foo();
15
+ }
16
+}
17
+
18
+export const FIXTURE_ENTRYPOINT = {
19
+ fn: Component,
20
+ params: [{ cond: true, a: 42, b: 3.14 }],
21
+};
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/error.todo-early-return--early-return-within-reactive-scope.expect.md
new
+30
@@ -0,0 +1,30 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+function Component(props) {
6
+ let x = [];
7
+ if (props.cond) {
8
+ x.push(props.a);
9
+ // oops no memo!
10
+ return x;
11
+ } else {
12
+ return foo();
13
+ }
14
+}
15
+
16
+export const FIXTURE_ENTRYPOINT = {
17
+ fn: Component,
18
+ params: [{ cond: true, a: 42 }],
19
+};
20
+
21
+```
22
+
23
+
24
+## Error
25
+
26
+```
27
+[ReactForget] Todo: Support early return within a reactive scope (6:6)
28
+```
29
+
30
+
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/error.todo-early-return--early-return-within-reactive-scope.js
new
+15
@@ -0,0 +1,15 @@
1
+function Component(props) {
2
+ let x = [];
3
+ if (props.cond) {
4
+ x.push(props.a);
5
+ // oops no memo!
6
+ return x;
7
+ } else {
8
+ return foo();
9
+ }
10
+}
11
+
12
+export const FIXTURE_ENTRYPOINT = {
13
+ fn: Component,
14
+ params: [{ cond: true, a: 42 }],
15
+};
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/error.todo-early-return--partial-early-return-within-reactive-scope.expect.md
new
+35
@@ -0,0 +1,35 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+function Component(props) {
6
+ let x = [];
7
+ let y = null;
8
+ if (props.cond) {
9
+ x.push(props.a);
10
+ // oops no memo!
11
+ return x;
12
+ } else {
13
+ y = foo();
14
+ if (props.b) {
15
+ return;
16
+ }
17
+ }
18
+ return y;
19
+}
20
+
21
+export const FIXTURE_ENTRYPOINT = {
22
+ fn: Component,
23
+ params: [{ cond: true, a: 42 }],
24
+};
25
+
26
+```
27
+
28
+
29
+## Error
30
+
31
+```
32
+[ReactForget] Todo: Support early return within a reactive scope (7:7)
33
+```
34
+
35
+
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/error.todo-early-return--partial-early-return-within-reactive-scope.js
new
+20
@@ -0,0 +1,20 @@
1
+function Component(props) {
2
+ let x = [];
3
+ let y = null;
4
+ if (props.cond) {
5
+ x.push(props.a);
6
+ // oops no memo!
7
+ return x;
8
+ } else {
9
+ y = foo();
10
+ if (props.b) {
11
+ return;
12
+ }
13
+ }
14
+ return y;
15
+}
16
+
17
+export const FIXTURE_ENTRYPOINT = {
18
+ fn: Component,
19
+ params: [{ cond: true, a: 42 }],
20
+};
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/error.todo-early-return--try-catch-try-value-modified-in-catch.expect.md
new
+33
@@ -0,0 +1,33 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+const { throwInput } = require("shared-runtime");
6
+
7
+function Component(props) {
8
+ try {
9
+ const y = [];
10
+ y.push(props.y);
11
+ throwInput(y);
12
+ } catch (e) {
13
+ e.push(props.e);
14
+ return e;
15
+ }
16
+ return null;
17
+}
18
+
19
+export const FIXTURE_ENTRYPOINT = {
20
+ fn: Component,
21
+ params: [{ y: "foo", e: "bar" }],
22
+};
23
+
24
+```
25
+
26
+
27
+## Error
28
+
29
+```
30
+[ReactForget] Todo: Support early return within a reactive scope (10:10)
31
+```
32
+
33
+
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/error.todo-early-return--try-catch-try-value-modified-in-catch.js
renamed
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/error.todo-early-return--try-catch-with-catch-param.expect.md
renamed
+5
-24
@@ -24,30 +24,11 @@ export const FIXTURE_ENTRYPOINT = {
24
25
```
26
27
-## Code
27
29
-```javascript
30
-const { throwInput } = require("shared-runtime");
31
-
32
-function Component(props) {
33
- const x = [];
34
- try {
35
- throwInput(x);
36
- } catch (t22) {
37
- const e = t22;
38
-
39
- e.push(null);
40
- return e;
41
- }
42
- return x;
43
-}
44
-
45
-export const FIXTURE_ENTRYPOINT = {
46
- fn: Component,
47
- params: [{}],
48
-};
28
+## Error
29
30
```
51
-
52
-### Eval output
53
-(kind: ok) [null]
\ No newline at end of file
31
+[ReactForget] Todo: Support early return within a reactive scope (11:11)
32
+```
33
+
34
+
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/error.todo-early-return--try-catch-with-catch-param.js
renamed
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/error.todo-early-return--try-catch-with-return.expect.md
new
+36
@@ -0,0 +1,36 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+const { shallowCopy, throwInput } = require("shared-runtime");
6
+
7
+// @debug
8
+function Component(props) {
9
+ let x = [];
10
+ try {
11
+ const y = shallowCopy({});
12
+ if (y == null) {
13
+ return;
14
+ }
15
+ x.push(throwInput(y));
16
+ } catch {
17
+ return null;
18
+ }
19
+ return x;
20
+}
21
+
22
+export const FIXTURE_ENTRYPOINT = {
23
+ fn: Component,
24
+ params: [{}],
25
+};
26
+
27
+```
28
+
29
+
30
+## Error
31
+
32
+```
33
+[ReactForget] Todo: Support early return within a reactive scope
34
+```
35
+
36
+
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/error.todo-early-return--try-catch-with-return.js
renamed
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/try-catch-try-value-modified-in-catch.expect.md
deleted
-52
@@ -1,52 +0,0 @@
1
-
2
-## Input
3
-
4
-```javascript
5
-const { throwInput } = require("shared-runtime");
6
-
7
-function Component(props) {
8
- try {
9
- const y = [];
10
- y.push(props.y);
11
- throwInput(y);
12
- } catch (e) {
13
- e.push(props.e);
14
- return e;
15
- }
16
- return null;
17
-}
18
-
19
-export const FIXTURE_ENTRYPOINT = {
20
- fn: Component,
21
- params: [{ y: "foo", e: "bar" }],
22
-};
23
-
24
-```
25
-
26
-## Code
27
-
28
-```javascript
29
-const { throwInput } = require("shared-runtime");
30
-
31
-function Component(props) {
32
- try {
33
- const y = [];
34
- y.push(props.y);
35
- throwInput(y);
36
- } catch (t25) {
37
- const e = t25;
38
- e.push(props.e);
39
- return e;
40
- }
41
- return null;
42
-}
43
-
44
-export const FIXTURE_ENTRYPOINT = {
45
- fn: Component,
46
- params: [{ y: "foo", e: "bar" }],
47
-};
48
-
49
-```
50
-
51
-### Eval output
52
-(kind: ok) ["foo","bar"]
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/try-catch-with-return.expect.md
deleted
-66
@@ -1,66 +0,0 @@
1
-
2
-## Input
3
-
4
-```javascript
5
-const { shallowCopy, throwInput } = require("shared-runtime");
6
-
7
-// @debug
8
-function Component(props) {
9
- let x = [];
10
- try {
11
- const y = shallowCopy({});
12
- if (y == null) {
13
- return;
14
- }
15
- x.push(throwInput(y));
16
- } catch {
17
- return null;
18
- }
19
- return x;
20
-}
21
-
22
-export const FIXTURE_ENTRYPOINT = {
23
- fn: Component,
24
- params: [{}],
25
-};
26
-
27
-```
28
-
29
-## Code
30
-
31
-```javascript
32
-import { unstable_useMemoCache as useMemoCache } from "react";
33
-const { shallowCopy, throwInput } = require("shared-runtime");
34
-
35
-// @debug
36
-function Component(props) {
37
- const $ = useMemoCache(1);
38
- let x;
39
- if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
40
- x = [];
41
- try {
42
- const y = shallowCopy({});
43
- if (y == null) {
44
- return;
45
- }
46
-
47
- x.push(throwInput(y));
48
- } catch {
49
- return null;
50
- }
51
- $[0] = x;
52
- } else {
53
- x = $[0];
54
- }
55
- return x;
56
-}
57
-
58
-export const FIXTURE_ENTRYPOINT = {
59
- fn: Component,
60
- params: [{}],
61
-};
62
-
63
-```
64
-
65
-### Eval output
66
-(kind: ok) null
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/validate-no-set-state-in-render-uncalled-function-with-mutable-range-is-valid.expect.md
+41
-26
@@ -2,7 +2,7 @@
2
## Input
3
4
```javascript
5
-// @validateNoSetStateInRender
5
+// @validateNoSetStateInRender @enableAssumeHooksFollowRulesOfReact
6
function Component(props) {
7
const logEvent = useLogging(props.appId);
8
const [currentStep, setCurrentStep] = useState(0);
@@ -33,47 +33,62 @@ function Component(props) {
33
## Code
34
35
```javascript
36
-import { unstable_useMemoCache as useMemoCache } from "react"; // @validateNoSetStateInRender
36
+import { unstable_useMemoCache as useMemoCache } from "react"; // @validateNoSetStateInRender @enableAssumeHooksFollowRulesOfReact
37
function Component(props) {
38
- const $ = useMemoCache(3);
38
+ const $ = useMemoCache(7);
39
const logEvent = useLogging(props.appId);
40
const [currentStep, setCurrentStep] = useState(0);
41
-
42
- const onSubmit = (errorEvent) => {
43
- logEvent(errorEvent);
44
- setCurrentStep(1);
45
- };
41
+ let t0;
42
+ if ($[0] !== logEvent) {
43
+ t0 = (errorEvent) => {
44
+ logEvent(errorEvent);
45
+ setCurrentStep(1);
46
+ };
47
+ $[0] = logEvent;
48
+ $[1] = t0;
49
+ } else {
50
+ t0 = $[1];
51
+ }
52
+ const onSubmit = t0;
53
switch (currentStep) {
54
case 0: {
48
- let t0;
49
- if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
50
- t0 = <OtherComponent data={{ foo: "bar" }} />;
51
- $[0] = t0;
55
+ let t1;
56
+ if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
57
+ t1 = <OtherComponent data={{ foo: "bar" }} />;
58
+ $[2] = t1;
59
} else {
53
- t0 = $[0];
60
+ t1 = $[2];
61
}
55
- return t0;
62
+ return t1;
63
}
64
case 1: {
58
- let t1;
59
- if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
60
- t1 = { foo: "joe" };
61
- $[1] = t1;
65
+ let t2;
66
+ if ($[3] === Symbol.for("react.memo_cache_sentinel")) {
67
+ t2 = { foo: "joe" };
68
+ $[3] = t2;
69
} else {
63
- t1 = $[1];
70
+ t2 = $[3];
71
}
65
- return <OtherComponent data={t1} onSubmit={onSubmit} />;
72
+ let t3;
73
+ if ($[4] !== onSubmit) {
74
+ t3 = <OtherComponent data={t2} onSubmit={onSubmit} />;
75
+ $[4] = onSubmit;
76
+ $[5] = t3;
77
+ } else {
78
+ t3 = $[5];
79
+ }
80
+ return t3;
81
}
82
default: {
83
logEvent("Invalid step");
69
- let t2;
70
- if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
71
- t2 = <OtherComponent data={null} />;
72
- $[2] = t2;
84
+ let t4;
85
+ if ($[6] === Symbol.for("react.memo_cache_sentinel")) {
86
+ t4 = <OtherComponent data={null} />;
87
+ $[6] = t4;
88
} else {
74
- t2 = $[2];
89
+ t4 = $[6];
90
}
76
- return t2;
91
+ return t4;
92
}
93
}
94
}
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/validate-no-set-state-in-render-uncalled-function-with-mutable-range-is-valid.js
+1
-1
@@ -1,4 +1,4 @@
1
-// @validateNoSetStateInRender
1
+// @validateNoSetStateInRender @enableAssumeHooksFollowRulesOfReact
2
function Component(props) {
3
const logEvent = useLogging(props.appId);
4
const [currentStep, setCurrentStep] = useState(0);