Skip to content

Commit

Permalink
Repro function expr hoisting (facebook#29615)
Browse files Browse the repository at this point in the history
Modified version of @mofeiZ's facebook#29232 with CI passing (had to run
prettier)

---------

Co-authored-by: Mofei Zhang <feifei0@meta.com>
  • Loading branch information
2 people authored and nikeee committed May 28, 2024
1 parent cb4a4e4 commit 057666b
Show file tree
Hide file tree
Showing 6 changed files with 291 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -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 }],
};

```
Original file line number Diff line number Diff line change
@@ -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 }],
};
Original file line number Diff line number Diff line change
@@ -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 }],
};

```
Original file line number Diff line number Diff line change
@@ -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 }],
};
2 changes: 2 additions & 0 deletions compiler/packages/snap/src/SproutTodoFilter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",

Expand Down
4 changes: 4 additions & 0 deletions compiler/packages/snap/src/sprout/shared-runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 057666b

Please sign in to comment.