@samitouri / QOS-React / commits / 3a2ff8b51b

[compiler] Fix <ValidateMemoization> (#33547)

By accident we were only ever checking the compiled output, but the intention was in general to be able to compare memoization with/without forget. --- [//]: # (BEGIN SAPLING FOOTER) Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33547). * #33571 * #33558 * __->__ #33547

Joseph Savona committed Jun 18, 2025 at 16:00 UTC 3a2ff8b51b5ab54c22f55a5f826c53419f718887
21 files changed +141 -74
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/todo-control-flow-sensitive-mutation.expect.md
+4 -9
@@ -23,14 +23,9 @@ function Component({a, b, c}: {a: number; b: number; c: number}) {
23
24 return (
25 <>
26 - <ValidateMemoization inputs={[a, b, c]} output={x} alwaysCheck={true} />;
26 + <ValidateMemoization inputs={[a, b, c]} output={x} />;
27 {/* TODO: should only depend on c */}
28 - <ValidateMemoization
29 - inputs={[a, b, c]}
30 - output={x[0]}
31 - alwaysCheck={true}
32 - />
33 - ;
28 + <ValidateMemoization inputs={[a, b, c]} output={x[0]} />;
29 </>
30 );
31 }
@@ -98,7 +93,7 @@ function Component(t0) {
93 }
94 let t3;
95 if ($[9] !== t2 || $[10] !== x) {
101 - t3 = <ValidateMemoization inputs={t2} output={x} alwaysCheck={true} />;
96 + t3 = <ValidateMemoization inputs={t2} output={x} />;
97 $[9] = t2;
98 $[10] = x;
99 $[11] = t3;
@@ -117,7 +112,7 @@ function Component(t0) {
112 }
113 let t5;
114 if ($[16] !== t4 || $[17] !== x[0]) {
120 - t5 = <ValidateMemoization inputs={t4} output={x[0]} alwaysCheck={true} />;
115 + t5 = <ValidateMemoization inputs={t4} output={x[0]} />;
116 $[16] = t4;
117 $[17] = x[0];
118 $[18] = t5;
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/todo-control-flow-sensitive-mutation.tsx
+2 -7
@@ -19,14 +19,9 @@ function Component({a, b, c}: {a: number; b: number; c: number}) {
19
20 return (
21 <>
22 - <ValidateMemoization inputs={[a, b, c]} output={x} alwaysCheck={true} />;
22 + <ValidateMemoization inputs={[a, b, c]} output={x} />;
23 {/* TODO: should only depend on c */}
24 - <ValidateMemoization
25 - inputs={[a, b, c]}
26 - output={x[0]}
27 - alwaysCheck={true}
28 - />
29 - ;
24 + <ValidateMemoization inputs={[a, b, c]} output={x[0]} />;
25 </>
26 );
27 }
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/todo-transitivity-createfrom-capture-lambda.expect.md
+2 -2
@@ -22,7 +22,7 @@ function Component({a, b}) {
22 typedMutate(z, b);
23
24 // TODO: this *should* only depend on `a`
25 - return <ValidateMemoization inputs={[a, b]} output={x} alwaysCheck={true} />;
25 + return <ValidateMemoization inputs={[a, b]} output={x} />;
26 }
27
28 export const FIXTURE_ENTRYPOINT = {
@@ -86,7 +86,7 @@ function Component(t0) {
86 }
87 let t3;
88 if ($[7] !== t2 || $[8] !== x) {
89 - t3 = <ValidateMemoization inputs={t2} output={x} alwaysCheck={true} />;
89 + t3 = <ValidateMemoization inputs={t2} output={x} />;
90 $[7] = t2;
91 $[8] = x;
92 $[9] = t3;
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/todo-transitivity-createfrom-capture-lambda.tsx
+1 -1
@@ -18,7 +18,7 @@ function Component({a, b}) {
18 typedMutate(z, b);
19
20 // TODO: this *should* only depend on `a`
21 - return <ValidateMemoization inputs={[a, b]} output={x} alwaysCheck={true} />;
21 + return <ValidateMemoization inputs={[a, b]} output={x} />;
22 }
23
24 export const FIXTURE_ENTRYPOINT = {
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/transitivity-add-captured-array-to-itself.expect.md
+4 -4
@@ -20,8 +20,8 @@ function Component({a, b}) {
20
21 return (
22 <>
23 - <ValidateMemoization inputs={[a]} output={o} alwaysCheck={true} />;
24 - <ValidateMemoization inputs={[a, b]} output={x} alwaysCheck={true} />;
23 + <ValidateMemoization inputs={[a]} output={o} />;
24 + <ValidateMemoization inputs={[a, b]} output={x} />;
25 </>
26 );
27 }
@@ -92,7 +92,7 @@ function Component(t0) {
92 }
93 let t5;
94 if ($[8] !== o || $[9] !== t4) {
95 - t5 = <ValidateMemoization inputs={t4} output={o} alwaysCheck={true} />;
95 + t5 = <ValidateMemoization inputs={t4} output={o} />;
96 $[8] = o;
97 $[9] = t4;
98 $[10] = t5;
@@ -110,7 +110,7 @@ function Component(t0) {
110 }
111 let t7;
112 if ($[14] !== t6 || $[15] !== x) {
113 - t7 = <ValidateMemoization inputs={t6} output={x} alwaysCheck={true} />;
113 + t7 = <ValidateMemoization inputs={t6} output={x} />;
114 $[14] = t6;
115 $[15] = x;
116 $[16] = t7;
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/transitivity-add-captured-array-to-itself.tsx
+2 -2
@@ -16,8 +16,8 @@ function Component({a, b}) {
16
17 return (
18 <>
19 - <ValidateMemoization inputs={[a]} output={o} alwaysCheck={true} />;
20 - <ValidateMemoization inputs={[a, b]} output={x} alwaysCheck={true} />;
19 + <ValidateMemoization inputs={[a]} output={o} />;
20 + <ValidateMemoization inputs={[a, b]} output={x} />;
21 </>
22 );
23 }
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/transitivity-capture-createfrom-lambda.expect.md
+2 -2
@@ -21,7 +21,7 @@ function Component({a, b}: {a: number; b: number}) {
21 // mutates x
22 typedMutate(z, b);
23
24 - return <ValidateMemoization inputs={[a, b]} output={x} alwaysCheck={true} />;
24 + return <ValidateMemoization inputs={[a, b]} output={x} />;
25 }
26
27 export const FIXTURE_ENTRYPOINT = {
@@ -85,7 +85,7 @@ function Component(t0) {
85 }
86 let t3;
87 if ($[7] !== t2 || $[8] !== x) {
88 - t3 = <ValidateMemoization inputs={t2} output={x} alwaysCheck={true} />;
88 + t3 = <ValidateMemoization inputs={t2} output={x} />;
89 $[7] = t2;
90 $[8] = x;
91 $[9] = t3;
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/transitivity-capture-createfrom-lambda.tsx
+1 -1
@@ -17,7 +17,7 @@ function Component({a, b}: {a: number; b: number}) {
17 // mutates x
18 typedMutate(z, b);
19
20 - return <ValidateMemoization inputs={[a, b]} output={x} alwaysCheck={true} />;
20 + return <ValidateMemoization inputs={[a, b]} output={x} />;
21 }
22
23 export const FIXTURE_ENTRYPOINT = {
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/transitivity-capture-createfrom.expect.md
+2 -2
@@ -17,7 +17,7 @@ function Component({a, b}: {a: number; b: number}) {
17 // mutates x
18 typedMutate(z, b);
19
20 - return <ValidateMemoization inputs={[a, b]} output={x} alwaysCheck={true} />;
20 + return <ValidateMemoization inputs={[a, b]} output={x} />;
21 }
22
23 export const FIXTURE_ENTRYPOINT = {
@@ -76,7 +76,7 @@ function Component(t0) {
76 }
77 let t3;
78 if ($[7] !== t2 || $[8] !== x) {
79 - t3 = <ValidateMemoization inputs={t2} output={x} alwaysCheck={true} />;
79 + t3 = <ValidateMemoization inputs={t2} output={x} />;
80 $[7] = t2;
81 $[8] = x;
82 $[9] = t3;
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/transitivity-capture-createfrom.tsx
+1 -1
@@ -13,7 +13,7 @@ function Component({a, b}: {a: number; b: number}) {
13 // mutates x
14 typedMutate(z, b);
15
16 - return <ValidateMemoization inputs={[a, b]} output={x} alwaysCheck={true} />;
16 + return <ValidateMemoization inputs={[a, b]} output={x} />;
17 }
18
19 export const FIXTURE_ENTRYPOINT = {
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/transitivity-createfrom-capture.expect.md
+2 -2
@@ -17,7 +17,7 @@ function Component({a, b}) {
17 // does not mutate x, so x should not depend on b
18 typedMutate(z, b);
19
20 - return <ValidateMemoization inputs={[a]} output={x} alwaysCheck={true} />;
20 + return <ValidateMemoization inputs={[a]} output={x} />;
21 }
22
23 export const FIXTURE_ENTRYPOINT = {
@@ -73,7 +73,7 @@ function Component(t0) {
73 }
74 let t4;
75 if ($[4] !== t3 || $[5] !== x) {
76 - t4 = <ValidateMemoization inputs={t3} output={x} alwaysCheck={true} />;
76 + t4 = <ValidateMemoization inputs={t3} output={x} />;
77 $[4] = t3;
78 $[5] = x;
79 $[6] = t4;
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/transitivity-createfrom-capture.tsx
+1 -1
@@ -13,7 +13,7 @@ function Component({a, b}) {
13 // does not mutate x, so x should not depend on b
14 typedMutate(z, b);
15
16 - return <ValidateMemoization inputs={[a]} output={x} alwaysCheck={true} />;
16 + return <ValidateMemoization inputs={[a]} output={x} />;
17 }
18
19 export const FIXTURE_ENTRYPOINT = {
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/transitivity-phi-assign-or-capture.expect.md
+2 -2
@@ -21,7 +21,7 @@ function Component({a, b}) {
21 // could mutate x
22 typedMutate(z, b);
23
24 - return <ValidateMemoization inputs={[a, b]} output={x} alwaysCheck={true} />;
24 + return <ValidateMemoization inputs={[a, b]} output={x} />;
25 }
26
27 export const FIXTURE_ENTRYPOINT = {
@@ -92,7 +92,7 @@ function Component(t0) {
92 }
93 let t4;
94 if ($[9] !== t3 || $[10] !== x) {
95 - t4 = <ValidateMemoization inputs={t3} output={x} alwaysCheck={true} />;
95 + t4 = <ValidateMemoization inputs={t3} output={x} />;
96 $[9] = t3;
97 $[10] = x;
98 $[11] = t4;
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/transitivity-phi-assign-or-capture.tsx
+1 -1
@@ -17,7 +17,7 @@ function Component({a, b}) {
17 // could mutate x
18 typedMutate(z, b);
19
20 - return <ValidateMemoization inputs={[a, b]} output={x} alwaysCheck={true} />;
20 + return <ValidateMemoization inputs={[a, b]} output={x} />;
21 }
22
23 export const FIXTURE_ENTRYPOINT = {
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/typed-identity-function-frozen-input.expect.md
+19 -15
@@ -4,6 +4,7 @@
4 ```javascript
5 // @enableNewMutationAliasingModel
6
7 +import {useMemo} from 'react';
8 import {
9 identity,
10 makeObject_Primitives,
@@ -14,7 +15,7 @@ import {
15
16 function Component({a, b}) {
17 // create a mutable value with input `a`
17 - const x = makeObject_Primitives(a);
18 + const x = useMemo(() => makeObject_Primitives(a), [a]);
19
20 // freeze the value
21 useIdentity(x);
@@ -49,6 +50,7 @@ export const FIXTURE_ENTRYPOINT = {
50 ```javascript
51 import { c as _c } from "react/compiler-runtime"; // @enableNewMutationAliasingModel
52
53 +import { useMemo } from "react";
54 import {
55 identity,
56 makeObject_Primitives,
@@ -61,13 +63,15 @@ function Component(t0) {
63 const $ = _c(7);
64 const { a, b } = t0;
65 let t1;
66 + let t2;
67 if ($[0] !== a) {
65 - t1 = makeObject_Primitives(a);
68 + t2 = makeObject_Primitives(a);
69 $[0] = a;
67 - $[1] = t1;
70 + $[1] = t2;
71 } else {
69 - t1 = $[1];
72 + t2 = $[1];
73 }
74 + t1 = t2;
75 const x = t1;
76
77 useIdentity(x);
@@ -75,24 +79,24 @@ function Component(t0) {
79 const x2 = typedIdentity(x);
80
81 identity(x2, b);
78 - let t2;
82 + let t3;
83 if ($[2] !== a) {
80 - t2 = [a];
84 + t3 = [a];
85 $[2] = a;
82 - $[3] = t2;
86 + $[3] = t3;
87 } else {
84 - t2 = $[3];
88 + t3 = $[3];
89 }
86 - let t3;
87 - if ($[4] !== t2 || $[5] !== x) {
88 - t3 = <ValidateMemoization inputs={t2} output={x} />;
89 - $[4] = t2;
90 + let t4;
91 + if ($[4] !== t3 || $[5] !== x) {
92 + t4 = <ValidateMemoization inputs={t3} output={x} />;
93 + $[4] = t3;
94 $[5] = x;
91 - $[6] = t3;
95 + $[6] = t4;
96 } else {
93 - t3 = $[6];
97 + t4 = $[6];
98 }
95 - return t3;
99 + return t4;
100 }
101
102 export const FIXTURE_ENTRYPOINT = {
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/typed-identity-function-frozen-input.js
+2 -1
@@ -1,5 +1,6 @@
1 // @enableNewMutationAliasingModel
2
3 +import {useMemo} from 'react';
4 import {
5 identity,
6 makeObject_Primitives,
@@ -10,7 +11,7 @@ import {
11
12 function Component({a, b}) {
13 // create a mutable value with input `a`
13 - const x = makeObject_Primitives(a);
14 + const x = useMemo(() => makeObject_Primitives(a), [a]);
15
16 // freeze the value
17 useIdentity(x);
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/weakmap-constructor.expect.md
+30 -6
@@ -2,6 +2,7 @@
2 ## Input
3
4 ```javascript
5 +import {useMemo} from 'react';
6 import {ValidateMemoization} from 'shared-runtime';
7
8 function Component({a, b, c}) {
@@ -13,9 +14,21 @@ function Component({a, b, c}) {
14
15 return (
16 <>
16 - <ValidateMemoization inputs={[a, c]} output={map} />
17 - <ValidateMemoization inputs={[a, c]} output={mapAlias} />
18 - <ValidateMemoization inputs={[b]} output={[hasB]} />
17 + <ValidateMemoization
18 + inputs={[a, c]}
19 + output={map}
20 + onlyCheckCompiled={true}
21 + />
22 + <ValidateMemoization
23 + inputs={[a, c]}
24 + output={mapAlias}
25 + onlyCheckCompiled={true}
26 + />
27 + <ValidateMemoization
28 + inputs={[b]}
29 + output={[hasB]}
30 + onlyCheckCompiled={true}
31 + />
32 </>
33 );
34 }
@@ -44,6 +57,7 @@ export const FIXTURE_ENTRYPOINT = {
57
58 ```javascript
59 import { c as _c } from "react/compiler-runtime";
60 +import { useMemo } from "react";
61 import { ValidateMemoization } from "shared-runtime";
62
63 function Component(t0) {
@@ -76,7 +90,9 @@ function Component(t0) {
90 }
91 let t2;
92 if ($[7] !== map || $[8] !== t1) {
79 - t2 = <ValidateMemoization inputs={t1} output={map} />;
93 + t2 = (
94 + <ValidateMemoization inputs={t1} output={map} onlyCheckCompiled={true} />
95 + );
96 $[7] = map;
97 $[8] = t1;
98 $[9] = t2;
@@ -94,7 +110,13 @@ function Component(t0) {
110 }
111 let t4;
112 if ($[13] !== mapAlias || $[14] !== t3) {
97 - t4 = <ValidateMemoization inputs={t3} output={mapAlias} />;
113 + t4 = (
114 + <ValidateMemoization
115 + inputs={t3}
116 + output={mapAlias}
117 + onlyCheckCompiled={true}
118 + />
119 + );
120 $[13] = mapAlias;
121 $[14] = t3;
122 $[15] = t4;
@@ -119,7 +141,9 @@ function Component(t0) {
141 }
142 let t7;
143 if ($[20] !== t5 || $[21] !== t6) {
122 - t7 = <ValidateMemoization inputs={t5} output={t6} />;
144 + t7 = (
145 + <ValidateMemoization inputs={t5} output={t6} onlyCheckCompiled={true} />
146 + );
147 $[20] = t5;
148 $[21] = t6;
149 $[22] = t7;
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/weakmap-constructor.js
+16 -3
@@ -1,3 +1,4 @@
1 +import {useMemo} from 'react';
2 import {ValidateMemoization} from 'shared-runtime';
3
4 function Component({a, b, c}) {
@@ -9,9 +10,21 @@ function Component({a, b, c}) {
10
11 return (
12 <>
12 - <ValidateMemoization inputs={[a, c]} output={map} />
13 - <ValidateMemoization inputs={[a, c]} output={mapAlias} />
14 - <ValidateMemoization inputs={[b]} output={[hasB]} />
13 + <ValidateMemoization
14 + inputs={[a, c]}
15 + output={map}
16 + onlyCheckCompiled={true}
17 + />
18 + <ValidateMemoization
19 + inputs={[a, c]}
20 + output={mapAlias}
21 + onlyCheckCompiled={true}
22 + />
23 + <ValidateMemoization
24 + inputs={[b]}
25 + output={[hasB]}
26 + onlyCheckCompiled={true}
27 + />
28 </>
29 );
30 }
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/weakset-constructor.expect.md
+30 -6
@@ -2,6 +2,7 @@
2 ## Input
3
4 ```javascript
5 +import {useMemo} from 'react';
6 import {ValidateMemoization} from 'shared-runtime';
7
8 function Component({a, b, c}) {
@@ -13,9 +14,21 @@ function Component({a, b, c}) {
14
15 return (
16 <>
16 - <ValidateMemoization inputs={[a, c]} output={set} />
17 - <ValidateMemoization inputs={[a, c]} output={setAlias} />
18 - <ValidateMemoization inputs={[b]} output={[hasB]} />
17 + <ValidateMemoization
18 + inputs={[a, c]}
19 + output={set}
20 + onlyCheckCompiled={true}
21 + />
22 + <ValidateMemoization
23 + inputs={[a, c]}
24 + output={setAlias}
25 + onlyCheckCompiled={true}
26 + />
27 + <ValidateMemoization
28 + inputs={[b]}
29 + output={[hasB]}
30 + onlyCheckCompiled={true}
31 + />
32 </>
33 );
34 }
@@ -44,6 +57,7 @@ export const FIXTURE_ENTRYPOINT = {
57
58 ```javascript
59 import { c as _c } from "react/compiler-runtime";
60 +import { useMemo } from "react";
61 import { ValidateMemoization } from "shared-runtime";
62
63 function Component(t0) {
@@ -76,7 +90,9 @@ function Component(t0) {
90 }
91 let t2;
92 if ($[7] !== set || $[8] !== t1) {
79 - t2 = <ValidateMemoization inputs={t1} output={set} />;
93 + t2 = (
94 + <ValidateMemoization inputs={t1} output={set} onlyCheckCompiled={true} />
95 + );
96 $[7] = set;
97 $[8] = t1;
98 $[9] = t2;
@@ -94,7 +110,13 @@ function Component(t0) {
110 }
111 let t4;
112 if ($[13] !== setAlias || $[14] !== t3) {
97 - t4 = <ValidateMemoization inputs={t3} output={setAlias} />;
113 + t4 = (
114 + <ValidateMemoization
115 + inputs={t3}
116 + output={setAlias}
117 + onlyCheckCompiled={true}
118 + />
119 + );
120 $[13] = setAlias;
121 $[14] = t3;
122 $[15] = t4;
@@ -119,7 +141,9 @@ function Component(t0) {
141 }
142 let t7;
143 if ($[20] !== t5 || $[21] !== t6) {
122 - t7 = <ValidateMemoization inputs={t5} output={t6} />;
144 + t7 = (
145 + <ValidateMemoization inputs={t5} output={t6} onlyCheckCompiled={true} />
146 + );
147 $[20] = t5;
148 $[21] = t6;
149 $[22] = t7;
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/weakset-constructor.js
+16 -3
@@ -1,3 +1,4 @@
1 +import {useMemo} from 'react';
2 import {ValidateMemoization} from 'shared-runtime';
3
4 function Component({a, b, c}) {
@@ -9,9 +10,21 @@ function Component({a, b, c}) {
10
11 return (
12 <>
12 - <ValidateMemoization inputs={[a, c]} output={set} />
13 - <ValidateMemoization inputs={[a, c]} output={setAlias} />
14 - <ValidateMemoization inputs={[b]} output={[hasB]} />
13 + <ValidateMemoization
14 + inputs={[a, c]}
15 + output={set}
16 + onlyCheckCompiled={true}
17 + />
18 + <ValidateMemoization
19 + inputs={[a, c]}
20 + output={setAlias}
21 + onlyCheckCompiled={true}
22 + />
23 + <ValidateMemoization
24 + inputs={[b]}
25 + output={[hasB]}
26 + onlyCheckCompiled={true}
27 + />
28 </>
29 );
30 }
compiler/packages/snap/src/sprout/shared-runtime.ts
+1 -3
@@ -269,12 +269,10 @@ export function ValidateMemoization({
269 inputs,
270 output: rawOutput,
271 onlyCheckCompiled = false,
272 - alwaysCheck = false,
272 }: {
273 inputs: Array<any>;
274 output: any;
275 onlyCheckCompiled?: boolean;
277 - alwaysCheck?: boolean;
276 }): React.ReactElement {
277 'use no forget';
278 // Wrap rawOutput as it might be a function, which useState would invoke.
@@ -282,7 +280,7 @@ export function ValidateMemoization({
280 const [previousInputs, setPreviousInputs] = React.useState(inputs);
281 const [previousOutput, setPreviousOutput] = React.useState(output);
282 if (
285 - alwaysCheck ||
283 + !onlyCheckCompiled ||
284 (onlyCheckCompiled &&
285 (globalThis as any).__SNAP_EVALUATOR_MODE === 'forget')
286 ) {