[compiler][repro] False positives for ValidatePreserveMemo

ghstack-source-id: 7fa94fea86
Pull Request resolved: https://github.com/facebook/react/pull/30629
This commit is contained in:
Mofei Zhang
2024-08-07 16:11:34 -07:00
parent 2bd415355e
commit 2f8ff3deb2
6 changed files with 235 additions and 0 deletions

View File

@@ -0,0 +1,57 @@
## Input
```javascript
// @validatePreserveExistingMemoizationGuarantees:true
import {useCallback} from 'react';
import {Stringify, useIdentity} from 'shared-runtime';
/**
* Here, the *inferred* dependencies of cb are `a` and `t1 = LoadContext capture x_@1`.
* - t1 does not have a scope as it captures `x` after x's mutable range
* - `x` is a context variable, which means its mutable range extends to all
* references / aliases.
* - `a`, `b`, and `x` get the same mutable range due to potential aliasing.
*
* We currently bail out because `a` has a scope and is not transitively memoized
* (as its scope is pruned due to a hook call)
*/
function useBar({a, b}, cond) {
let x = useIdentity({val: 3});
if (cond) {
x = b;
}
const cb = useCallback(() => {
return [a, x];
}, [a, x]);
return <Stringify cb={cb} shouldInvoke={true} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: useBar,
params: [{a: 1, b: 2}, true],
};
```
## Error
```
20 | }
21 |
> 22 | const cb = useCallback(() => {
| ^^^^^^^
> 23 | return [a, x];
| ^^^^^^^^^^^^^^^^^^
> 24 | }, [a, x]);
| ^^^^ 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. (22:24)
25 |
26 | return <Stringify cb={cb} shouldInvoke={true} />;
27 | }
```

View File

@@ -0,0 +1,32 @@
// @validatePreserveExistingMemoizationGuarantees:true
import {useCallback} from 'react';
import {Stringify, useIdentity} from 'shared-runtime';
/**
* Here, the *inferred* dependencies of cb are `a` and `t1 = LoadContext capture x_@1`.
* - t1 does not have a scope as it captures `x` after x's mutable range
* - `x` is a context variable, which means its mutable range extends to all
* references / aliases.
* - `a`, `b`, and `x` get the same mutable range due to potential aliasing.
*
* We currently bail out because `a` has a scope and is not transitively memoized
* (as its scope is pruned due to a hook call)
*/
function useBar({a, b}, cond) {
let x = useIdentity({val: 3});
if (cond) {
x = b;
}
const cb = useCallback(() => {
return [a, x];
}, [a, x]);
return <Stringify cb={cb} shouldInvoke={true} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: useBar,
params: [{a: 1, b: 2}, true],
};

View File

@@ -0,0 +1,42 @@
## Input
```javascript
// @validatePreserveExistingMemoizationGuarantees
import {useCallback} from 'react';
import {identity, useIdentity} from 'shared-runtime';
/**
* Repro showing a manual memo whose declaration (useCallback's 1st argument)
* is memoized, but not its dependency (x). In this case, `x`'s scope is pruned
* due to hook-call flattening.
*/
function useFoo(a) {
const x = identity(a);
useIdentity(2);
mutate(x);
return useCallback(() => [x, []], [x]);
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [3],
};
```
## Error
```
13 | mutate(x);
14 |
> 15 | return useCallback(() => [x, []], [x]);
| ^^^^^^^^^^^^^ 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. (15:15)
16 | }
17 |
18 | export const FIXTURE_ENTRYPOINT = {
```

View File

@@ -0,0 +1,21 @@
// @validatePreserveExistingMemoizationGuarantees
import {useCallback} from 'react';
import {identity, useIdentity} from 'shared-runtime';
/**
* Repro showing a manual memo whose declaration (useCallback's 1st argument)
* is memoized, but not its dependency (x). In this case, `x`'s scope is pruned
* due to hook-call flattening.
*/
function useFoo(a) {
const x = identity(a);
useIdentity(2);
mutate(x);
return useCallback(() => [x, []], [x]);
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [3],
};

View File

@@ -0,0 +1,52 @@
## Input
```javascript
// @validatePreserveExistingMemoizationGuarantees:true
import {useMemo} from 'react';
import {arrayPush} from 'shared-runtime';
/**
* Repro showing differences between mutable ranges and scope ranges.
*
* For useMemo dependency `x`:
* - mutable range ends after the `arrayPush(x, b)` instruction
* - scope range is extended due to MergeOverlappingScopes
*
* Since manual memo deps are guaranteed to be named (guaranteeing valid
* codegen), it's correct to take a dependency on a dep *before* the end
* of its scope (but after its mutable range ends).
*/
function useFoo(a, b) {
const x = [];
const y = [];
arrayPush(x, b);
const result = useMemo(() => {
return [Math.max(x[1], a)];
}, [a, x]);
arrayPush(y, 3);
return {result, y};
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [1, 2],
};
```
## Error
```
21 | const result = useMemo(() => {
22 | return [Math.max(x[1], a)];
> 23 | }, [a, x]);
| ^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. This dependency may be mutated later, which could cause the value to change unexpectedly (23:23)
24 | arrayPush(y, 3);
25 | return {result, y};
26 | }
```

View File

@@ -0,0 +1,31 @@
// @validatePreserveExistingMemoizationGuarantees:true
import {useMemo} from 'react';
import {arrayPush} from 'shared-runtime';
/**
* Repro showing differences between mutable ranges and scope ranges.
*
* For useMemo dependency `x`:
* - mutable range ends after the `arrayPush(x, b)` instruction
* - scope range is extended due to MergeOverlappingScopes
*
* Since manual memo deps are guaranteed to be named (guaranteeing valid
* codegen), it's correct to take a dependency on a dep *before* the end
* of its scope (but after its mutable range ends).
*/
function useFoo(a, b) {
const x = [];
const y = [];
arrayPush(x, b);
const result = useMemo(() => {
return [Math.max(x[1], a)];
}, [a, x]);
arrayPush(y, 3);
return {result, y};
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [1, 2],
};