[compiler] Allow inferred non-optional paths when manual deps were optional
If the inferred deps are more precise (non-optional) than the manual deps (optional) it should pass validation. The other direction also seems like it would be fine - inferring optional deps when the original was non-optional - but for now let's keep the "at least as precise" rule. ghstack-source-id: 9f7a99ee5f7caa2c2d96f70f360e4320bac3de2d Pull Request resolved: https://github.com/facebook/react/pull/30816
Joe Savona committed
Aug 28, 2024 at 10:52 UTC
9180a37fba0c9ad642bfc6e1c2839f88f66485ab
3 files changed
+67
-4
compiler/packages/babel-plugin-react-compiler/src/Validation/ValidatePreservedManualMemoization.ts
+8
-4
@@ -167,12 +167,16 @@ function compareDeps(
167
168
let isSubpath = true;
169
for (let i = 0; i < Math.min(inferred.path.length, source.path.length); i++) {
170
- if (
171
- inferred.path[i].property !== source.path[i].property ||
172
- inferred.path[i].optional !== source.path[i].optional
173
- ) {
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) {
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
177
+ * been optional too.
178
+ */
179
+ return CompareDependencyResult.PathDifference;
180
}
181
}
182
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-as-memo-dep-non-optional-in-body.expect.md
new
+50
@@ -0,0 +1,50 @@
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-as-memo-dep-non-optional-in-body.js
new
+9
@@ -0,0 +1,9 @@
1
+// @validatePreserveExistingMemoizationGuarantees
2
+function Component(props) {
3
+ const data = useMemo(() => {
4
+ // actual code is non-optional
5
+ return props.items.edges.nodes ?? [];
6
+ // deps are optional
7
+ }, [props.items?.edges?.nodes]);
8
+ return <Foo data={data} />;
9
+}