@samitouri / QOS-React / commits / f7bb717e9e

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