1
+import { NodePath } from "@babel/traverse";
2
+import * as t from "@babel/types";
3
+import { CompilerError } from "../CompilerError";
4
+import { GeneratedSource } from "./HIR";
5
+
6
+type FindContextIdentifierState = {
7
+ inLambda: number;
8
+ currentLambda: Array<
9
+ NodePath<t.FunctionExpression> | NodePath<t.ArrowFunctionExpression>
10
+ >;
11
+ contextIdentifiers: Set<t.Identifier>;
12
+};
13
+
14
+export function findContextIdentifiers(
15
+ func: NodePath<t.Function>
16
+): Set<t.Identifier> {
17
+ const state: FindContextIdentifierState = {
18
+ inLambda: 0,
19
+ currentLambda: [],
20
+ contextIdentifiers: new Set(),
21
+ };
22
+
23
+ func.traverse<FindContextIdentifierState>(
24
+ {
25
+ FunctionExpression: {
26
+ enter(
27
+ fn: NodePath<t.FunctionExpression>,
28
+ state: FindContextIdentifierState
29
+ ): void {
30
+ state.currentLambda.push(fn);
31
+ },
32
+ exit(
33
+ fn: NodePath<t.FunctionExpression>,
34
+ state: FindContextIdentifierState
35
+ ): void {
36
+ state.currentLambda.pop();
37
+ },
38
+ },
39
+
40
+ ArrowFunctionExpression: {
41
+ enter(
42
+ fn: NodePath<t.ArrowFunctionExpression>,
43
+ state: FindContextIdentifierState
44
+ ): void {
45
+ state.currentLambda.push(fn);
46
+ },
47
+ exit(
48
+ fn: NodePath<t.ArrowFunctionExpression>,
49
+ state: FindContextIdentifierState
50
+ ): void {
51
+ state.currentLambda.pop();
52
+ },
53
+ },
54
+ AssignmentExpression(
55
+ path: NodePath<t.AssignmentExpression>,
56
+ state: FindContextIdentifierState
57
+ ): void {
58
+ const currentLambda = state.currentLambda.at(-1);
59
+ if (currentLambda) {
60
+ const left = path.get("left");
61
+ handleAssignment(currentLambda, state.contextIdentifiers, left);
62
+ }
63
+ },
64
+ },
65
+ state
66
+ );
67
+ return state.contextIdentifiers;
68
+}
69
+
70
+function handleAssignment(
71
+ currentLambda:
72
+ | NodePath<t.FunctionExpression>
73
+ | NodePath<t.ArrowFunctionExpression>,
74
+ contextIdentifiers: Set<t.Identifier>,
75
+ lvalPath: NodePath<t.LVal>
76
+): void {
77
+ // Find all reassignments to identifiers declared outside of currentLambda
78
+ // This closely follows destructuring assignment assumptions and logic in BuildHIR
79
+ const lvalNode = lvalPath.node;
80
+ switch (lvalNode.type) {
81
+ case "Identifier": {
82
+ const path = lvalPath as NodePath<t.Identifier>;
83
+ const name = path.node.name;
84
+ const ownBinding = path.scope.getBinding(name);
85
+ const bindingAboveLambdaScope =
86
+ currentLambda.scope.parent.getBinding(name);
87
+
88
+ if (ownBinding != null && ownBinding === bindingAboveLambdaScope) {
89
+ contextIdentifiers.add(ownBinding.identifier);
90
+ }
91
+ break;
92
+ }
93
+ case "ArrayPattern": {
94
+ const path = lvalPath as NodePath<t.ArrayPattern>;
95
+ for (const element of path.get("elements")) {
96
+ if (nonNull(element)) {
97
+ handleAssignment(currentLambda, contextIdentifiers, element);
98
+ }
99
+ }
100
+ break;
101
+ }
102
+ case "ObjectPattern": {
103
+ const path = lvalPath as NodePath<t.ObjectPattern>;
104
+ for (const property of path.get("properties")) {
105
+ if (property.isObjectProperty()) {
106
+ const valuePath = property.get("value");
107
+ if (!valuePath.isLVal()) {
108
+ CompilerError.invariant(
109
+ `[FindContextIdentifiers] Expected object property value to be an LVal, got: ${valuePath.type}`,
110
+ valuePath.node.loc ?? GeneratedSource
111
+ );
112
+ }
113
+ handleAssignment(currentLambda, contextIdentifiers, valuePath);
114
+ } else {
115
+ if (!property.isRestElement()) {
116
+ CompilerError.invariant(
117
+ `[FindContextIdentifiers] Invalid assumptions for babel types.`,
118
+ property.node.loc ?? GeneratedSource
119
+ );
120
+ }
121
+ handleAssignment(currentLambda, contextIdentifiers, property);
122
+ }
123
+ }
124
+ break;
125
+ }
126
+ case "AssignmentPattern": {
127
+ const path = lvalPath as NodePath<t.AssignmentPattern>;
128
+ const left = path.get("left");
129
+ handleAssignment(currentLambda, contextIdentifiers, left);
130
+ break;
131
+ }
132
+ case "RestElement": {
133
+ const path = lvalPath as NodePath<t.RestElement>;
134
+ handleAssignment(currentLambda, contextIdentifiers, path.get("argument"));
135
+ break;
136
+ }
137
+ case "MemberExpression": {
138
+ // Interior mutability (not a reassign)
139
+ break;
140
+ }
141
+ default: {
142
+ CompilerError.todo(
143
+ `[FindContextIdentifiers] Cannot handle Object destructuring assignment target ${lvalNode.type}`,
144
+ lvalNode.loc ?? GeneratedSource
145
+ );
146
+ }
147
+ }
148
+}
149
+
150
+function nonNull<T extends NonNullable<t.Node>>(
151
+ t: NodePath<T | null>
152
+): t is NodePath<T> {
153
+ return t.node != null;
154
+}