@samitouri / QOS-React-1 / commits / e2836eec5b

[bugfix] do not hoist computed memberpaths in lambdas

Mofei Zhang committed Oct 30, 2023 at 12:56 UTC e2836eec5be07acf5a0cf7af3a230f2526d26031
7 files changed +154 -51
compiler/packages/babel-plugin-react-forget/src/HIR/BuildHIR.ts
+32 -19
@@ -3530,6 +3530,14 @@ function lowerAssignment(
3530 }
3531 }
3532
3533 +function isValidDependency(path: NodePath<t.MemberExpression>): boolean {
3534 + const parent: NodePath<t.Node> = path.parentPath;
3535 + return (
3536 + !path.node.computed &&
3537 + !(parent.isCallExpression() && parent.get("callee") === path)
3538 + );
3539 +}
3540 +
3541 function captureScopes({ from, to }: { from: Scope; to: Scope }): Set<Scope> {
3542 let scopes: Set<Scope> = new Set();
3543 while (from) {
@@ -3579,7 +3587,7 @@ function gatherCapturedDeps(
3587 | NodePath<t.JSXOpeningElement>
3588 ): void {
3589 // Base context variable to depend on
3582 - let baseIdentifier: NodePath<t.Identifier | t.JSXIdentifier>;
3590 + let baseIdentifier: NodePath<t.Identifier> | NodePath<t.JSXIdentifier>;
3591 // Base expression to depend on, which (for now) may contain non side-effectful
3592 // member expressions
3593 let dependency:
@@ -3604,31 +3612,36 @@ function gatherCapturedDeps(
3612 dependency = current;
3613 } else if (path.isMemberExpression()) {
3614 // Calculate baseIdentifier
3607 - let current: NodePath<Expression> = path;
3608 - while (current.isMemberExpression()) {
3609 - current = current.get("object");
3615 + let currentId: NodePath<Expression> = path;
3616 + while (currentId.isMemberExpression()) {
3617 + currentId = currentId.get("object");
3618 }
3611 - if (!current.isIdentifier()) {
3619 + if (!currentId.isIdentifier()) {
3620 return;
3621 }
3614 - baseIdentifier = current;
3622 + baseIdentifier = currentId;
3623
3624 // Get the expression to depend on, which may involve PropertyLoads
3625 // for member expressions
3618 - current =
3619 - path.parent.type === "CallExpression" &&
3620 - path.parent.callee === path.node
3621 - ? path.get("object")
3622 - : path;
3623 - while (current.isMemberExpression() && current.node.computed) {
3624 - // computed nodes may contain side-effectful subexpressions
3625 - current = current.get("object");
3626 + let currentDep:
3627 + | NodePath<t.MemberExpression>
3628 + | NodePath<t.Identifier>
3629 + | NodePath<t.JSXIdentifier> = baseIdentifier;
3630 +
3631 + while (true) {
3632 + const nextDep: null | NodePath<t.Node> = currentDep.parentPath;
3633 + if (
3634 + nextDep &&
3635 + nextDep.isMemberExpression() &&
3636 + isValidDependency(nextDep)
3637 + ) {
3638 + currentDep = nextDep;
3639 + } else {
3640 + break;
3641 + }
3642 }
3627 - invariant(
3628 - current.isMemberExpression() || current.isIdentifier(),
3629 - "Internal invariant broken in BuildHIR, unexpected type for capturedDep"
3630 - );
3631 - dependency = current;
3643 +
3644 + dependency = currentDep;
3645 } else {
3646 baseIdentifier = path;
3647 dependency = path;
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/error.bug-lambda-array-access-member-expr.expect.md deleted
-30
@@ -1,30 +0,0 @@
1 -
2 -## Input
3 -
4 -```javascript
5 -import { invoke } from "shared-runtime";
6 -
7 -function Foo() {
8 - const x = [{ value: 0 }, { value: 1 }, { value: 2 }];
9 - const foo = (param: number) => {
10 - return x[param].value;
11 - };
12 -
13 - return invoke(foo, 1);
14 -}
15 -
16 -export const FIXTURE_ENTRYPONT = {
17 - fn: Foo,
18 - params: [{}],
19 -};
20 -
21 -```
22 -
23 -
24 -## Error
25 -
26 -```
27 -[ReactForget] Todo: EnterSSA: Expected identifier to be defined before being used. Identifier param$10 is undefined (5:5)
28 -```
29 -
30 -
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/lambda-array-access-member-expr-captured.expect.md new
+52
@@ -0,0 +1,52 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +import { CONST_NUMBER0, invoke } from "shared-runtime";
6 +
7 +function Foo() {
8 + const x = [{ value: 0 }, { value: 1 }, { value: 2 }];
9 + const param = CONST_NUMBER0;
10 + const foo = () => {
11 + return x[param].value;
12 + };
13 +
14 + return invoke(foo);
15 +}
16 +
17 +export const FIXTURE_ENTRYPOINT = {
18 + fn: Foo,
19 + params: [{}],
20 +};
21 +
22 +```
23 +
24 +## Code
25 +
26 +```javascript
27 +import { unstable_useMemoCache as useMemoCache } from "react";
28 +import { CONST_NUMBER0, invoke } from "shared-runtime";
29 +
30 +function Foo() {
31 + const $ = useMemoCache(1);
32 + let t0;
33 + if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
34 + const x = [{ value: 0 }, { value: 1 }, { value: 2 }];
35 +
36 + const foo = () => x[CONST_NUMBER0].value;
37 +
38 + t0 = invoke(foo);
39 + $[0] = t0;
40 + } else {
41 + t0 = $[0];
42 + }
43 + return t0;
44 +}
45 +
46 +export const FIXTURE_ENTRYPOINT = {
47 + fn: Foo,
48 + params: [{}],
49 +};
50 +
51 +```
52 +
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/lambda-array-access-member-expr-captured.ts new
+16
@@ -0,0 +1,16 @@
1 +import { CONST_NUMBER0, invoke } from "shared-runtime";
2 +
3 +function Foo() {
4 + const x = [{ value: 0 }, { value: 1 }, { value: 2 }];
5 + const param = CONST_NUMBER0;
6 + const foo = () => {
7 + return x[param].value;
8 + };
9 +
10 + return invoke(foo);
11 +}
12 +
13 +export const FIXTURE_ENTRYPOINT = {
14 + fn: Foo,
15 + params: [{}],
16 +};
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/lambda-array-access-member-expr-param.expect.md new
+50
@@ -0,0 +1,50 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +import { invoke } from "shared-runtime";
6 +
7 +function Foo() {
8 + const x = [{ value: 0 }, { value: 1 }, { value: 2 }];
9 + const foo = (param: number) => {
10 + return x[param].value;
11 + };
12 +
13 + return invoke(foo, 1);
14 +}
15 +
16 +export const FIXTURE_ENTRYPOINT = {
17 + fn: Foo,
18 + params: [{}],
19 +};
20 +
21 +```
22 +
23 +## Code
24 +
25 +```javascript
26 +import { unstable_useMemoCache as useMemoCache } from "react";
27 +import { invoke } from "shared-runtime";
28 +
29 +function Foo() {
30 + const $ = useMemoCache(1);
31 + let t0;
32 + if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
33 + const x = [{ value: 0 }, { value: 1 }, { value: 2 }];
34 + const foo = (param) => x[param].value;
35 +
36 + t0 = invoke(foo, 1);
37 + $[0] = t0;
38 + } else {
39 + t0 = $[0];
40 + }
41 + return t0;
42 +}
43 +
44 +export const FIXTURE_ENTRYPOINT = {
45 + fn: Foo,
46 + params: [{}],
47 +};
48 +
49 +```
50 +
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/lambda-array-access-member-expr-param.ts renamed
+1 -1
@@ -9,7 +9,7 @@ function Foo() {
9 return invoke(foo, 1);
10 }
11
12 -export const FIXTURE_ENTRYPONT = {
12 +export const FIXTURE_ENTRYPOINT = {
13 fn: Foo,
14 params: [{}],
15 };
compiler/packages/sprout/src/runner-evaluator.ts
+3 -1
@@ -99,7 +99,9 @@ export function doEval(source: string): EvaluatorResult {
99 ) {
100 return {
101 kind: "UnexpectedError",
102 - value: 'FIXTURE_ENTRYPOINT not exported!',
102 + value: 'FIXTURE_ENTRYPOINT not exported! Found {'
103 + + Object.keys(exports).filter(e => e !== 'FIXTURE_ENTRYPOINT').toString()
104 + + '}',
105 };
106 }
107 const validationError = validateEntrypoint(exports.FIXTURE_ENTRYPOINT);