Skip to content

Commit

Permalink
[compiler] Repro for missing memoization due to inferred mutation
Browse files Browse the repository at this point in the history
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: #30764
  • Loading branch information
josephsavona committed Aug 22, 2024
1 parent eb3ad06 commit f7bb717
Show file tree
Hide file tree
Showing 3 changed files with 133 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import {
ScopeId,
SourceLocation,
} from '../HIR';
import {printManualMemoDependency} from '../HIR/PrintHIR';
import {printIdentifier, printManualMemoDependency} from '../HIR/PrintHIR';
import {eachInstructionValueOperand} from '../HIR/visitors';
import {collectMaybeMemoDependencies} from '../Inference/DropManualMemoization';
import {
Expand Down Expand Up @@ -537,7 +537,9 @@ class Visitor extends ReactiveFunctionVisitor<VisitorState> {
state.errors.push({
reason:
'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.',
description: null,
description: DEBUG
? `${printIdentifier(identifier)} was not memoized`
: null,
severity: ErrorSeverity.CannotPreserveMemoization,
loc,
suggestions: null,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@

## Input

```javascript
// @flow @validatePreserveExistingMemoizationGuarantees
import {useFragment} from 'react-relay';
import LogEvent from 'LogEvent';
import {useCallback, useMemo} from 'react';

component Component(id) {
const {data} = useFragment();
const items = data.items.edges;

const [prevId, setPrevId] = useState(id);
const [index, setIndex] = useState(0);

const logData = useMemo(() => {
const item = items[index];
return {
key: item.key ?? '',
};
}, [index, items]);

const setCurrentIndex = useCallback(
(index: number) => {
const object = {
tracking: logData.key,
};
// We infer that this may mutate `object`, which in turn aliases
// data from `logData`, such that `logData` may be mutated.
LogEvent.log(() => object);
setIndex(index);
},
[index, logData, items]
);

if (prevId !== id) {
setPrevId(id);
setCurrentIndex(0);
}

return (
<Foo
index={index}
items={items}
current={mediaList[index]}
setCurrentIndex={setCurrentIndex}
/>
);
}

```
## Error
```
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 | },
| ^^^^^^ 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)
30 | [index, logData, items]
31 | );
32 |
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// @flow @validatePreserveExistingMemoizationGuarantees
import {useFragment} from 'react-relay';
import LogEvent from 'LogEvent';
import {useCallback, useMemo} from 'react';

component Component(id) {
const {data} = useFragment();
const items = data.items.edges;

const [prevId, setPrevId] = useState(id);
const [index, setIndex] = useState(0);

const logData = useMemo(() => {
const item = items[index];
return {
key: item.key ?? '',
};
}, [index, items]);

const setCurrentIndex = useCallback(
(index: number) => {
const object = {
tracking: logData.key,
};
// We infer that this may mutate `object`, which in turn aliases
// data from `logData`, such that `logData` may be mutated.
LogEvent.log(() => object);
setIndex(index);
},
[index, logData, items]
);

if (prevId !== id) {
setPrevId(id);
setCurrentIndex(0);
}

return (
<Foo
index={index}
items={items}
current={mediaList[index]}
setCurrentIndex={setCurrentIndex}
/>
);
}

0 comments on commit f7bb717

Please sign in to comment.