Repro function expr hoisting (#29615)

Modified version of @mofeiZ's #29232 with CI passing (had to run
prettier)

---------

Co-authored-by: Mofei Zhang <feifei0@meta.com>
This commit is contained in:
Joseph Savona
2024-05-28 10:06:05 -07:00
committed by GitHub
parent 6f23540c7d
commit 4ec6a6f714
6 changed files with 291 additions and 0 deletions

View File

@@ -0,0 +1,93 @@
## Input
```javascript
import { Stringify } from "shared-runtime";
/**
* We currently hoist the accessed properties of function expressions,
* regardless of control flow. This is simply because we wrote support for
* function expressions before doing a lot of work in PropagateScopeDeps
* to handle conditionally accessed dependencies.
*
* Current evaluator error:
* Found differences in evaluator results
* Non-forget (expected):
* (kind: ok) <div>{"shouldInvokeFns":true,"callback":{"kind":"Function","result":null}}</div>
* Forget:
* (kind: exception) Cannot read properties of null (reading 'prop')
*/
function Component({ obj, isObjNull }) {
const callback = () => {
if (!isObjNull) {
return obj.prop;
} else {
return null;
}
};
return <Stringify shouldInvokeFns={true} callback={callback} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ obj: null, isObjNull: true }],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { Stringify } from "shared-runtime";
/**
* We currently hoist the accessed properties of function expressions,
* regardless of control flow. This is simply because we wrote support for
* function expressions before doing a lot of work in PropagateScopeDeps
* to handle conditionally accessed dependencies.
*
* Current evaluator error:
* Found differences in evaluator results
* Non-forget (expected):
* (kind: ok) <div>{"shouldInvokeFns":true,"callback":{"kind":"Function","result":null}}</div>
* Forget:
* (kind: exception) Cannot read properties of null (reading 'prop')
*/
function Component(t0) {
const $ = _c(5);
const { obj, isObjNull } = t0;
let t1;
if ($[0] !== isObjNull || $[1] !== obj.prop) {
t1 = () => {
if (!isObjNull) {
return obj.prop;
} else {
return null;
}
};
$[0] = isObjNull;
$[1] = obj.prop;
$[2] = t1;
} else {
t1 = $[2];
}
const callback = t1;
let t2;
if ($[3] !== callback) {
t2 = <Stringify shouldInvokeFns={true} callback={callback} />;
$[3] = callback;
$[4] = t2;
} else {
t2 = $[4];
}
return t2;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ obj: null, isObjNull: true }],
};
```

View File

@@ -0,0 +1,30 @@
import { Stringify } from "shared-runtime";
/**
* We currently hoist the accessed properties of function expressions,
* regardless of control flow. This is simply because we wrote support for
* function expressions before doing a lot of work in PropagateScopeDeps
* to handle conditionally accessed dependencies.
*
* Current evaluator error:
* Found differences in evaluator results
* Non-forget (expected):
* (kind: ok) <div>{"shouldInvokeFns":true,"callback":{"kind":"Function","result":null}}</div>
* Forget:
* (kind: exception) Cannot read properties of null (reading 'prop')
*/
function Component({ obj, isObjNull }) {
const callback = () => {
if (!isObjNull) {
return obj.prop;
} else {
return null;
}
};
return <Stringify shouldInvokeFns={true} callback={callback} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ obj: null, isObjNull: true }],
};

View File

