[bugfix] do not hoist computed memberpaths in lambdas

This commit is contained in:
Mofei Zhang
2023-10-30 12:56:50 -04:00
parent 3e812df89c
commit e2836eec5b
7 changed files with 154 additions and 51 deletions
@@ -3530,6 +3530,14 @@ function lowerAssignment(
}
}
function isValidDependency(path: NodePath<t.MemberExpression>): boolean {
const parent: NodePath<t.Node> = path.parentPath;
return (
!path.node.computed &&
!(parent.isCallExpression() && parent.get("callee") === path)
);
}
function captureScopes({ from, to }: { from: Scope; to: Scope }): Set<Scope> {
let scopes: Set<Scope> = new Set();
while (from) {
@@ -3579,7 +3587,7 @@ function gatherCapturedDeps(
| NodePath<t.JSXOpeningElement>
): void {
// Base context variable to depend on
let baseIdentifier: NodePath<t.Identifier | t.JSXIdentifier>;
let baseIdentifier: NodePath<t.Identifier> | NodePath<t.JSXIdentifier>;
// Base expression to depend on, which (for now) may contain non side-effectful
// member expressions
let dependency:
@@ -3604,31 +3612,36 @@ function gatherCapturedDeps(
dependency = current;
} else if (path.isMemberExpression()) {
// Calculate baseIdentifier
let current: NodePath<Expression> = path;
while (current.isMemberExpression()) {
current = current.get("object");
let currentId: NodePath<Expression> = path;
while (currentId.isMemberExpression()) {
currentId = currentId.get("object");
}
if (!current.isIdentifier()) {
if (!currentId.isIdentifier()) {
return;
}
baseIdentifier = current;
baseIdentifier = currentId;
// Get the expression to depend on, which may involve PropertyLoads
// for member expressions
current =
path.parent.type === "CallExpression" &&
path.parent.callee === path.node
? path.get("object")
: path;
while (current.isMemberExpression() && current.node.computed) {
// computed nodes may contain side-effectful subexpressions
current = current.get("object");
let currentDep:
| NodePath<t.MemberExpression>
| NodePath<t.Identifier>
| NodePath<t.JSXIdentifier> = baseIdentifier;
while (true) {
const nextDep: null | NodePath<t.Node> = currentDep.parentPath;
if (
nextDep &&
nextDep.isMemberExpression() &&
isValidDependency(nextDep)
) {
currentDep = nextDep;
} else {
break;
}
}
invariant(
current.isMemberExpression() || current.isIdentifier(),
"Internal invariant broken in BuildHIR, unexpected type for capturedDep"
);
dependency = current;
dependency = currentDep;
} else {
baseIdentifier = path;
dependency = path;
@@ -1,30 +0,0 @@
## Input
```javascript
import { invoke } from "shared-runtime";
function Foo() {
const x = [{ value: 0 }, { value: 1 }, { value: 2 }];
const foo = (param: number) => {
return x[param].value;
};
return invoke(foo, 1);
}
export const FIXTURE_ENTRYPONT = {
fn: Foo,
params: [{}],
};
```
## Error
```
[ReactForget] Todo: EnterSSA: Expected identifier to be defined before being used. Identifier param$10 is undefined (5:5)
```
@@ -0,0 +1,52 @@
## Input
```javascript
import { CONST_NUMBER0, invoke } from "shared-runtime";
function Foo() {
const x = [{ value: 0 }, { value: 1 }, { value: 2 }];
const param = CONST_NUMBER0;
const foo = () => {
return x[param].value;
};
return invoke(foo);
}
export const FIXTURE_ENTRYPOINT = {
fn: Foo,
params: [{}],
};
```
## Code
```javascript
import { unstable_useMemoCache as useMemoCache } from "react";
import { CONST_NUMBER0, invoke } from "shared-runtime";
function Foo() {
const $ = useMemoCache(1);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
const x = [{ value: 0 }, { value: 1 }, { value: 2 }];
const foo = () => x[CONST_NUMBER0].value;
t0 = invoke(foo);
$[0] = t0;
} else {
t0 = $[0];
}
return t0;
}
export const FIXTURE_ENTRYPOINT = {
fn: Foo,
params: [{}],
};
```
@@ -0,0 +1,16 @@
import { CONST_NUMBER0, invoke } from "shared-runtime";
function Foo() {
const x = [{ value: 0 }, { value: 1 }, { value: 2 }];
const param = CONST_NUMBER0;
const foo = () => {
return x[param].value;
};
return invoke(foo);
}
export const FIXTURE_ENTRYPOINT = {
fn: Foo,
params: [{}],
};
@@ -0,0 +1,50 @@
## Input
```javascript
import { invoke } from "shared-runtime";
function Foo() {
const x = [{ value: 0 }, { value: 1 }, { value: 2 }];
const foo = (param: number) => {
return x[param].value;
};
return invoke(foo, 1);
}
export const FIXTURE_ENTRYPOINT = {
fn: Foo,
params: [{}],
};
```
## Code
```javascript
import { unstable_useMemoCache as useMemoCache } from "react";
import { invoke } from "shared-runtime";
function Foo() {
const $ = useMemoCache(1);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
const x = [{ value: 0 }, { value: 1 }, { value: 2 }];
const foo = (param) => x[param].value;
t0 = invoke(foo, 1);
$[0] = t0;
} else {
t0 = $[0];
}
return t0;
}
export const FIXTURE_ENTRYPOINT = {
fn: Foo,
params: [{}],
};
```
@@ -9,7 +9,7 @@ function Foo() {
return invoke(foo, 1);
}
export const FIXTURE_ENTRYPONT = {
export const FIXTURE_ENTRYPOINT = {
fn: Foo,
params: [{}],
};
@@ -99,7 +99,9 @@ export function doEval(source: string): EvaluatorResult {
) {
return {
kind: "UnexpectedError",
value: 'FIXTURE_ENTRYPOINT not exported!',
value: 'FIXTURE_ENTRYPOINT not exported! Found {'
+ Object.keys(exports).filter(e => e !== 'FIXTURE_ENTRYPOINT').toString()
+ '}',
};
}
const validationError = validateEntrypoint(exports.FIXTURE_ENTRYPOINT);