Repro for unmemoized value due to assignment to a context variable
Joe Savona committed
Apr 3, 2024 at 08:02 UTC
94156d701461399f6d441f74b977e7cb0e204535
2 files changed
+103
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/error.todo-repro-unmemoized-callback-captured-in-context-variable.expect.md
new
+62
@@ -0,0 +1,62 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+// @validatePreserveExistingMemoizationGuarantees
6
+import { useMemo } from "react";
7
+import { ValidateMemoization, useHook } from "shared-runtime";
8
+
9
+function UnmemoizedCallbackCapturedInContextVariable({ cond1, cond2 }) {
10
+ // The return value is captured by `x` which is a context variable, which
11
+ // extends a's range to include the call instruction. This prevents the entire
12
+ // range from being memoized
13
+ const a = useHook();
14
+ // Because b is also part of that same mutable range, it can't be memoized either
15
+ const b = useMemo(() => ({}), []);
16
+
17
+ // Conditional assignment without a subsequent mutation normally doesn't create a mutable
18
+ // range, but in this case we're reassigning a context variable
19
+ let x;
20
+ if (cond1) {
21
+ x = a;
22
+ } else if (cond2) {
23
+ x = b;
24
+ } else {
25
+ return null;
26
+ }
27
+
28
+ const f = () => {
29
+ return x;
30
+ };
31
+ const result = f();
32
+
33
+ return <ValidateMemoization inputs={[cond1, cond2]} output={result} />;
34
+}
35
+
36
+export const FIXTURE_ENTRYPOINT = {
37
+ fn: UnmemoizedCallbackCapturedInContextVariable,
38
+ params: [{ cond1: true, cond2: false }],
39
+ sequentialRenders: [
40
+ { cond1: true, cond2: true },
41
+ { cond1: false, cond2: true },
42
+ { cond1: false, cond2: true }, // fails sprout bc memoization is not preserved
43
+ { cond1: false, cond2: false },
44
+ ],
45
+};
46
+
47
+```
48
+
49
+
50
+## Error
51
+
52
+```
53
+ 9 | const a = useHook();
54
+ 10 | // Because b is also part of that same mutable range, it can't be memoized either
55
+> 11 | const b = useMemo(() => ({}), []);
56
+ | ^^^^^^^^^^ InvalidReact: This value was manually memoized, but cannot be memoized under Forget because it may be mutated after it is memoized (11:11)
57
+ 12 |
58
+ 13 | // Conditional assignment without a subsequent mutation normally doesn't create a mutable
59
+ 14 | // range, but in this case we're reassigning a context variable
60
+```
61
+
62
+
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/error.todo-repro-unmemoized-callback-captured-in-context-variable.tsx
new
+41
@@ -0,0 +1,41 @@
1
+// @validatePreserveExistingMemoizationGuarantees
2
+import { useMemo } from "react";
3
+import { ValidateMemoization, useHook } from "shared-runtime";
4
+
5
+function UnmemoizedCallbackCapturedInContextVariable({ cond1, cond2 }) {
6
+ // The return value is captured by `x` which is a context variable, which
7
+ // extends a's range to include the call instruction. This prevents the entire
8
+ // range from being memoized
9
+ const a = useHook();
10
+ // Because b is also part of that same mutable range, it can't be memoized either
11
+ const b = useMemo(() => ({}), []);
12
+
13
+ // Conditional assignment without a subsequent mutation normally doesn't create a mutable
14
+ // range, but in this case we're reassigning a context variable
15
+ let x;
16
+ if (cond1) {
17
+ x = a;
18
+ } else if (cond2) {
19
+ x = b;
20
+ } else {
21
+ return null;
22
+ }
23
+
24
+ const f = () => {
25
+ return x;
26
+ };
27
+ const result = f();
28
+
29
+ return <ValidateMemoization inputs={[cond1, cond2]} output={result} />;
30
+}
31
+
32
+export const FIXTURE_ENTRYPOINT = {
33
+ fn: UnmemoizedCallbackCapturedInContextVariable,
34
+ params: [{ cond1: true, cond2: false }],
35
+ sequentialRenders: [
36
+ { cond1: true, cond2: true },
37
+ { cond1: false, cond2: true },
38
+ { cond1: false, cond2: true }, // fails sprout bc memoization is not preserved
39
+ { cond1: false, cond2: false },
40
+ ],
41
+};