@@ -0,0 +1,119 @@
## Input
```javascript
import invariant from "invariant";
import {
makeObject_Primitives,
mutate,
sum,
useIdentity,
} from "shared-runtime";
/**
* Exposes fundamental issue with pruning 'non-reactive' dependencies + flattening
* those scopes. Here, `z`'s original memo block is removed due to the inner hook call.
* However, we also infer that `z` is non-reactive and does not need to be a memo
* dependency.
*
* Current evaluator error:
* Found differences in evaluator results
* Non-forget (expected):
* (kind: ok) [4,{"a":0,"b":"value1","c":true,"wat0":"joe"}]
* [4,{"a":0,"b":"value1","c":true,"wat0":"joe"}]
* [5,{"a":0,"b":"value1","c":true,"wat0":"joe"}]
* Forget:
* (kind: ok) [4,{"a":0,"b":"value1","c":true,"wat0":"joe"}]
* [[ (exception in render) Invariant Violation: oh no! ]]
* [5,{"a":0,"b":"value1","c":true,"wat0":"joe"}]
*/
function MyApp({ count }) {
const z = makeObject_Primitives();
const x = useIdentity(2);
const y = sum(x, count);
mutate(z);
const thing = [y, z];
if (thing[1] !== z) {
invariant(false, "oh no!");
}
return thing;
}
export const FIXTURE_ENTRYPOINT = {
fn: MyApp,
params: [{ count: 2 }],
sequentialRenders: [{ count: 2 }, { count: 2 }, { count: 3 }],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import invariant from "invariant";
import {
makeObject_Primitives,
mutate,
sum,
useIdentity,
} from "shared-runtime";
/**
* Exposes fundamental issue with pruning 'non-reactive' dependencies + flattening
* those scopes. Here, `z`'s original memo block is removed due to the inner hook call.
* However, we also infer that `z` is non-reactive and does not need to be a memo
* dependency.
*
* Current evaluator error:
* Found differences in evaluator results
* Non-forget (expected):
* (kind: ok) [4,{"a":0,"b":"value1","c":true,"wat0":"joe"}]
* [4,{"a":0,"b":"value1","c":true,"wat0":"joe"}]
* [5,{"a":0,"b":"value1","c":true,"wat0":"joe"}]
* Forget:
* (kind: ok) [4,{"a":0,"b":"value1","c":true,"wat0":"joe"}]
* [[ (exception in render) Invariant Violation: oh no! ]]
* [5,{"a":0,"b":"value1","c":true,"wat0":"joe"}]
*/
function MyApp(t0) {
const $ = _c(5);
const { count } = t0;
const z = makeObject_Primitives();
const x = useIdentity(2);
let t1;
if ($[0] !== x || $[1] !== count) {
t1 = sum(x, count);
$[0] = x;
$[1] = count;
$[2] = t1;
} else {
t1 = $[2];
}
const y = t1;
mutate(z);
let t2;
if ($[3] !== y) {
t2 = [y, z];
$[3] = y;
$[4] = t2;
} else {
t2 = $[4];
}
const thing = t2;
if (thing[1] !== z) {
invariant(false, "oh no!");
}
return thing;
}
export const FIXTURE_ENTRYPOINT = {
fn: MyApp,
params: [{ count: 2 }],
sequentialRenders: [{ count: 2 }, { count: 2 }, { count: 3 }],
};
```

View File

@@ -0,0 +1,43 @@
import invariant from "invariant";
import {
makeObject_Primitives,
mutate,
sum,
useIdentity,
} from "shared-runtime";
/**
* Exposes fundamental issue with pruning 'non-reactive' dependencies + flattening
* those scopes. Here, `z`'s original memo block is removed due to the inner hook call.
* However, we also infer that `z` is non-reactive and does not need to be a memo
* dependency.
*
* Current evaluator error:
* Found differences in evaluator results
* Non-forget (expected):
* (kind: ok) [4,{"a":0,"b":"value1","c":true,"wat0":"joe"}]
* [4,{"a":0,"b":"value1","c":true,"wat0":"joe"}]
* [5,{"a":0,"b":"value1","c":true,"wat0":"joe"}]
* Forget:
* (kind: ok) [4,{"a":0,"b":"value1","c":true,"wat0":"joe"}]
* [[ (exception in render) Invariant Violation: oh no! ]]
* [5,{"a":0,"b":"value1","c":true,"wat0":"joe"}]
*/
function MyApp({ count }) {
const z = makeObject_Primitives();
const x = useIdentity(2);
const y = sum(x, count);
mutate(z);
const thing = [y, z];
if (thing[1] !== z) {
invariant(false, "oh no!");
}
return thing;
}
export const FIXTURE_ENTRYPOINT = {
fn: MyApp,
params: [{ count: 2 }],
sequentialRenders: [{ count: 2 }, { count: 2 }, { count: 3 }],
};

View File

@@ -486,6 +486,8 @@ const skipFilter = new Set([
// bugs
"bug-invalid-reactivity-value-block",
"bug-invalid-pruned-scope-leaks-value",
"bug-invalid-hoisting-functionexpr",
"original-reactive-scopes-fork/bug-nonmutating-capture-in-unsplittable-memo-block",
"original-reactive-scopes-fork/bug-hoisted-declaration-with-scope",

View File

@@ -176,6 +176,10 @@ export function useNoAlias(...args: Array<any>): object {
return noAliasObject;
}
export function useIdentity<T>(arg: T): T {
return arg;
}
export function invoke<T extends Array<any>, ReturnType>(
fn: (...input: T) => ReturnType,
...params: T