@samitouri / QOS-React-1 / commits / 4c68da2e60

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);