@samitouri / QOS-React / commits / fc0df475c4

[compiler] Inferred deps must match exact optionality of manual deps

To prevent any difference in behavior, we check that the optionality of the inferred deps exactly matches the optionality of the manual dependencies. This required a fix, I was incorrectly inferring optionality of manual deps (they're only optional if OptionalTerminal.optional is true) - for nested cases of mixed optional/non-optional. ghstack-source-id: afd49e89cc3194eb3c317ca7434d3fa948896bff Pull Request resolved: https://github.com/facebook/react/pull/30840

Joe Savona committed Aug 28, 2024 at 15:16 UTC fc0df475c4417670272b819bad92590b310bcdaa
7 files changed +57 -72
compiler/packages/babel-plugin-react-compiler/src/Inference/DropManualMemoization.ts
+1 -1
@@ -488,7 +488,7 @@ export function dropManualMemoization(func: HIRFunction): void {
488 function findOptionalPlaces(fn: HIRFunction): Set<IdentifierId> {
489 const optionals = new Set<IdentifierId>();
490 for (const [, block] of fn.body.blocks) {
491 - if (block.terminal.kind === 'optional') {
491 + if (block.terminal.kind === 'optional' && block.terminal.optional) {
492 const optionalTerminal = block.terminal;
493 let testBlock = fn.body.blocks.get(block.terminal.test)!;
494 loop: while (true) {
compiler/packages/babel-plugin-react-compiler/src/Validation/ValidatePreservedManualMemoization.ts
+1 -1
@@ -170,7 +170,7 @@ function compareDeps(
170 if (inferred.path[i].property !== source.path[i].property) {
171 isSubpath = false;
172 break;
173 - } else if (inferred.path[i].optional && !source.path[i].optional) {
173 + } else if (inferred.path[i].optional !== source.path[i].optional) {
174 /**
175 * The inferred path must be at least as precise as the manual path:
176 * if the inferred path is optional, then the source path must have
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-optional-member-expression-as-memo-dep-non-optional-in-body.expect.md new
+38
@@ -0,0 +1,38 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +// @validatePreserveExistingMemoizationGuarantees
6 +function Component(props) {
7 + const data = useMemo(() => {
8 + // actual code is non-optional
9 + return props.items.edges.nodes ?? [];
10 + // deps are optional
11 + }, [props.items?.edges?.nodes]);
12 + return <Foo data={data} />;
13 +}
14 +
15 +```
16 +
17 +
18 +## Error
19 +
20 +```
21 + 1 | // @validatePreserveExistingMemoizationGuarantees
22 + 2 | function Component(props) {
23 +> 3 | const data = useMemo(() => {
24 + | ^^^^^^^
25 +> 4 | // actual code is non-optional
26 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
27 +> 5 | return props.items.edges.nodes ?? [];
28 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
29 +> 6 | // deps are optional
30 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
31 +> 7 | }, [props.items?.edges?.nodes]);
32 + | ^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (3:7)
33 + 8 | return <Foo data={data} />;
34 + 9 | }
35 + 10 |
36 +```
37 +
38 +
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-optional-member-expression-as-memo-dep-non-optional-in-body.js renamed
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-as-memo-dep-non-optional-in-body.expect.md deleted
-50
@@ -1,50 +0,0 @@
1 -
2 -## Input
3 -
4 -```javascript
5 -// @validatePreserveExistingMemoizationGuarantees
6 -function Component(props) {
7 - const data = useMemo(() => {
8 - // actual code is non-optional
9 - return props.items.edges.nodes ?? [];
10 - // deps are optional
11 - }, [props.items?.edges?.nodes]);
12 - return <Foo data={data} />;
13 -}
14 -
15 -```
16 -
17 -## Code
18 -
19 -```javascript
20 -import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees
21 -function Component(props) {
22 - const $ = _c(4);
23 -
24 - props.items?.edges?.nodes;
25 - let t0;
26 - let t1;
27 - if ($[0] !== props.items.edges.nodes) {
28 - t1 = props.items.edges.nodes ?? [];
29 - $[0] = props.items.edges.nodes;
30 - $[1] = t1;
31 - } else {
32 - t1 = $[1];
33 - }
34 - t0 = t1;
35 - const data = t0;
36 - let t2;
37 - if ($[2] !== data) {
38 - t2 = <Foo data={data} />;
39 - $[2] = data;
40 - $[3] = t2;
41 - } else {
42 - t2 = $[3];
43 - }
44 - return t2;
45 -}
46 -
47 -```
48 -
49 -### Eval output
50 -(kind: exception) Fixture not implemented
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-single-with-unconditional.expect.md
+15 -18
@@ -10,8 +10,8 @@ function Component(props) {
10 x.push(props?.items);
11 x.push(props.items);
12 return x;
13 - }, [props?.items]);
14 - return <ValidateMemoization inputs={[props?.items]} output={data} />;
13 + }, [props.items]);
14 + return <ValidateMemoization inputs={[props.items]} output={data} />;
15 }
16
17 ```
@@ -23,8 +23,6 @@ import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMe
23 import { ValidateMemoization } from "shared-runtime";
24 function Component(props) {
25 const $ = _c(7);
26 -
27 - props?.items;
26 let t0;
27 let x;
28 if ($[0] !== props.items) {
@@ -38,25 +36,24 @@ function Component(props) {
36 }
37 t0 = x;
38 const data = t0;
41 - const t1 = props?.items;
42 - let t2;
43 - if ($[2] !== t1) {
44 - t2 = [t1];
45 - $[2] = t1;
46 - $[3] = t2;
39 + let t1;
40 + if ($[2] !== props.items) {
41 + t1 = [props.items];
42 + $[2] = props.items;
43 + $[3] = t1;
44 } else {
48 - t2 = $[3];
45 + t1 = $[3];
46 }
50 - let t3;
51 - if ($[4] !== t2 || $[5] !== data) {
52 - t3 = <ValidateMemoization inputs={t2} output={data} />;
53 - $[4] = t2;
47 + let t2;
48 + if ($[4] !== t1 || $[5] !== data) {
49 + t2 = <ValidateMemoization inputs={t1} output={data} />;
50 + $[4] = t1;
51 $[5] = data;
55 - $[6] = t3;
52 + $[6] = t2;
53 } else {
57 - t3 = $[6];
54 + t2 = $[6];
55 }
59 - return t3;
56 + return t2;
57 }
58
59 ```
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-single-with-unconditional.js
+2 -2
@@ -6,6 +6,6 @@ function Component(props) {
6 x.push(props?.items);
7 x.push(props.items);
8 return x;
9 - }, [props?.items]);
10 - return <ValidateMemoization inputs={[props?.items]} output={data} />;
9 + }, [props.items]);
10 + return <ValidateMemoization inputs={[props.items]} output={data} />;
11 }