@samitouri / QOS-React / commits / 94156d7014

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 +};