[hir] Gather dependencies for lambdas
Sathya Gunasekaran committed
Jan 9, 2023 at 18:22 UTC
346ecb27fe1b5dc31912d0120929250affbdd24e
11 files changed
+250
-2
compiler/forget/src/HIR/BuildHIR.ts
+54
-1
@@ -5,7 +5,7 @@
5
* LICENSE file in the root directory of this source tree.
6
*/
7
8
-import { NodePath } from "@babel/traverse";
8
+import { NodePath, Scope } from "@babel/traverse";
9
import * as t from "@babel/types";
10
import { invariant } from "../Utils/CompilerError";
11
import todo, { todoInvariant } from "../Utils/todo";
@@ -1151,6 +1151,8 @@ function lowerExpression(
1151
case "FunctionExpression": {
1152
const expr = exprPath as NodePath<t.FunctionExpression>;
1153
const name: string | null = expr.get("id")?.node?.name ?? null;
1154
+ const componentScope: Scope = expr.scope.parent.getFunctionParent()!;
1155
+ const dependencies = gatherCapturedDeps(expr, componentScope);
1156
const body = expr.get("body").node;
1157
const params: Array<string> = expr.get("params").map((p) => {
1158
todoInvariant(p.isIdentifier(), "handle non identifier params");
@@ -1161,6 +1163,7 @@ function lowerExpression(
1163
name,
1164
body,
1165
params,
1166
+ dependencies,
1167
loc: exprLoc,
1168
};
1169
}
@@ -1540,3 +1543,53 @@ function lowerAssignment(
1543
}
1544
}
1545
}
1546
+
1547
+function capturePureScopes(
1548
+ currentScope: Scope,
1549
+ componentScope: Scope
1550
+): Set<Scope> {
1551
+ let pureScopes: Set<Scope> = new Set();
1552
+ while (currentScope) {
1553
+ pureScopes.add(currentScope);
1554
+
1555
+ if (currentScope === componentScope) {
1556
+ break;
1557
+ }
1558
+
1559
+ currentScope = currentScope.parent;
1560
+ }
1561
+ return pureScopes;
1562
+}
1563
+
1564
+function gatherCapturedDeps(
1565
+ fn: NodePath<t.FunctionExpression>,
1566
+ componentScope: Scope
1567
+): Set<t.Identifier> {
1568
+ const captured: Set<t.Identifier> = new Set();
1569
+
1570
+ // Capture all the scopes from the parent of this function up to and including
1571
+ // the component scope.
1572
+ const pureScopes: Set<Scope> = capturePureScopes(
1573
+ fn.scope.parent,
1574
+ componentScope
1575
+ );
1576
+
1577
+ fn.get("body").traverse({
1578
+ Expression(path) {
1579
+ // TODO(gsn): Handle member expressions
1580
+ if (!path.isIdentifier) {
1581
+ return;
1582
+ }
1583
+
1584
+ const id = path as NodePath<t.Identifier>;
1585
+ const binding = id.scope.getBinding(id.node.name);
1586
+ if (binding === undefined || !pureScopes.has(binding.scope)) {
1587
+ return;
1588
+ }
1589
+
1590
+ captured.add(binding.identifier);
1591
+ },
1592
+ });
1593
+
1594
+ return captured;
1595
+}
compiler/forget/src/HIR/HIR.ts
+1
@@ -313,6 +313,7 @@ export type InstructionData =
313
kind: "FunctionExpression";
314
name: string | null;
315
params: Array<string>;
316
+ dependencies: Set<t.Identifier>;
317
body: t.BlockStatement;
318
}
319
compiler/forget/src/HIR/PrintHIR.ts
+4
-1
@@ -293,7 +293,10 @@ export function printInstructionValue(instrValue: InstructionValue): string {
293
case "FunctionExpression": {
294
const params = instrValue.params.join(",");
295
const body = generate(instrValue.body).code;
296
- value = `Function ${instrValue.name}(${params}){${body}}`;
296
+ const deps = [...instrValue.dependencies].map((i) => i.name).join(",");
297
+ value = `Function ${instrValue.name ?? ""} @deps[${deps}] (${
298
+ params ?? ""
299
+ }){${body}}`;
300
break;
301
}
302
default: {
compiler/forget/src/__tests__/fixtures/hir/capturing-function-1.expect.md
new
+36
@@ -0,0 +1,36 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+function component() {
6
+ let z = 100;
7
+ let x = function () {
8
+ z;
9
+ };
10
+ return x;
11
+}
12
+
13
+```
14
+
15
+## Code
16
+
17
+```javascript
18
+function component() {
19
+ const $ = React.useMemoCache();
20
+ const z = 100;
21
+ let x;
22
+ if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
23
+ x = function () {
24
+ z;
25
+ };
26
+
27
+ $[0] = x;
28
+ } else {
29
+ x = $[0];
30
+ }
31
+
32
+ return x;
33
+}
34
+
35
+```
36
+
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/hir/capturing-function-1.js
new
+7
@@ -0,0 +1,7 @@
1
+function component() {
2
+ let z = 100;
3
+ let x = function () {
4
+ z;
5
+ };
6
+ return x;
7
+}
compiler/forget/src/__tests__/fixtures/hir/capturing-function-within-block.expect.md
new
+40
@@ -0,0 +1,40 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+function component() {
6
+ let z = 100;
7
+ let x;
8
+ {
9
+ x = function () {
10
+ z;
11
+ };
12
+ }
13
+ return x;
14
+}
15
+
16
+```
17
+
18
+## Code
19
+
20
+```javascript
21
+function component() {
22
+ const $ = React.useMemoCache();
23
+ const z = 100;
24
+ const x = undefined;
25
+ let x$0;
26
+ if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
27
+ x$0 = function () {
28
+ z;
29
+ };
30
+
31
+ $[0] = x$0;
32
+ } else {
33
+ x$0 = $[0];
34
+ }
35
+
36
+ return x$0;
37
+}
38
+
39
+```
40
+
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/hir/capturing-function-within-block.js
new
+10
@@ -0,0 +1,10 @@
1
+function component() {
2
+ let z = 100;
3
+ let x;
4
+ {
5
+ x = function () {
6
+ z;
7
+ };
8
+ }
9
+ return x;
10
+}
compiler/forget/src/__tests__/fixtures/hir/capturing-variable-in-nested-block.expect.md
new
+40
@@ -0,0 +1,40 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+function component() {
6
+ let z = 100;
7
+ let x = function () {
8
+ {
9
+ z;
10
+ }
11
+ };
12
+ return x;
13
+}
14
+
15
+```
16
+
17
+## Code
18
+
19
+```javascript
20
+function component() {
21
+ const $ = React.useMemoCache();
22
+ const z = 100;
23
+ let x;
24
+ if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
25
+ x = function () {
26
+ {
27
+ z;
28
+ }
29
+ };
30
+
31
+ $[0] = x;
32
+ } else {
33
+ x = $[0];
34
+ }
35
+
36
+ return x;
37
+}
38
+
39
+```
40
+
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/hir/capturing-variable-in-nested-block.js
new
+9
@@ -0,0 +1,9 @@
1
+function component() {
2
+ let z = 100;
3
+ let x = function () {
4
+ {
5
+ z;
6
+ }
7
+ };
8
+ return x;
9
+}
compiler/forget/src/__tests__/fixtures/hir/capturing-variable-in-nested-function.expect.md
new
+40
@@ -0,0 +1,40 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+function component() {
6
+ let z = 100;
7
+ let x = function () {
8
+ (function () {
9
+ z;
10
+ })();
11
+ };
12
+ return x;
13
+}
14
+
15
+```
16
+
17
+## Code
18
+
19
+```javascript
20
+function component() {
21
+ const $ = React.useMemoCache();
22
+ const z = 100;
23
+ let x;
24
+ if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
25
+ x = function () {
26
+ (function () {
27
+ z;
28
+ })();
29
+ };
30
+
31
+ $[0] = x;
32
+ } else {
33
+ x = $[0];
34
+ }
35
+
36
+ return x;
37
+}
38
+
39
+```
40
+
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/hir/capturing-variable-in-nested-function.js
new
+9
@@ -0,0 +1,9 @@
1
+function component() {
2
+ let z = 100;
3
+ let x = function () {
4
+ (function () {
5
+ z;
6
+ })();
7
+ };
8
+ return x;
9
+}