[compiler] Repro for missing memoization due to inferred mutation
This fixture bails out on ValidatePreserveExistingMemo but would ideally memoize since the original memoization is safe. It's trivial to make it pass by commenting out the commented line (`LogEvent.log(() => object)`). I would expect the compiler to infer this as possible mutation of `logData`, since `object` captures a reference to `logData`. But somehow `logData` is getting memoized successfully, but we still infer the callback, `setCurrentIndex`, as having a mutable range that extends to the `setCurrentIndex()` call after the useCallback. ghstack-source-id: 4f82e345102f82f6da74de3f9014af263d016762 Pull Request resolved: https://github.com/facebook/react/pull/30764
Joe Savona committed
Aug 21, 2024 at 14:07 UTC
f7bb717e9e9f876c6a466a5f6d31004c7f7590c5
3 files changed
+133
-2
compiler/packages/babel-plugin-react-compiler/src/Validation/ValidatePreservedManualMemoization.ts
+4
-2
@@ -23,7 +23,7 @@ import {
23
ScopeId,
24
SourceLocation,
25
} from '../HIR';
26
-import {printManualMemoDependency} from '../HIR/PrintHIR';
26
+import {printIdentifier, printManualMemoDependency} from '../HIR/PrintHIR';
27
import {eachInstructionValueOperand} from '../HIR/visitors';
28
import {collectMaybeMemoDependencies} from '../Inference/DropManualMemoization';
29
import {
@@ -537,7 +537,9 @@ class Visitor extends ReactiveFunctionVisitor<VisitorState> {
537
state.errors.push({
538
reason:
539
'React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. This value was memoized in source but not in compilation output.',
540
- description: null,
540
+ description: DEBUG
541
+ ? `${printIdentifier(identifier)} was not memoized`
542
+ : null,
543
severity: ErrorSeverity.CannotPreserveMemoization,
544
loc,
545
suggestions: null,
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-missed-memoization-from-inferred-mutation-in-logger.expect.md
new
+83
@@ -0,0 +1,83 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+// @flow @validatePreserveExistingMemoizationGuarantees
6
+import {useFragment} from 'react-relay';
7
+import LogEvent from 'LogEvent';
8
+import {useCallback, useMemo} from 'react';
9
+
10
+component Component(id) {
11
+ const {data} = useFragment();
12
+ const items = data.items.edges;
13
+
14
+ const [prevId, setPrevId] = useState(id);
15
+ const [index, setIndex] = useState(0);
16
+
17
+ const logData = useMemo(() => {
18
+ const item = items[index];
19
+ return {
20
+ key: item.key ?? '',
21
+ };
22
+ }, [index, items]);
23
+
24
+ const setCurrentIndex = useCallback(
25
+ (index: number) => {
26
+ const object = {
27
+ tracking: logData.key,
28
+ };
29
+ // We infer that this may mutate `object`, which in turn aliases
30
+ // data from `logData`, such that `logData` may be mutated.
31
+ LogEvent.log(() => object);
32
+ setIndex(index);
33
+ },
34
+ [index, logData, items]
35
+ );
36
+
37
+ if (prevId !== id) {
38
+ setPrevId(id);
39
+ setCurrentIndex(0);
40
+ }
41
+
42
+ return (
43
+ <Foo
44
+ index={index}
45
+ items={items}
46
+ current={mediaList[index]}
47
+ setCurrentIndex={setCurrentIndex}
48
+ />
49
+ );
50
+}
51
+
52
+```
53
+
54
+
55
+## Error
56
+
57
+```
58
+ 19 |
59
+ 20 | const setCurrentIndex = useCallback(
60
+> 21 | (index: number) => {
61
+ | ^^^^^^^^^^^^^^^^^^^^
62
+> 22 | const object = {
63
+ | ^^^^^^^^^^^^^^^^^^^^^^
64
+> 23 | tracking: logData.key,
65
+ | ^^^^^^^^^^^^^^^^^^^^^^
66
+> 24 | };
67
+ | ^^^^^^^^^^^^^^^^^^^^^^
68
+> 25 | // We infer that this may mutate `object`, which in turn aliases
69
+ | ^^^^^^^^^^^^^^^^^^^^^^
70
+> 26 | // data from `logData`, such that `logData` may be mutated.
71
+ | ^^^^^^^^^^^^^^^^^^^^^^
72
+> 27 | LogEvent.log(() => object);
73
+ | ^^^^^^^^^^^^^^^^^^^^^^
74
+> 28 | setIndex(index);
75
+ | ^^^^^^^^^^^^^^^^^^^^^^
76
+> 29 | },
77
+ | ^^^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. This value was memoized in source but not in compilation output. (21:29)
78
+ 30 | [index, logData, items]
79
+ 31 | );
80
+ 32 |
81
+```
82
+
83
+
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-missed-memoization-from-inferred-mutation-in-logger.js
new
+46
@@ -0,0 +1,46 @@
1
+// @flow @validatePreserveExistingMemoizationGuarantees
2
+import {useFragment} from 'react-relay';
3
+import LogEvent from 'LogEvent';
4
+import {useCallback, useMemo} from 'react';
5
+
6
+component Component(id) {
7
+ const {data} = useFragment();
8
+ const items = data.items.edges;
9
+
10
+ const [prevId, setPrevId] = useState(id);
11
+ const [index, setIndex] = useState(0);
12
+
13
+ const logData = useMemo(() => {
14
+ const item = items[index];
15
+ return {
16
+ key: item.key ?? '',
17
+ };
18
+ }, [index, items]);
19
+
20
+ const setCurrentIndex = useCallback(
21
+ (index: number) => {
22
+ const object = {
23
+ tracking: logData.key,
24
+ };
25
+ // We infer that this may mutate `object`, which in turn aliases
26
+ // data from `logData`, such that `logData` may be mutated.
27
+ LogEvent.log(() => object);
28
+ setIndex(index);
29
+ },
30
+ [index, logData, items]
31
+ );
32
+
33
+ if (prevId !== id) {
34
+ setPrevId(id);
35
+ setCurrentIndex(0);
36
+ }
37
+
38
+ return (
39
+ <Foo
40
+ index={index}
41
+ items={items}
42
+ current={mediaList[index]}
43
+ setCurrentIndex={setCurrentIndex}
44
+ />
45
+ );
46
+}