@nudojs/cli 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -0
- package/dist/chunk-JW3RMCNY.js +1467 -0
- package/dist/chunk-RBHWWZII.js +1467 -0
- package/dist/chunk-VN5HMBGB.js +1467 -0
- package/dist/evaluator-api.d.ts +57 -0
- package/dist/evaluator-api.js +26 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +3560 -0
- package/package.json +20 -0
- package/src/__tests__/evaluator-phase2.test.ts +564 -0
- package/src/__tests__/evaluator-phase3.test.ts +513 -0
- package/src/__tests__/evaluator.test.ts +243 -0
- package/src/__tests__/narrowing.test.ts +82 -0
- package/src/evaluator-api.ts +14 -0
- package/src/evaluator.ts +1576 -0
- package/src/index.ts +280 -0
- package/src/narrowing.ts +266 -0
- package/tsconfig.json +9 -0
package/src/evaluator.ts
ADDED
|
@@ -0,0 +1,1576 @@
|
|
|
1
|
+
import type { Node } from "@babel/types";
|
|
2
|
+
import {
|
|
3
|
+
type TypeValue,
|
|
4
|
+
T,
|
|
5
|
+
simplifyUnion,
|
|
6
|
+
applyBinaryOp,
|
|
7
|
+
Ops,
|
|
8
|
+
type Environment,
|
|
9
|
+
createEnvironment,
|
|
10
|
+
deepCloneTypeValue,
|
|
11
|
+
mergeObjectProperties,
|
|
12
|
+
typeValueEquals,
|
|
13
|
+
typeValueToString,
|
|
14
|
+
isSubtypeOf,
|
|
15
|
+
} from "@nudojs/core";
|
|
16
|
+
import { narrow } from "./narrowing.ts";
|
|
17
|
+
|
|
18
|
+
type SourceRange = { start: { line: number; column: number }; end: { line: number; column: number } };
|
|
19
|
+
|
|
20
|
+
const RETURN_SIGNAL = Symbol("ReturnSignal");
|
|
21
|
+
const BRANCH_SIGNAL = Symbol("BranchSignal");
|
|
22
|
+
const THROW_SIGNAL = Symbol("ThrowSignal");
|
|
23
|
+
|
|
24
|
+
type ReturnSignal = {
|
|
25
|
+
readonly [RETURN_SIGNAL]: true;
|
|
26
|
+
readonly value: TypeValue;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
type BranchSignal = {
|
|
30
|
+
readonly [BRANCH_SIGNAL]: true;
|
|
31
|
+
readonly returnedValue: TypeValue;
|
|
32
|
+
readonly fallthroughEnv: Environment;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
type ThrowSignal = {
|
|
36
|
+
readonly [THROW_SIGNAL]: true;
|
|
37
|
+
readonly thrown: TypeValue;
|
|
38
|
+
readonly loc?: SourceRange;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
function makeReturn(value: TypeValue): ReturnSignal {
|
|
42
|
+
return { [RETURN_SIGNAL]: true, value };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function makeBranch(returnedValue: TypeValue, fallthroughEnv: Environment): BranchSignal {
|
|
46
|
+
return { [BRANCH_SIGNAL]: true, returnedValue, fallthroughEnv };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function makeThrow(thrown: TypeValue, loc?: SourceRange): ThrowSignal {
|
|
50
|
+
return { [THROW_SIGNAL]: true, thrown, loc };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function isReturn(v: unknown): v is ReturnSignal {
|
|
54
|
+
return typeof v === "object" && v !== null && RETURN_SIGNAL in v;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function isBranch(v: unknown): v is BranchSignal {
|
|
58
|
+
return typeof v === "object" && v !== null && BRANCH_SIGNAL in v;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function isThrow(v: unknown): v is ThrowSignal {
|
|
62
|
+
return typeof v === "object" && v !== null && THROW_SIGNAL in v;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
type EvalResult = TypeValue | ReturnSignal | BranchSignal | ThrowSignal;
|
|
66
|
+
|
|
67
|
+
const MEMO_IN_PROGRESS = Symbol("MemoInProgress");
|
|
68
|
+
const callMemo = new Map<string, TypeValue | typeof MEMO_IN_PROGRESS>();
|
|
69
|
+
|
|
70
|
+
function buildMemoKey(fn: TypeValue & { kind: "function" }, args: TypeValue[]): string | null {
|
|
71
|
+
const fnName = (fn as any)._memoize as string | undefined;
|
|
72
|
+
if (!fnName) return null;
|
|
73
|
+
const argsKey = args.map(typeValueToString).join(",");
|
|
74
|
+
return `${fnName}(${argsKey})`;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const moduleCache = new Map<string, Environment>();
|
|
78
|
+
|
|
79
|
+
export function resetMemo(): void {
|
|
80
|
+
callMemo.clear();
|
|
81
|
+
moduleCache.clear();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function setModuleResolver(resolver: ((source: string, fromDir: string) => { ast: Node; filePath: string } | null) | null): void {
|
|
85
|
+
currentModuleResolver = resolver;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
let currentModuleResolver: ((source: string, fromDir: string) => { ast: Node; filePath: string } | null) | null = null;
|
|
89
|
+
let currentFileDir = "";
|
|
90
|
+
|
|
91
|
+
let _nodeTypeCollector: ((node: Node, tv: TypeValue) => void) | null = null;
|
|
92
|
+
|
|
93
|
+
export function setNodeTypeCollector(collector: ((node: Node, tv: TypeValue) => void) | null): void {
|
|
94
|
+
_nodeTypeCollector = collector;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function recordNodeType(node: Node, tv: TypeValue): void {
|
|
98
|
+
if (_nodeTypeCollector && node.loc) {
|
|
99
|
+
_nodeTypeCollector(node, tv);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function distributeOverUnion(
|
|
104
|
+
tv: TypeValue,
|
|
105
|
+
fn: (member: TypeValue) => TypeValue,
|
|
106
|
+
): TypeValue {
|
|
107
|
+
if (tv.kind === "union") {
|
|
108
|
+
return simplifyUnion(tv.members.map(fn));
|
|
109
|
+
}
|
|
110
|
+
return fn(tv);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function distributeBinaryOverUnion(
|
|
114
|
+
left: TypeValue,
|
|
115
|
+
right: TypeValue,
|
|
116
|
+
fn: (l: TypeValue, r: TypeValue) => TypeValue,
|
|
117
|
+
): TypeValue {
|
|
118
|
+
if (left.kind === "union" && right.kind === "union") {
|
|
119
|
+
return simplifyUnion(
|
|
120
|
+
left.members.flatMap((l) => right.members.map((r) => fn(l, r))),
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
if (left.kind === "union") {
|
|
124
|
+
return simplifyUnion(left.members.map((l) => fn(l, right)));
|
|
125
|
+
}
|
|
126
|
+
if (right.kind === "union") {
|
|
127
|
+
return simplifyUnion(right.members.map((r) => fn(left, r)));
|
|
128
|
+
}
|
|
129
|
+
return fn(left, right);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
let _unreachableRanges: SourceRange[] = [];
|
|
133
|
+
|
|
134
|
+
function collectUnreachable(stmts: readonly Node[], fromIndex: number): void {
|
|
135
|
+
for (let j = fromIndex; j < stmts.length; j++) {
|
|
136
|
+
const s = stmts[j];
|
|
137
|
+
if (s.loc) {
|
|
138
|
+
_unreachableRanges.push({
|
|
139
|
+
start: { line: s.loc.start.line, column: s.loc.start.column },
|
|
140
|
+
end: { line: s.loc.end.line, column: s.loc.end.column },
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function evaluateStatements(
|
|
147
|
+
stmts: readonly Node[],
|
|
148
|
+
env: Environment,
|
|
149
|
+
): EvalResult {
|
|
150
|
+
const returnValues: TypeValue[] = [];
|
|
151
|
+
let currentEnv = env;
|
|
152
|
+
let lastValue: TypeValue = T.undefined;
|
|
153
|
+
|
|
154
|
+
for (let i = 0; i < stmts.length; i++) {
|
|
155
|
+
const stmt = stmts[i];
|
|
156
|
+
const result = evaluate(stmt, currentEnv);
|
|
157
|
+
|
|
158
|
+
if (isThrow(result)) {
|
|
159
|
+
collectUnreachable(stmts, i + 1);
|
|
160
|
+
return result;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (isReturn(result)) {
|
|
164
|
+
returnValues.push(result.value);
|
|
165
|
+
collectUnreachable(stmts, i + 1);
|
|
166
|
+
return makeReturn(simplifyUnion(returnValues));
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (isBranch(result)) {
|
|
170
|
+
returnValues.push(result.returnedValue);
|
|
171
|
+
currentEnv = result.fallthroughEnv;
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
lastValue = result;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (returnValues.length > 0) {
|
|
179
|
+
return makeBranch(simplifyUnion(returnValues), currentEnv);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return lastValue;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function describeParam(p: Node): string {
|
|
186
|
+
if (p.type === "Identifier") return p.name;
|
|
187
|
+
if (p.type === "AssignmentPattern" && p.left.type === "Identifier") return p.left.name;
|
|
188
|
+
if (p.type === "AssignmentPattern") return describeParam(p.left);
|
|
189
|
+
if (p.type === "RestElement") return `...${describeParam(p.argument)}`;
|
|
190
|
+
if (p.type === "ObjectPattern") {
|
|
191
|
+
const keys = p.properties.map((prop: any) => {
|
|
192
|
+
if (prop.type === "RestElement") return `...${describeParam(prop.argument)}`;
|
|
193
|
+
const key = prop.key?.type === "Identifier" ? prop.key.name : "?";
|
|
194
|
+
return key;
|
|
195
|
+
});
|
|
196
|
+
return `{ ${keys.join(", ")} }`;
|
|
197
|
+
}
|
|
198
|
+
if (p.type === "ArrayPattern") {
|
|
199
|
+
const elems = p.elements.map((e: any) => (e ? describeParam(e) : ""));
|
|
200
|
+
return `[${elems.join(", ")}]`;
|
|
201
|
+
}
|
|
202
|
+
return "_";
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export function evaluate(node: Node, env: Environment): EvalResult {
|
|
206
|
+
const result = evaluateNode(node, env);
|
|
207
|
+
if (_nodeTypeCollector && node.loc && !isReturn(result) && !isBranch(result) && !isThrow(result)) {
|
|
208
|
+
recordNodeType(node, result);
|
|
209
|
+
}
|
|
210
|
+
return result;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function evaluateNode(node: Node, env: Environment): EvalResult {
|
|
214
|
+
switch (node.type) {
|
|
215
|
+
case "File":
|
|
216
|
+
return evaluate(node.program, env);
|
|
217
|
+
|
|
218
|
+
case "Program":
|
|
219
|
+
return evaluateStatements(node.body, env);
|
|
220
|
+
|
|
221
|
+
case "ExpressionStatement":
|
|
222
|
+
return evaluate(node.expression, env);
|
|
223
|
+
|
|
224
|
+
case "NumericLiteral":
|
|
225
|
+
return T.literal(node.value);
|
|
226
|
+
|
|
227
|
+
case "StringLiteral":
|
|
228
|
+
return T.literal(node.value);
|
|
229
|
+
|
|
230
|
+
case "BooleanLiteral":
|
|
231
|
+
return T.literal(node.value);
|
|
232
|
+
|
|
233
|
+
case "NullLiteral":
|
|
234
|
+
return T.null;
|
|
235
|
+
|
|
236
|
+
case "Identifier": {
|
|
237
|
+
if (node.name === "undefined") return T.undefined;
|
|
238
|
+
return env.lookup(node.name);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
case "ThisExpression":
|
|
242
|
+
return env.lookup("this");
|
|
243
|
+
|
|
244
|
+
case "TemplateLiteral": {
|
|
245
|
+
if (node.expressions.length === 0 && node.quasis.length === 1) {
|
|
246
|
+
return T.literal(node.quasis[0].value.cooked ?? node.quasis[0].value.raw);
|
|
247
|
+
}
|
|
248
|
+
const parts: TypeValue[] = [];
|
|
249
|
+
for (let i = 0; i < node.quasis.length; i++) {
|
|
250
|
+
const quasi = node.quasis[i];
|
|
251
|
+
const raw = quasi.value.cooked ?? quasi.value.raw;
|
|
252
|
+
if (raw) parts.push(T.literal(raw));
|
|
253
|
+
if (i < node.expressions.length) {
|
|
254
|
+
const exprVal = evaluate(node.expressions[i], env);
|
|
255
|
+
if (isReturn(exprVal) || isBranch(exprVal) || isThrow(exprVal)) return exprVal;
|
|
256
|
+
parts.push(exprVal);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
const allLiteral = parts.every(
|
|
260
|
+
(p) => p.kind === "literal" && (typeof p.value === "string" || typeof p.value === "number"),
|
|
261
|
+
);
|
|
262
|
+
if (allLiteral) {
|
|
263
|
+
return T.literal(
|
|
264
|
+
parts.map((p) => (p.kind === "literal" ? String(p.value) : "")).join(""),
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
return T.string;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
case "BinaryExpression": {
|
|
271
|
+
const leftVal = evaluate(node.left, env);
|
|
272
|
+
if (isReturn(leftVal) || isBranch(leftVal) || isThrow(leftVal)) return leftVal;
|
|
273
|
+
const rightVal = evaluate(node.right, env);
|
|
274
|
+
if (isReturn(rightVal) || isBranch(rightVal) || isThrow(rightVal)) return rightVal;
|
|
275
|
+
|
|
276
|
+
if (node.operator === "instanceof") {
|
|
277
|
+
return evaluateInstanceof(leftVal, rightVal, node.right, env);
|
|
278
|
+
}
|
|
279
|
+
return distributeBinaryOverUnion(leftVal, rightVal, (l, r) =>
|
|
280
|
+
applyBinaryOp(node.operator, l, r),
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
case "UnaryExpression": {
|
|
285
|
+
const argVal = evaluate(node.argument, env);
|
|
286
|
+
if (isReturn(argVal) || isBranch(argVal) || isThrow(argVal)) return argVal;
|
|
287
|
+
if (node.operator === "typeof") {
|
|
288
|
+
return distributeOverUnion(argVal, (v) => Ops.typeof_(v));
|
|
289
|
+
}
|
|
290
|
+
if (node.operator === "!") {
|
|
291
|
+
return distributeOverUnion(argVal, (v) => Ops.not(v));
|
|
292
|
+
}
|
|
293
|
+
if (node.operator === "-") {
|
|
294
|
+
return distributeOverUnion(argVal, (v) => Ops.neg(v));
|
|
295
|
+
}
|
|
296
|
+
return T.unknown;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
case "LogicalExpression": {
|
|
300
|
+
const leftVal = evaluate(node.left, env);
|
|
301
|
+
if (isReturn(leftVal) || isBranch(leftVal) || isThrow(leftVal)) return leftVal;
|
|
302
|
+
|
|
303
|
+
if (node.operator === "&&") {
|
|
304
|
+
if (leftVal.kind === "literal" && !leftVal.value) return leftVal;
|
|
305
|
+
if (leftVal.kind === "literal" && leftVal.value) {
|
|
306
|
+
const rv = evaluate(node.right, env);
|
|
307
|
+
return isReturn(rv) || isBranch(rv) || isThrow(rv) ? rv : rv;
|
|
308
|
+
}
|
|
309
|
+
const rv = evaluate(node.right, env);
|
|
310
|
+
const rightTV = isReturn(rv) || isBranch(rv) || isThrow(rv) ? T.unknown : rv;
|
|
311
|
+
return simplifyUnion([leftVal, rightTV]);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if (node.operator === "||") {
|
|
315
|
+
if (leftVal.kind === "literal" && leftVal.value) return leftVal;
|
|
316
|
+
if (leftVal.kind === "literal" && !leftVal.value) {
|
|
317
|
+
const rv = evaluate(node.right, env);
|
|
318
|
+
return isReturn(rv) || isBranch(rv) || isThrow(rv) ? rv : rv;
|
|
319
|
+
}
|
|
320
|
+
const rv = evaluate(node.right, env);
|
|
321
|
+
const rightTV = isReturn(rv) || isBranch(rv) || isThrow(rv) ? T.unknown : rv;
|
|
322
|
+
return simplifyUnion([leftVal, rightTV]);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
if (node.operator === "??") {
|
|
326
|
+
if (leftVal.kind === "literal" && leftVal.value !== null && leftVal.value !== undefined) {
|
|
327
|
+
return leftVal;
|
|
328
|
+
}
|
|
329
|
+
if (leftVal.kind === "literal" && (leftVal.value === null || leftVal.value === undefined)) {
|
|
330
|
+
const rv = evaluate(node.right, env);
|
|
331
|
+
return isReturn(rv) || isBranch(rv) || isThrow(rv) ? rv : rv;
|
|
332
|
+
}
|
|
333
|
+
const rv = evaluate(node.right, env);
|
|
334
|
+
const rightTV = isReturn(rv) || isBranch(rv) || isThrow(rv) ? T.unknown : rv;
|
|
335
|
+
return simplifyUnion([leftVal, rightTV]);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
return T.unknown;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
case "ConditionalExpression": {
|
|
342
|
+
const test = node.test;
|
|
343
|
+
const [trueEnv, falseEnv] = narrow(test, env);
|
|
344
|
+
const testVal = evaluate(test, env);
|
|
345
|
+
if (isReturn(testVal) || isBranch(testVal) || isThrow(testVal)) return testVal;
|
|
346
|
+
|
|
347
|
+
if (testVal.kind === "literal") {
|
|
348
|
+
return testVal.value
|
|
349
|
+
? evaluate(node.consequent, trueEnv)
|
|
350
|
+
: evaluate(node.alternate, falseEnv);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
const cResult = evaluate(node.consequent, trueEnv);
|
|
354
|
+
const aResult = evaluate(node.alternate, falseEnv);
|
|
355
|
+
const cVal = isReturn(cResult) ? cResult.value : isBranch(cResult) ? cResult.returnedValue : isThrow(cResult) ? T.never : cResult;
|
|
356
|
+
const aVal = isReturn(aResult) ? aResult.value : isBranch(aResult) ? aResult.returnedValue : isThrow(aResult) ? T.never : aResult;
|
|
357
|
+
return simplifyUnion([cVal, aVal]);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
case "IfStatement": {
|
|
361
|
+
const test = node.test;
|
|
362
|
+
const [trueEnv, falseEnv] = narrow(test, env);
|
|
363
|
+
const testVal = evaluate(test, env);
|
|
364
|
+
if (isReturn(testVal) || isBranch(testVal) || isThrow(testVal)) return testVal;
|
|
365
|
+
|
|
366
|
+
if (testVal.kind === "literal") {
|
|
367
|
+
if (testVal.value) {
|
|
368
|
+
if (node.alternate?.loc) {
|
|
369
|
+
_unreachableRanges.push({
|
|
370
|
+
start: { line: node.alternate.loc.start.line, column: node.alternate.loc.start.column },
|
|
371
|
+
end: { line: node.alternate.loc.end.line, column: node.alternate.loc.end.column },
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
return evaluate(node.consequent, trueEnv);
|
|
375
|
+
}
|
|
376
|
+
if (node.consequent.loc) {
|
|
377
|
+
_unreachableRanges.push({
|
|
378
|
+
start: { line: node.consequent.loc.start.line, column: node.consequent.loc.start.column },
|
|
379
|
+
end: { line: node.consequent.loc.end.line, column: node.consequent.loc.end.column },
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
return node.alternate
|
|
383
|
+
? evaluate(node.alternate, falseEnv)
|
|
384
|
+
: T.undefined;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
const consequentResult = evaluate(node.consequent, trueEnv);
|
|
388
|
+
const alternateResult = node.alternate
|
|
389
|
+
? evaluate(node.alternate, falseEnv)
|
|
390
|
+
: null;
|
|
391
|
+
|
|
392
|
+
const cReturns = isReturn(consequentResult);
|
|
393
|
+
const cBranches = isBranch(consequentResult);
|
|
394
|
+
const cThrows = isThrow(consequentResult);
|
|
395
|
+
const aReturns = alternateResult !== null && isReturn(alternateResult);
|
|
396
|
+
const aBranches = alternateResult !== null && isBranch(alternateResult);
|
|
397
|
+
const aThrows = alternateResult !== null && isThrow(alternateResult);
|
|
398
|
+
|
|
399
|
+
if (cThrows && aThrows) {
|
|
400
|
+
return consequentResult;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
if (cThrows && !node.alternate) {
|
|
404
|
+
return makeBranch(T.never, falseEnv);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
if (cThrows) {
|
|
408
|
+
const aVal = aReturns ? (alternateResult as ReturnSignal).value
|
|
409
|
+
: aBranches ? (alternateResult as BranchSignal).returnedValue
|
|
410
|
+
: alternateResult as TypeValue;
|
|
411
|
+
return makeBranch(aVal, falseEnv);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
if (aThrows) {
|
|
415
|
+
const cVal = cReturns ? consequentResult.value
|
|
416
|
+
: cBranches ? consequentResult.returnedValue
|
|
417
|
+
: consequentResult as TypeValue;
|
|
418
|
+
return makeBranch(cVal, trueEnv);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
const cVal = cReturns ? consequentResult.value
|
|
422
|
+
: cBranches ? consequentResult.returnedValue
|
|
423
|
+
: consequentResult as TypeValue;
|
|
424
|
+
const aVal = aReturns ? (alternateResult as ReturnSignal).value
|
|
425
|
+
: aBranches ? (alternateResult as BranchSignal).returnedValue
|
|
426
|
+
: alternateResult as TypeValue | null;
|
|
427
|
+
|
|
428
|
+
if (cReturns && aReturns) {
|
|
429
|
+
return makeReturn(simplifyUnion([cVal, aVal!]));
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
if (cReturns && !node.alternate) {
|
|
433
|
+
return makeBranch(cVal, falseEnv);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
if (cReturns && node.alternate) {
|
|
437
|
+
if (aReturns) {
|
|
438
|
+
return makeReturn(simplifyUnion([cVal, aVal!]));
|
|
439
|
+
}
|
|
440
|
+
return makeBranch(cVal, falseEnv);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
if (aReturns) {
|
|
444
|
+
return makeBranch(aVal!, trueEnv);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
const allVals = [cVal];
|
|
448
|
+
if (aVal !== null) allVals.push(aVal);
|
|
449
|
+
else allVals.push(T.undefined);
|
|
450
|
+
return simplifyUnion(allVals);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
case "BlockStatement": {
|
|
454
|
+
const blockEnv = env.extend({});
|
|
455
|
+
const result = evaluateStatements(node.body, blockEnv);
|
|
456
|
+
return result;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
case "ReturnStatement": {
|
|
460
|
+
const arg = node.argument;
|
|
461
|
+
if (!arg) return makeReturn(T.undefined);
|
|
462
|
+
const val = evaluate(arg, env);
|
|
463
|
+
if (isReturn(val) || isBranch(val) || isThrow(val)) return val;
|
|
464
|
+
return makeReturn(val);
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
case "VariableDeclaration": {
|
|
468
|
+
for (const decl of node.declarations) {
|
|
469
|
+
const init = decl.init ? evaluate(decl.init, env) : T.undefined;
|
|
470
|
+
if (isReturn(init) || isBranch(init) || isThrow(init)) return init;
|
|
471
|
+
bindPattern(decl.id, init, env);
|
|
472
|
+
if (decl.id.type === "Identifier") {
|
|
473
|
+
recordNodeType(decl.id, init);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
return T.undefined;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
case "AssignmentExpression": {
|
|
480
|
+
if (node.left.type === "Identifier") {
|
|
481
|
+
const val = evaluate(node.right, env);
|
|
482
|
+
if (isReturn(val) || isBranch(val) || isThrow(val)) return val;
|
|
483
|
+
if (!env.update(node.left.name, val)) {
|
|
484
|
+
env.bind(node.left.name, val);
|
|
485
|
+
}
|
|
486
|
+
return val;
|
|
487
|
+
}
|
|
488
|
+
if (node.left.type === "MemberExpression") {
|
|
489
|
+
const val = evaluate(node.right, env);
|
|
490
|
+
if (isReturn(val) || isBranch(val) || isThrow(val)) return val;
|
|
491
|
+
const objVal = evaluate(node.left.object, env);
|
|
492
|
+
if (isReturn(objVal) || isBranch(objVal) || isThrow(objVal)) return val;
|
|
493
|
+
if (objVal.kind === "object") {
|
|
494
|
+
const propName = getMemberKey(node.left, env);
|
|
495
|
+
if (propName !== null) {
|
|
496
|
+
objVal.properties[propName] = val;
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
if (objVal.kind === "tuple" || objVal.kind === "array") {
|
|
500
|
+
const propVal = node.left.computed
|
|
501
|
+
? evaluate(node.left.property, env)
|
|
502
|
+
: null;
|
|
503
|
+
if (
|
|
504
|
+
objVal.kind === "tuple" &&
|
|
505
|
+
propVal &&
|
|
506
|
+
!isReturn(propVal) &&
|
|
507
|
+
!isBranch(propVal) &&
|
|
508
|
+
!isThrow(propVal) &&
|
|
509
|
+
propVal.kind === "literal" &&
|
|
510
|
+
typeof propVal.value === "number"
|
|
511
|
+
) {
|
|
512
|
+
objVal.elements[propVal.value] = val;
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
return val;
|
|
516
|
+
}
|
|
517
|
+
if (
|
|
518
|
+
node.left.type === "ObjectPattern" ||
|
|
519
|
+
node.left.type === "ArrayPattern"
|
|
520
|
+
) {
|
|
521
|
+
const val = evaluate(node.right, env);
|
|
522
|
+
if (isReturn(val) || isBranch(val) || isThrow(val)) return val;
|
|
523
|
+
bindPattern(node.left, val, env);
|
|
524
|
+
return val;
|
|
525
|
+
}
|
|
526
|
+
return T.unknown;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
case "ForOfStatement": {
|
|
530
|
+
const rightVal = evaluate(node.right, env);
|
|
531
|
+
if (isReturn(rightVal) || isBranch(rightVal) || isThrow(rightVal)) return rightVal;
|
|
532
|
+
return evaluateForOf(node, rightVal, env);
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
case "ForInStatement": {
|
|
536
|
+
const rightVal = evaluate(node.right, env);
|
|
537
|
+
if (isReturn(rightVal) || isBranch(rightVal) || isThrow(rightVal)) return rightVal;
|
|
538
|
+
return evaluateForIn(node, rightVal, env);
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
case "ForStatement": {
|
|
542
|
+
if (node.init) {
|
|
543
|
+
const initResult = evaluate(node.init, env);
|
|
544
|
+
if (isReturn(initResult) || isBranch(initResult) || isThrow(initResult)) return initResult;
|
|
545
|
+
}
|
|
546
|
+
return T.undefined;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
case "WhileStatement":
|
|
550
|
+
return T.undefined;
|
|
551
|
+
|
|
552
|
+
case "FunctionDeclaration": {
|
|
553
|
+
if (!node.id) return T.undefined;
|
|
554
|
+
const paramNames = node.params.map(describeParam);
|
|
555
|
+
const fnType = T.fn(paramNames, node.body, env);
|
|
556
|
+
(fnType as any)._paramPatterns = node.params;
|
|
557
|
+
if (node.async) (fnType as any)._async = true;
|
|
558
|
+
env.bind(node.id.name, fnType);
|
|
559
|
+
return T.undefined;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
case "FunctionExpression":
|
|
563
|
+
case "ArrowFunctionExpression": {
|
|
564
|
+
const paramNames = node.params.map(describeParam);
|
|
565
|
+
const body = node.body;
|
|
566
|
+
const fnType = T.fn(paramNames, body, env);
|
|
567
|
+
(fnType as any)._paramPatterns = node.params;
|
|
568
|
+
if (node.async) (fnType as any)._async = true;
|
|
569
|
+
return fnType;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
case "AwaitExpression": {
|
|
573
|
+
const argVal = evaluate(node.argument, env);
|
|
574
|
+
if (isReturn(argVal) || isBranch(argVal) || isThrow(argVal)) return argVal;
|
|
575
|
+
return distributeOverUnion(argVal, (v) =>
|
|
576
|
+
v.kind === "promise" ? v.value : v,
|
|
577
|
+
);
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
case "ClassDeclaration": {
|
|
581
|
+
return evaluateClassDeclaration(node, env);
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
case "ImportDeclaration": {
|
|
585
|
+
return evaluateImportDeclaration(node, env);
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
case "ExportNamedDeclaration": {
|
|
589
|
+
if (node.declaration) {
|
|
590
|
+
const result = evaluate(node.declaration, env);
|
|
591
|
+
if (isReturn(result) || isBranch(result) || isThrow(result)) return result;
|
|
592
|
+
if (node.declaration.type === "VariableDeclaration") {
|
|
593
|
+
for (const decl of node.declaration.declarations) {
|
|
594
|
+
if (decl.id.type === "Identifier") {
|
|
595
|
+
const val = env.lookup(decl.id.name);
|
|
596
|
+
env.bind(`__export_${decl.id.name}`, val);
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
} else if (node.declaration.type === "FunctionDeclaration" && node.declaration.id) {
|
|
600
|
+
const val = env.lookup(node.declaration.id.name);
|
|
601
|
+
env.bind(`__export_${node.declaration.id.name}`, val);
|
|
602
|
+
} else if (node.declaration.type === "ClassDeclaration" && node.declaration.id) {
|
|
603
|
+
const val = env.lookup(node.declaration.id.name);
|
|
604
|
+
env.bind(`__export_${node.declaration.id.name}`, val);
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
if (node.specifiers) {
|
|
608
|
+
for (const spec of node.specifiers) {
|
|
609
|
+
if (spec.type === "ExportSpecifier") {
|
|
610
|
+
const localName = spec.local.type === "Identifier" ? spec.local.name : null;
|
|
611
|
+
const exportedName = spec.exported.type === "Identifier" ? spec.exported.name : null;
|
|
612
|
+
if (localName && exportedName) {
|
|
613
|
+
env.bind(`__export_${exportedName}`, env.lookup(localName));
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
return T.undefined;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
case "ExportDefaultDeclaration": {
|
|
622
|
+
const decl = node.declaration;
|
|
623
|
+
const result = evaluate(decl, env);
|
|
624
|
+
if (isReturn(result) || isBranch(result) || isThrow(result)) return result;
|
|
625
|
+
if (decl.type === "FunctionDeclaration" && decl.id) {
|
|
626
|
+
env.bind(`__export_default`, env.lookup(decl.id.name));
|
|
627
|
+
} else if (decl.type === "ClassDeclaration" && decl.id) {
|
|
628
|
+
env.bind(`__export_default`, env.lookup(decl.id.name));
|
|
629
|
+
} else {
|
|
630
|
+
env.bind(`__export_default`, result);
|
|
631
|
+
}
|
|
632
|
+
return T.undefined;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
case "CallExpression": {
|
|
636
|
+
const callee = node.callee as Node;
|
|
637
|
+
|
|
638
|
+
if (callee.type === "MemberExpression") {
|
|
639
|
+
const methodResult = evaluateMethodCall(callee, node.arguments as Node[], env);
|
|
640
|
+
if (methodResult !== null) return methodResult;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
const calleeVal = evaluate(callee, env);
|
|
644
|
+
if (isReturn(calleeVal) || isBranch(calleeVal) || isThrow(calleeVal)) return calleeVal;
|
|
645
|
+
|
|
646
|
+
const argVals = evaluateArgs(node.arguments as Node[], env);
|
|
647
|
+
if (isReturn(argVals) || isBranch(argVals) || isThrow(argVals)) return argVals;
|
|
648
|
+
|
|
649
|
+
if (calleeVal.kind === "function") {
|
|
650
|
+
const full = callFunctionFull(calleeVal, argVals as TypeValue[]);
|
|
651
|
+
if (full.value.kind === "never" && full.throws.kind !== "never") {
|
|
652
|
+
const callLoc = node.loc ? {
|
|
653
|
+
start: { line: node.loc.start.line, column: node.loc.start.column },
|
|
654
|
+
end: { line: node.loc.end.line, column: node.loc.end.column },
|
|
655
|
+
} : full.throwLoc;
|
|
656
|
+
return makeThrow(full.throws, callLoc);
|
|
657
|
+
}
|
|
658
|
+
return full.value;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
return distributeOverUnion(calleeVal, (fn) => {
|
|
662
|
+
if (fn.kind !== "function") return T.unknown;
|
|
663
|
+
return callFunction(fn, argVals as TypeValue[]);
|
|
664
|
+
});
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
case "MemberExpression": {
|
|
668
|
+
const objVal = evaluate(node.object, env);
|
|
669
|
+
if (isReturn(objVal) || isBranch(objVal) || isThrow(objVal)) return objVal;
|
|
670
|
+
|
|
671
|
+
if (node.computed) {
|
|
672
|
+
const propVal = evaluate(node.property, env);
|
|
673
|
+
if (isReturn(propVal) || isBranch(propVal) || isThrow(propVal)) return propVal;
|
|
674
|
+
return distributeOverUnion(objVal, (obj) => {
|
|
675
|
+
if (obj.kind === "object" && propVal.kind === "literal" && typeof propVal.value === "string") {
|
|
676
|
+
return obj.properties[propVal.value] ?? T.undefined;
|
|
677
|
+
}
|
|
678
|
+
if ((obj.kind === "array" || obj.kind === "tuple") && propVal.kind === "literal" && typeof propVal.value === "number") {
|
|
679
|
+
if (obj.kind === "tuple") return obj.elements[propVal.value] ?? T.undefined;
|
|
680
|
+
return obj.element;
|
|
681
|
+
}
|
|
682
|
+
return T.unknown;
|
|
683
|
+
});
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
if (node.property.type === "Identifier") {
|
|
687
|
+
const propName = node.property.name;
|
|
688
|
+
return distributeOverUnion(objVal, (obj) => {
|
|
689
|
+
if (obj.kind === "object") return obj.properties[propName] ?? T.undefined;
|
|
690
|
+
if (obj.kind === "instance") return obj.properties[propName] ?? T.undefined;
|
|
691
|
+
if (propName === "length" && (obj.kind === "array" || obj.kind === "tuple")) {
|
|
692
|
+
return obj.kind === "tuple" ? T.literal(obj.elements.length) : T.number;
|
|
693
|
+
}
|
|
694
|
+
return T.unknown;
|
|
695
|
+
});
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
return T.unknown;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
case "ObjectExpression": {
|
|
702
|
+
const props: Record<string, TypeValue> = {};
|
|
703
|
+
for (const prop of node.properties) {
|
|
704
|
+
if (prop.type === "ObjectProperty") {
|
|
705
|
+
const key = prop.computed
|
|
706
|
+
? (() => {
|
|
707
|
+
const kv = evaluate(prop.key, env);
|
|
708
|
+
return !isReturn(kv) && !isBranch(kv) && !isThrow(kv) && kv.kind === "literal" && typeof kv.value === "string"
|
|
709
|
+
? kv.value
|
|
710
|
+
: null;
|
|
711
|
+
})()
|
|
712
|
+
: prop.key.type === "Identifier"
|
|
713
|
+
? prop.key.name
|
|
714
|
+
: prop.key.type === "StringLiteral"
|
|
715
|
+
? prop.key.value
|
|
716
|
+
: null;
|
|
717
|
+
if (key) {
|
|
718
|
+
const val = evaluate(prop.value as Node, env);
|
|
719
|
+
if (isReturn(val) || isBranch(val) || isThrow(val)) return val;
|
|
720
|
+
props[key] = val;
|
|
721
|
+
}
|
|
722
|
+
} else if (prop.type === "SpreadElement") {
|
|
723
|
+
const spreadVal = evaluate(prop.argument, env);
|
|
724
|
+
if (isReturn(spreadVal) || isBranch(spreadVal) || isThrow(spreadVal)) return spreadVal;
|
|
725
|
+
if (spreadVal.kind === "object") {
|
|
726
|
+
Object.assign(props, spreadVal.properties);
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
return T.object(props);
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
case "ArrayExpression": {
|
|
734
|
+
const elements: TypeValue[] = [];
|
|
735
|
+
for (const elem of node.elements) {
|
|
736
|
+
if (!elem) {
|
|
737
|
+
elements.push(T.undefined);
|
|
738
|
+
continue;
|
|
739
|
+
}
|
|
740
|
+
if (elem.type === "SpreadElement") {
|
|
741
|
+
const spreadVal = evaluate(elem.argument, env);
|
|
742
|
+
if (isReturn(spreadVal) || isBranch(spreadVal) || isThrow(spreadVal)) return spreadVal;
|
|
743
|
+
if (spreadVal.kind === "tuple") {
|
|
744
|
+
elements.push(...spreadVal.elements);
|
|
745
|
+
} else if (spreadVal.kind === "array") {
|
|
746
|
+
return T.array(simplifyUnion([...elements, spreadVal.element]));
|
|
747
|
+
} else {
|
|
748
|
+
elements.push(T.unknown);
|
|
749
|
+
}
|
|
750
|
+
continue;
|
|
751
|
+
}
|
|
752
|
+
const val = evaluate(elem as Node, env);
|
|
753
|
+
if (isReturn(val) || isBranch(val) || isThrow(val)) return val;
|
|
754
|
+
elements.push(val);
|
|
755
|
+
}
|
|
756
|
+
return T.tuple(elements);
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
case "ThrowStatement": {
|
|
760
|
+
const argVal = node.argument ? evaluate(node.argument, env) : T.undefined;
|
|
761
|
+
if (isReturn(argVal) || isBranch(argVal) || isThrow(argVal)) return argVal;
|
|
762
|
+
const throwLoc = node.loc ? {
|
|
763
|
+
start: { line: node.loc.start.line, column: node.loc.start.column },
|
|
764
|
+
end: { line: node.loc.end.line, column: node.loc.end.column },
|
|
765
|
+
} : undefined;
|
|
766
|
+
return makeThrow(argVal, throwLoc);
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
case "TryStatement": {
|
|
770
|
+
return evaluateTryStatement(node, env);
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
case "NewExpression": {
|
|
774
|
+
return evaluateNewExpression(node, env);
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
case "SwitchStatement": {
|
|
778
|
+
return evaluateSwitchStatement(node, env);
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
case "UpdateExpression": {
|
|
782
|
+
if (node.argument.type === "Identifier") {
|
|
783
|
+
const current = env.lookup(node.argument.name);
|
|
784
|
+
if (current.kind === "literal" && typeof current.value === "number") {
|
|
785
|
+
const newVal = node.operator === "++"
|
|
786
|
+
? T.literal(current.value + 1)
|
|
787
|
+
: T.literal(current.value - 1);
|
|
788
|
+
if (!env.update(node.argument.name, newVal)) {
|
|
789
|
+
env.bind(node.argument.name, newVal);
|
|
790
|
+
}
|
|
791
|
+
return node.prefix ? newVal : current;
|
|
792
|
+
}
|
|
793
|
+
if (!env.update(node.argument.name, T.number)) {
|
|
794
|
+
env.bind(node.argument.name, T.number);
|
|
795
|
+
}
|
|
796
|
+
return T.number;
|
|
797
|
+
}
|
|
798
|
+
return T.number;
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
default:
|
|
802
|
+
return T.unknown;
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
function getMemberKey(node: Node & { type: "MemberExpression" }, env: Environment): string | null {
|
|
807
|
+
if (!node.computed && node.property.type === "Identifier") {
|
|
808
|
+
return node.property.name;
|
|
809
|
+
}
|
|
810
|
+
if (node.computed) {
|
|
811
|
+
const propVal = evaluate(node.property, env);
|
|
812
|
+
if (!isReturn(propVal) && !isBranch(propVal) && !isThrow(propVal) && propVal.kind === "literal") {
|
|
813
|
+
return String(propVal.value);
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
return null;
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
function bindPattern(pattern: Node, value: TypeValue, env: Environment): void {
|
|
820
|
+
if (pattern.type === "Identifier") {
|
|
821
|
+
env.bind(pattern.name, value);
|
|
822
|
+
return;
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
if (pattern.type === "AssignmentPattern") {
|
|
826
|
+
const defaultVal = evaluate(pattern.right, env);
|
|
827
|
+
const resolved = (value.kind === "literal" && value.value === undefined)
|
|
828
|
+
? (!isReturn(defaultVal) && !isBranch(defaultVal) && !isThrow(defaultVal) ? defaultVal : T.unknown)
|
|
829
|
+
: value;
|
|
830
|
+
bindPattern(pattern.left, resolved, env);
|
|
831
|
+
return;
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
if (pattern.type === "ObjectPattern") {
|
|
835
|
+
const restKeys: string[] = [];
|
|
836
|
+
for (const prop of pattern.properties) {
|
|
837
|
+
if (prop.type === "RestElement") {
|
|
838
|
+
if (value.kind === "object") {
|
|
839
|
+
const remaining: Record<string, TypeValue> = {};
|
|
840
|
+
for (const [k, v] of Object.entries(value.properties)) {
|
|
841
|
+
if (!restKeys.includes(k)) remaining[k] = v;
|
|
842
|
+
}
|
|
843
|
+
bindPattern(prop.argument, T.object(remaining), env);
|
|
844
|
+
} else {
|
|
845
|
+
bindPattern(prop.argument, T.object({}), env);
|
|
846
|
+
}
|
|
847
|
+
continue;
|
|
848
|
+
}
|
|
849
|
+
if (prop.type !== "ObjectProperty") continue;
|
|
850
|
+
const key = prop.key.type === "Identifier"
|
|
851
|
+
? prop.key.name
|
|
852
|
+
: prop.key.type === "StringLiteral"
|
|
853
|
+
? prop.key.value
|
|
854
|
+
: null;
|
|
855
|
+
if (!key) continue;
|
|
856
|
+
restKeys.push(key);
|
|
857
|
+
const propVal = value.kind === "object"
|
|
858
|
+
? (value.properties[key] ?? T.undefined)
|
|
859
|
+
: T.unknown;
|
|
860
|
+
bindPattern(prop.value as Node, propVal, env);
|
|
861
|
+
}
|
|
862
|
+
return;
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
if (pattern.type === "ArrayPattern") {
|
|
866
|
+
for (let i = 0; i < pattern.elements.length; i++) {
|
|
867
|
+
const elem = pattern.elements[i];
|
|
868
|
+
if (!elem) continue;
|
|
869
|
+
if (elem.type === "RestElement") {
|
|
870
|
+
if (value.kind === "tuple") {
|
|
871
|
+
bindPattern(elem.argument, T.tuple(value.elements.slice(i)), env);
|
|
872
|
+
} else if (value.kind === "array") {
|
|
873
|
+
bindPattern(elem.argument, value, env);
|
|
874
|
+
} else {
|
|
875
|
+
bindPattern(elem.argument, T.tuple([]), env);
|
|
876
|
+
}
|
|
877
|
+
continue;
|
|
878
|
+
}
|
|
879
|
+
const elemVal = value.kind === "tuple"
|
|
880
|
+
? (value.elements[i] ?? T.undefined)
|
|
881
|
+
: value.kind === "array"
|
|
882
|
+
? value.element
|
|
883
|
+
: T.unknown;
|
|
884
|
+
bindPattern(elem, elemVal, env);
|
|
885
|
+
}
|
|
886
|
+
return;
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
function evaluateArgs(args: Node[], env: Environment): TypeValue[] | ReturnSignal | BranchSignal | ThrowSignal {
|
|
891
|
+
const result: TypeValue[] = [];
|
|
892
|
+
for (const arg of args) {
|
|
893
|
+
if (arg.type === "SpreadElement") {
|
|
894
|
+
const spreadVal = evaluate(arg.argument, env);
|
|
895
|
+
if (isReturn(spreadVal) || isBranch(spreadVal) || isThrow(spreadVal)) return spreadVal;
|
|
896
|
+
if (spreadVal.kind === "tuple") {
|
|
897
|
+
result.push(...spreadVal.elements);
|
|
898
|
+
} else if (spreadVal.kind === "array") {
|
|
899
|
+
result.push(spreadVal.element);
|
|
900
|
+
} else {
|
|
901
|
+
result.push(T.unknown);
|
|
902
|
+
}
|
|
903
|
+
continue;
|
|
904
|
+
}
|
|
905
|
+
const v = evaluate(arg, env);
|
|
906
|
+
if (isReturn(v) || isBranch(v) || isThrow(v)) return v;
|
|
907
|
+
result.push(v);
|
|
908
|
+
}
|
|
909
|
+
return result;
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
function evaluateMethodCall(
|
|
913
|
+
callee: Node & { type: "MemberExpression" },
|
|
914
|
+
args: Node[],
|
|
915
|
+
env: Environment,
|
|
916
|
+
): EvalResult | null {
|
|
917
|
+
const objVal = evaluate(callee.object, env);
|
|
918
|
+
if (isReturn(objVal) || isBranch(objVal) || isThrow(objVal)) return objVal;
|
|
919
|
+
|
|
920
|
+
const methodName = !callee.computed && callee.property.type === "Identifier"
|
|
921
|
+
? callee.property.name
|
|
922
|
+
: null;
|
|
923
|
+
if (!methodName) return null;
|
|
924
|
+
|
|
925
|
+
if (
|
|
926
|
+
callee.object.type === "Identifier" &&
|
|
927
|
+
callee.object.name === "Object" &&
|
|
928
|
+
args.length >= 1
|
|
929
|
+
) {
|
|
930
|
+
const argVal = evaluate(args[0], env);
|
|
931
|
+
if (isReturn(argVal) || isBranch(argVal) || isThrow(argVal)) return argVal;
|
|
932
|
+
return evaluateObjectStaticMethod(methodName, argVal);
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
if (objVal.kind === "array" || objVal.kind === "tuple") {
|
|
936
|
+
return evaluateArrayMethod(objVal, methodName, args, env);
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
return null;
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
function evaluateObjectStaticMethod(
|
|
943
|
+
method: string,
|
|
944
|
+
obj: TypeValue,
|
|
945
|
+
): TypeValue | null {
|
|
946
|
+
if (obj.kind !== "object") {
|
|
947
|
+
if (method === "keys") return T.array(T.string);
|
|
948
|
+
if (method === "values") return T.array(T.unknown);
|
|
949
|
+
if (method === "entries") return T.array(T.tuple([T.string, T.unknown]));
|
|
950
|
+
return null;
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
const keys = Object.keys(obj.properties);
|
|
954
|
+
const values = Object.values(obj.properties);
|
|
955
|
+
|
|
956
|
+
if (method === "keys") {
|
|
957
|
+
return T.tuple(keys.map((k) => T.literal(k)));
|
|
958
|
+
}
|
|
959
|
+
if (method === "values") {
|
|
960
|
+
return T.tuple(values);
|
|
961
|
+
}
|
|
962
|
+
if (method === "entries") {
|
|
963
|
+
return T.tuple(
|
|
964
|
+
keys.map((k) => T.tuple([T.literal(k), obj.properties[k]])),
|
|
965
|
+
);
|
|
966
|
+
}
|
|
967
|
+
return null;
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
function evaluateArrayMethod(
|
|
971
|
+
arr: TypeValue & { kind: "array" | "tuple" },
|
|
972
|
+
method: string,
|
|
973
|
+
args: Node[],
|
|
974
|
+
env: Environment,
|
|
975
|
+
): EvalResult | null {
|
|
976
|
+
const argVals = evaluateArgs(args, env);
|
|
977
|
+
if (isReturn(argVals) || isBranch(argVals) || isThrow(argVals)) return argVals;
|
|
978
|
+
|
|
979
|
+
const callbackFn = (argVals as TypeValue[])[0];
|
|
980
|
+
|
|
981
|
+
if (method === "push") {
|
|
982
|
+
if (arr.kind === "tuple") {
|
|
983
|
+
arr.elements.push(...(argVals as TypeValue[]));
|
|
984
|
+
return T.literal(arr.elements.length);
|
|
985
|
+
}
|
|
986
|
+
return T.number;
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
if (method === "length") {
|
|
990
|
+
return arr.kind === "tuple" ? T.literal(arr.elements.length) : T.number;
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
if (method === "indexOf" || method === "lastIndexOf") {
|
|
994
|
+
return T.number;
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
if (method === "includes") {
|
|
998
|
+
if (arr.kind === "tuple" && (argVals as TypeValue[])[0]?.kind === "literal") {
|
|
999
|
+
const searchVal = (argVals as TypeValue[])[0];
|
|
1000
|
+
const found = arr.elements.some((e) => typeValueEquals(e, searchVal));
|
|
1001
|
+
return T.literal(found);
|
|
1002
|
+
}
|
|
1003
|
+
return T.boolean;
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
if (method === "join") {
|
|
1007
|
+
return T.string;
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
if (method === "concat") {
|
|
1011
|
+
if (arr.kind === "tuple") {
|
|
1012
|
+
const otherElements: TypeValue[] = [];
|
|
1013
|
+
for (const a of argVals as TypeValue[]) {
|
|
1014
|
+
if (a.kind === "tuple") otherElements.push(...a.elements);
|
|
1015
|
+
else if (a.kind === "array") return T.array(simplifyUnion([...arr.elements, a.element]));
|
|
1016
|
+
else otherElements.push(a);
|
|
1017
|
+
}
|
|
1018
|
+
return T.tuple([...arr.elements, ...otherElements]);
|
|
1019
|
+
}
|
|
1020
|
+
return T.array(arr.element);
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
if (method === "slice") {
|
|
1024
|
+
if (arr.kind === "tuple") {
|
|
1025
|
+
const start = (argVals as TypeValue[])[0];
|
|
1026
|
+
const end = (argVals as TypeValue[])[1];
|
|
1027
|
+
const startIdx = start?.kind === "literal" && typeof start.value === "number" ? start.value : 0;
|
|
1028
|
+
const endIdx = end?.kind === "literal" && typeof end.value === "number" ? end.value : arr.elements.length;
|
|
1029
|
+
return T.tuple(arr.elements.slice(startIdx, endIdx));
|
|
1030
|
+
}
|
|
1031
|
+
return T.array(arr.element);
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
if (!callbackFn || callbackFn.kind !== "function") {
|
|
1035
|
+
if (method === "map") return arr.kind === "tuple" ? T.tuple(arr.elements.map(() => T.unknown)) : T.array(T.unknown);
|
|
1036
|
+
if (method === "filter") return arr.kind === "tuple" ? T.array(simplifyUnion(arr.elements)) : arr;
|
|
1037
|
+
if (method === "find") return arr.kind === "tuple" ? simplifyUnion([...arr.elements, T.undefined]) : simplifyUnion([arr.element, T.undefined]);
|
|
1038
|
+
if (method === "some" || method === "every") return T.boolean;
|
|
1039
|
+
if (method === "reduce") return (argVals as TypeValue[])[1] ?? T.unknown;
|
|
1040
|
+
if (method === "forEach") return T.undefined;
|
|
1041
|
+
if (method === "flatMap") return T.array(T.unknown);
|
|
1042
|
+
return null;
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
const fn = callbackFn as TypeValue & { kind: "function" };
|
|
1046
|
+
|
|
1047
|
+
if (method === "map") {
|
|
1048
|
+
if (arr.kind === "tuple") {
|
|
1049
|
+
const mapped = arr.elements.map((el, i) =>
|
|
1050
|
+
callFunction(fn, [el, T.literal(i), arr]),
|
|
1051
|
+
);
|
|
1052
|
+
return T.tuple(mapped);
|
|
1053
|
+
}
|
|
1054
|
+
return T.array(callFunction(fn, [arr.element, T.number, arr]));
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
if (method === "filter") {
|
|
1058
|
+
if (arr.kind === "tuple") {
|
|
1059
|
+
const kept: TypeValue[] = [];
|
|
1060
|
+
for (let i = 0; i < arr.elements.length; i++) {
|
|
1061
|
+
const result = callFunction(fn, [arr.elements[i], T.literal(i), arr]);
|
|
1062
|
+
if (result.kind === "literal" && !result.value) continue;
|
|
1063
|
+
kept.push(arr.elements[i]);
|
|
1064
|
+
}
|
|
1065
|
+
if (kept.length === 0) return T.tuple([]);
|
|
1066
|
+
return T.array(simplifyUnion(kept));
|
|
1067
|
+
}
|
|
1068
|
+
return T.array(arr.element);
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
if (method === "reduce") {
|
|
1072
|
+
const init = (argVals as TypeValue[])[1];
|
|
1073
|
+
if (arr.kind === "tuple") {
|
|
1074
|
+
let acc = init ?? arr.elements[0] ?? T.unknown;
|
|
1075
|
+
const startIdx = init ? 0 : 1;
|
|
1076
|
+
for (let i = startIdx; i < arr.elements.length; i++) {
|
|
1077
|
+
acc = callFunction(fn, [acc, arr.elements[i], T.literal(i), arr]);
|
|
1078
|
+
}
|
|
1079
|
+
return acc;
|
|
1080
|
+
}
|
|
1081
|
+
const acc = init ?? arr.element;
|
|
1082
|
+
return callFunction(fn, [acc, arr.element, T.number, arr]);
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
if (method === "find") {
|
|
1086
|
+
const elementType = arr.kind === "tuple"
|
|
1087
|
+
? simplifyUnion(arr.elements)
|
|
1088
|
+
: arr.element;
|
|
1089
|
+
return simplifyUnion([elementType, T.undefined]);
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
if (method === "some" || method === "every") {
|
|
1093
|
+
if (arr.kind === "tuple") {
|
|
1094
|
+
const results = arr.elements.map((el, i) =>
|
|
1095
|
+
callFunction(fn, [el, T.literal(i), arr]),
|
|
1096
|
+
);
|
|
1097
|
+
const allLiteral = results.every((r) => r.kind === "literal");
|
|
1098
|
+
if (allLiteral) {
|
|
1099
|
+
const boolVals = results.map((r) => !!(r as TypeValue & { kind: "literal" }).value);
|
|
1100
|
+
return T.literal(method === "some" ? boolVals.some(Boolean) : boolVals.every(Boolean));
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
return T.boolean;
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
if (method === "forEach") {
|
|
1107
|
+
if (arr.kind === "tuple") {
|
|
1108
|
+
arr.elements.forEach((el, i) => callFunction(fn, [el, T.literal(i), arr]));
|
|
1109
|
+
} else {
|
|
1110
|
+
callFunction(fn, [arr.element, T.number, arr]);
|
|
1111
|
+
}
|
|
1112
|
+
return T.undefined;
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
if (method === "flatMap") {
|
|
1116
|
+
if (arr.kind === "tuple") {
|
|
1117
|
+
const results: TypeValue[] = [];
|
|
1118
|
+
for (let i = 0; i < arr.elements.length; i++) {
|
|
1119
|
+
const r = callFunction(fn, [arr.elements[i], T.literal(i), arr]);
|
|
1120
|
+
if (r.kind === "tuple") results.push(...r.elements);
|
|
1121
|
+
else if (r.kind === "array") return T.array(r.element);
|
|
1122
|
+
else results.push(r);
|
|
1123
|
+
}
|
|
1124
|
+
return T.tuple(results);
|
|
1125
|
+
}
|
|
1126
|
+
const r = callFunction(fn, [arr.element, T.number, arr]);
|
|
1127
|
+
if (r.kind === "tuple") return T.array(simplifyUnion(r.elements));
|
|
1128
|
+
if (r.kind === "array") return T.array(r.element);
|
|
1129
|
+
return T.array(r);
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
return null;
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
function evaluateForOf(
|
|
1136
|
+
node: Node & { type: "ForOfStatement" },
|
|
1137
|
+
iterable: TypeValue,
|
|
1138
|
+
env: Environment,
|
|
1139
|
+
): EvalResult {
|
|
1140
|
+
if (iterable.kind === "tuple") {
|
|
1141
|
+
const returnValues: TypeValue[] = [];
|
|
1142
|
+
let currentEnv = env;
|
|
1143
|
+
for (const element of iterable.elements) {
|
|
1144
|
+
const loopEnv = currentEnv.extend({});
|
|
1145
|
+
bindForLoopVar(node.left, element, loopEnv);
|
|
1146
|
+
const result = evaluate(node.body, loopEnv);
|
|
1147
|
+
if (isReturn(result)) {
|
|
1148
|
+
returnValues.push(result.value);
|
|
1149
|
+
return makeReturn(simplifyUnion(returnValues));
|
|
1150
|
+
}
|
|
1151
|
+
if (isBranch(result)) {
|
|
1152
|
+
returnValues.push(result.returnedValue);
|
|
1153
|
+
currentEnv = result.fallthroughEnv;
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
if (returnValues.length > 0) {
|
|
1157
|
+
return makeBranch(simplifyUnion(returnValues), currentEnv);
|
|
1158
|
+
}
|
|
1159
|
+
return T.undefined;
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
if (iterable.kind === "array") {
|
|
1163
|
+
const loopEnv = env.extend({});
|
|
1164
|
+
bindForLoopVar(node.left, iterable.element, loopEnv);
|
|
1165
|
+
const result = evaluate(node.body, loopEnv);
|
|
1166
|
+
if (isReturn(result)) return makeBranch(result.value, env);
|
|
1167
|
+
return T.undefined;
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
return T.undefined;
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
function evaluateForIn(
|
|
1174
|
+
node: Node & { type: "ForInStatement" },
|
|
1175
|
+
obj: TypeValue,
|
|
1176
|
+
env: Environment,
|
|
1177
|
+
): EvalResult {
|
|
1178
|
+
if (obj.kind === "object") {
|
|
1179
|
+
const keys = Object.keys(obj.properties);
|
|
1180
|
+
if (keys.length > 0) {
|
|
1181
|
+
const returnValues: TypeValue[] = [];
|
|
1182
|
+
let currentEnv = env;
|
|
1183
|
+
for (const key of keys) {
|
|
1184
|
+
const loopEnv = currentEnv.extend({});
|
|
1185
|
+
bindForLoopVar(node.left, T.literal(key), loopEnv);
|
|
1186
|
+
const result = evaluate(node.body, loopEnv);
|
|
1187
|
+
if (isReturn(result)) {
|
|
1188
|
+
returnValues.push(result.value);
|
|
1189
|
+
return makeReturn(simplifyUnion(returnValues));
|
|
1190
|
+
}
|
|
1191
|
+
if (isBranch(result)) {
|
|
1192
|
+
returnValues.push(result.returnedValue);
|
|
1193
|
+
currentEnv = result.fallthroughEnv;
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1196
|
+
if (returnValues.length > 0) {
|
|
1197
|
+
return makeBranch(simplifyUnion(returnValues), currentEnv);
|
|
1198
|
+
}
|
|
1199
|
+
return T.undefined;
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
const loopEnv = env.extend({});
|
|
1204
|
+
bindForLoopVar(node.left, T.string, loopEnv);
|
|
1205
|
+
evaluate(node.body, loopEnv);
|
|
1206
|
+
return T.undefined;
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
function bindForLoopVar(left: Node, value: TypeValue, env: Environment): void {
|
|
1210
|
+
if (left.type === "VariableDeclaration") {
|
|
1211
|
+
const decl = left.declarations[0];
|
|
1212
|
+
if (decl) bindPattern(decl.id, value, env);
|
|
1213
|
+
} else if (left.type === "Identifier") {
|
|
1214
|
+
env.bind(left.name, value);
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
function evaluateImportDeclaration(node: Node & { type: "ImportDeclaration" }, env: Environment): EvalResult {
|
|
1219
|
+
const source = node.source.value;
|
|
1220
|
+
if (!currentModuleResolver) return T.undefined;
|
|
1221
|
+
|
|
1222
|
+
const resolved = currentModuleResolver(source, currentFileDir);
|
|
1223
|
+
if (!resolved) return T.undefined;
|
|
1224
|
+
|
|
1225
|
+
let moduleEnv = moduleCache.get(resolved.filePath);
|
|
1226
|
+
if (!moduleEnv) {
|
|
1227
|
+
moduleEnv = createEnvironment();
|
|
1228
|
+
moduleCache.set(resolved.filePath, moduleEnv);
|
|
1229
|
+
const savedDir = currentFileDir;
|
|
1230
|
+
currentFileDir = resolved.filePath.replace(/\/[^/]+$/, "");
|
|
1231
|
+
evaluateProgram(resolved.ast, moduleEnv);
|
|
1232
|
+
currentFileDir = savedDir;
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
for (const spec of node.specifiers) {
|
|
1236
|
+
if (spec.type === "ImportDefaultSpecifier") {
|
|
1237
|
+
const val = moduleEnv.has(`__export_default`) ? moduleEnv.lookup(`__export_default`) : T.unknown;
|
|
1238
|
+
env.bind(spec.local.name, val);
|
|
1239
|
+
} else if (spec.type === "ImportSpecifier") {
|
|
1240
|
+
const importedName = spec.imported.type === "Identifier" ? spec.imported.name : null;
|
|
1241
|
+
if (importedName) {
|
|
1242
|
+
const val = moduleEnv.has(`__export_${importedName}`) ? moduleEnv.lookup(`__export_${importedName}`) : T.unknown;
|
|
1243
|
+
env.bind(spec.local.name, val);
|
|
1244
|
+
}
|
|
1245
|
+
} else if (spec.type === "ImportNamespaceSpecifier") {
|
|
1246
|
+
const exports: Record<string, TypeValue> = {};
|
|
1247
|
+
const bindings = moduleEnv.getOwnBindings();
|
|
1248
|
+
for (const [k, v] of Object.entries(bindings)) {
|
|
1249
|
+
if (k.startsWith("__export_") && k !== "__export_default") {
|
|
1250
|
+
exports[k.slice("__export_".length)] = v;
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
env.bind(spec.local.name, T.object(exports));
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
return T.undefined;
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
function evaluateClassDeclaration(node: Node & { type: "ClassDeclaration" }, env: Environment): EvalResult {
|
|
1261
|
+
const className = node.id?.name ?? "<anonymous>";
|
|
1262
|
+
const methods: Record<string, TypeValue> = {};
|
|
1263
|
+
let constructorFn: (TypeValue & { kind: "function" }) | null = null;
|
|
1264
|
+
|
|
1265
|
+
for (const member of node.body.body) {
|
|
1266
|
+
if (member.type !== "ClassMethod") continue;
|
|
1267
|
+
const methodName = member.key.type === "Identifier" ? member.key.name : null;
|
|
1268
|
+
if (!methodName) continue;
|
|
1269
|
+
|
|
1270
|
+
const paramNames = member.params.map((p: Node) =>
|
|
1271
|
+
p.type === "Identifier" ? p.name : `_p${Math.random().toString(36).slice(2, 6)}`,
|
|
1272
|
+
);
|
|
1273
|
+
const fnType = T.fn(paramNames, member.body, env) as TypeValue & { kind: "function" };
|
|
1274
|
+
(fnType as any)._paramPatterns = member.params;
|
|
1275
|
+
if (member.async) (fnType as any)._async = true;
|
|
1276
|
+
|
|
1277
|
+
if (member.kind === "constructor") {
|
|
1278
|
+
constructorFn = fnType;
|
|
1279
|
+
} else {
|
|
1280
|
+
methods[methodName] = fnType;
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1284
|
+
const ctorFn = constructorFn ?? T.fn([], { type: "BlockStatement", body: [], directives: [] } as any, env) as TypeValue & { kind: "function" };
|
|
1285
|
+
(ctorFn as any)._classInfo = { className, methods };
|
|
1286
|
+
|
|
1287
|
+
if (node.id) {
|
|
1288
|
+
env.bind(className, ctorFn);
|
|
1289
|
+
}
|
|
1290
|
+
return T.undefined;
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1293
|
+
function evaluateInstanceof(left: TypeValue, _right: TypeValue, rightNode: Node, _env: Environment): TypeValue {
|
|
1294
|
+
const className = rightNode.type === "Identifier" ? rightNode.name : null;
|
|
1295
|
+
if (!className) return T.boolean;
|
|
1296
|
+
|
|
1297
|
+
return distributeOverUnion(left, (lv) => {
|
|
1298
|
+
if (lv.kind === "instance") {
|
|
1299
|
+
const matches = lv.className === className ||
|
|
1300
|
+
isSubtypeOf(lv, T.instanceOf(className));
|
|
1301
|
+
return T.literal(matches);
|
|
1302
|
+
}
|
|
1303
|
+
return T.boolean;
|
|
1304
|
+
});
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
const BUILTIN_ERROR_CLASSES = new Set([
|
|
1308
|
+
"Error", "TypeError", "SyntaxError", "RangeError", "ReferenceError", "URIError", "EvalError",
|
|
1309
|
+
]);
|
|
1310
|
+
|
|
1311
|
+
function evaluateNewExpression(node: Node & { type: "NewExpression" }, env: Environment): EvalResult {
|
|
1312
|
+
const callee = node.callee as Node;
|
|
1313
|
+
if (callee.type === "Identifier" && BUILTIN_ERROR_CLASSES.has(callee.name)) {
|
|
1314
|
+
const argVals = evaluateArgs(node.arguments as Node[], env);
|
|
1315
|
+
if (isReturn(argVals) || isBranch(argVals) || isThrow(argVals)) return argVals;
|
|
1316
|
+
const msgVal = (argVals as TypeValue[])[0] ?? T.undefined;
|
|
1317
|
+
return T.instanceOf(callee.name, { message: msgVal });
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
const calleeVal = evaluate(callee, env);
|
|
1321
|
+
if (isReturn(calleeVal) || isBranch(calleeVal) || isThrow(calleeVal)) return calleeVal;
|
|
1322
|
+
|
|
1323
|
+
if (calleeVal.kind === "function") {
|
|
1324
|
+
const argVals = evaluateArgs(node.arguments as Node[], env);
|
|
1325
|
+
if (isReturn(argVals) || isBranch(argVals) || isThrow(argVals)) return argVals;
|
|
1326
|
+
|
|
1327
|
+
const classInfo = (calleeVal as any)._classInfo as { className: string; methods: Record<string, TypeValue> } | undefined;
|
|
1328
|
+
if (classInfo) {
|
|
1329
|
+
const instanceProps: Record<string, TypeValue> = {};
|
|
1330
|
+
const constructEnv = calleeVal.closure.extend({});
|
|
1331
|
+
const thisObj = T.object(instanceProps);
|
|
1332
|
+
constructEnv.bind("this", thisObj);
|
|
1333
|
+
const paramPatterns = (calleeVal as any)._paramPatterns as Node[] | undefined;
|
|
1334
|
+
for (let i = 0; i < calleeVal.params.length; i++) {
|
|
1335
|
+
const argVal = (argVals as TypeValue[])[i] ?? T.undefined;
|
|
1336
|
+
if (paramPatterns?.[i]) {
|
|
1337
|
+
bindPattern(paramPatterns[i], argVal, constructEnv);
|
|
1338
|
+
} else {
|
|
1339
|
+
constructEnv.bind(calleeVal.params[i], argVal);
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1342
|
+
const result = evaluate(calleeVal.body, constructEnv);
|
|
1343
|
+
if (isThrow(result)) return result;
|
|
1344
|
+
const finalThis = constructEnv.lookup("this");
|
|
1345
|
+
const props = finalThis.kind === "object" ? { ...finalThis.properties } : instanceProps;
|
|
1346
|
+
for (const [k, v] of Object.entries(classInfo.methods)) {
|
|
1347
|
+
props[k] = v;
|
|
1348
|
+
}
|
|
1349
|
+
return T.instanceOf(classInfo.className, props);
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
return callFunction(calleeVal, argVals as TypeValue[]);
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1355
|
+
return T.unknown;
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
function evaluateTryStatement(node: Node & { type: "TryStatement" }, env: Environment): EvalResult {
|
|
1359
|
+
const tryResult = evaluateStatements(node.block.body, env.extend({}));
|
|
1360
|
+
|
|
1361
|
+
const thrownType = isThrow(tryResult) ? tryResult.thrown : null;
|
|
1362
|
+
|
|
1363
|
+
const tryValue = isThrow(tryResult)
|
|
1364
|
+
? null
|
|
1365
|
+
: isReturn(tryResult)
|
|
1366
|
+
? tryResult
|
|
1367
|
+
: isBranch(tryResult)
|
|
1368
|
+
? tryResult
|
|
1369
|
+
: tryResult;
|
|
1370
|
+
|
|
1371
|
+
let catchResult: EvalResult | null = null;
|
|
1372
|
+
if (node.handler && thrownType) {
|
|
1373
|
+
const catchEnv = env.extend({});
|
|
1374
|
+
if (node.handler.param) {
|
|
1375
|
+
bindPattern(node.handler.param, thrownType, catchEnv);
|
|
1376
|
+
}
|
|
1377
|
+
catchResult = evaluateStatements(node.handler.body.body, catchEnv);
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1380
|
+
if (node.finalizer) {
|
|
1381
|
+
const finallyResult = evaluateStatements(node.finalizer.body, env.extend({}));
|
|
1382
|
+
if (isThrow(finallyResult)) return finallyResult;
|
|
1383
|
+
if (isReturn(finallyResult)) return finallyResult;
|
|
1384
|
+
}
|
|
1385
|
+
|
|
1386
|
+
if (catchResult !== null) {
|
|
1387
|
+
if (isThrow(catchResult)) return catchResult;
|
|
1388
|
+
if (isReturn(catchResult)) {
|
|
1389
|
+
if (tryValue !== null && isReturn(tryValue)) {
|
|
1390
|
+
return makeReturn(simplifyUnion([tryValue.value, catchResult.value]));
|
|
1391
|
+
}
|
|
1392
|
+
return catchResult;
|
|
1393
|
+
}
|
|
1394
|
+
if (tryValue !== null && isReturn(tryValue)) {
|
|
1395
|
+
return tryValue;
|
|
1396
|
+
}
|
|
1397
|
+
if (tryValue !== null && isBranch(tryValue)) {
|
|
1398
|
+
return tryValue;
|
|
1399
|
+
}
|
|
1400
|
+
return catchResult;
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
if (thrownType && !node.handler) {
|
|
1404
|
+
return makeThrow(thrownType);
|
|
1405
|
+
}
|
|
1406
|
+
|
|
1407
|
+
if (tryValue !== null) return tryValue;
|
|
1408
|
+
return T.undefined;
|
|
1409
|
+
}
|
|
1410
|
+
|
|
1411
|
+
function evaluateSwitchStatement(node: Node & { type: "SwitchStatement" }, env: Environment): EvalResult {
|
|
1412
|
+
const discriminant = evaluate(node.discriminant, env);
|
|
1413
|
+
if (isReturn(discriminant) || isBranch(discriminant) || isThrow(discriminant)) return discriminant;
|
|
1414
|
+
|
|
1415
|
+
const isConcreteDiscriminant = discriminant.kind === "literal";
|
|
1416
|
+
|
|
1417
|
+
if (isConcreteDiscriminant) {
|
|
1418
|
+
let matched = false;
|
|
1419
|
+
const returnValues: TypeValue[] = [];
|
|
1420
|
+
for (const caseNode of node.cases) {
|
|
1421
|
+
if (caseNode.test) {
|
|
1422
|
+
const testVal = evaluate(caseNode.test, env);
|
|
1423
|
+
if (isReturn(testVal) || isBranch(testVal) || isThrow(testVal)) return testVal;
|
|
1424
|
+
if (testVal.kind === "literal" && discriminant.value === testVal.value) matched = true;
|
|
1425
|
+
} else {
|
|
1426
|
+
matched = true;
|
|
1427
|
+
}
|
|
1428
|
+
if (matched) {
|
|
1429
|
+
const result = evaluateStatements(caseNode.consequent, env);
|
|
1430
|
+
if (isThrow(result)) return result;
|
|
1431
|
+
if (isReturn(result)) {
|
|
1432
|
+
returnValues.push(result.value);
|
|
1433
|
+
break;
|
|
1434
|
+
}
|
|
1435
|
+
if (isBranch(result)) {
|
|
1436
|
+
returnValues.push(result.returnedValue);
|
|
1437
|
+
continue;
|
|
1438
|
+
}
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1441
|
+
if (returnValues.length > 0) {
|
|
1442
|
+
return makeBranch(simplifyUnion(returnValues), env);
|
|
1443
|
+
}
|
|
1444
|
+
return T.undefined;
|
|
1445
|
+
}
|
|
1446
|
+
|
|
1447
|
+
const returnValues: TypeValue[] = [];
|
|
1448
|
+
for (const caseNode of node.cases) {
|
|
1449
|
+
const result = evaluateStatements(caseNode.consequent, env);
|
|
1450
|
+
if (isThrow(result)) continue;
|
|
1451
|
+
if (isReturn(result)) {
|
|
1452
|
+
returnValues.push(result.value);
|
|
1453
|
+
continue;
|
|
1454
|
+
}
|
|
1455
|
+
if (isBranch(result)) {
|
|
1456
|
+
returnValues.push(result.returnedValue);
|
|
1457
|
+
continue;
|
|
1458
|
+
}
|
|
1459
|
+
}
|
|
1460
|
+
if (returnValues.length > 0) {
|
|
1461
|
+
return makeBranch(simplifyUnion(returnValues), env);
|
|
1462
|
+
}
|
|
1463
|
+
return T.undefined;
|
|
1464
|
+
}
|
|
1465
|
+
|
|
1466
|
+
type CallResult = {
|
|
1467
|
+
value: TypeValue;
|
|
1468
|
+
throws: TypeValue;
|
|
1469
|
+
throwLoc?: SourceRange;
|
|
1470
|
+
};
|
|
1471
|
+
|
|
1472
|
+
function callFunctionFull(fn: TypeValue & { kind: "function" }, args: TypeValue[]): CallResult {
|
|
1473
|
+
const callEnv = fn.closure.extend({});
|
|
1474
|
+
const paramPatterns = (fn as any)._paramPatterns as Node[] | undefined;
|
|
1475
|
+
const isAsync = !!(fn as any)._async;
|
|
1476
|
+
for (let i = 0; i < fn.params.length; i++) {
|
|
1477
|
+
const argVal = args[i] ?? T.undefined;
|
|
1478
|
+
if (paramPatterns && paramPatterns[i]) {
|
|
1479
|
+
bindPattern(paramPatterns[i], argVal, callEnv);
|
|
1480
|
+
} else {
|
|
1481
|
+
callEnv.bind(fn.params[i], argVal);
|
|
1482
|
+
}
|
|
1483
|
+
}
|
|
1484
|
+
|
|
1485
|
+
const savedUnreachable = _unreachableRanges;
|
|
1486
|
+
_unreachableRanges = [];
|
|
1487
|
+
|
|
1488
|
+
const memoKey = buildMemoKey(fn, args);
|
|
1489
|
+
if (memoKey !== null) {
|
|
1490
|
+
const cached = callMemo.get(memoKey);
|
|
1491
|
+
if (cached !== undefined) {
|
|
1492
|
+
_unreachableRanges = savedUnreachable;
|
|
1493
|
+
if (cached === MEMO_IN_PROGRESS) {
|
|
1494
|
+
return { value: T.unknown, throws: T.never };
|
|
1495
|
+
}
|
|
1496
|
+
return { value: cached, throws: T.never };
|
|
1497
|
+
}
|
|
1498
|
+
callMemo.set(memoKey, MEMO_IN_PROGRESS);
|
|
1499
|
+
const result = evaluate(fn.body, callEnv);
|
|
1500
|
+
_unreachableRanges = savedUnreachable;
|
|
1501
|
+
const value = isReturn(result) ? result.value
|
|
1502
|
+
: isBranch(result) ? result.returnedValue
|
|
1503
|
+
: isThrow(result) ? T.never
|
|
1504
|
+
: result;
|
|
1505
|
+
const throws = isThrow(result) ? result.thrown : T.never;
|
|
1506
|
+
const throwLoc = isThrow(result) ? result.loc : undefined;
|
|
1507
|
+
const wrapped = isAsync ? T.promise(value) : value;
|
|
1508
|
+
callMemo.set(memoKey, wrapped);
|
|
1509
|
+
return { value: wrapped, throws, throwLoc };
|
|
1510
|
+
}
|
|
1511
|
+
|
|
1512
|
+
const result = evaluate(fn.body, callEnv);
|
|
1513
|
+
_unreachableRanges = savedUnreachable;
|
|
1514
|
+
if (isThrow(result)) {
|
|
1515
|
+
return { value: T.never, throws: result.thrown, throwLoc: result.loc };
|
|
1516
|
+
}
|
|
1517
|
+
const value = isReturn(result) ? result.value
|
|
1518
|
+
: isBranch(result) ? result.returnedValue
|
|
1519
|
+
: result;
|
|
1520
|
+
const wrapped = isAsync ? T.promise(value) : value;
|
|
1521
|
+
return { value: wrapped, throws: T.never };
|
|
1522
|
+
}
|
|
1523
|
+
|
|
1524
|
+
function callFunction(fn: TypeValue & { kind: "function" }, args: TypeValue[]): TypeValue {
|
|
1525
|
+
return callFunctionFull(fn, args).value;
|
|
1526
|
+
}
|
|
1527
|
+
|
|
1528
|
+
export function evaluateFunction(
|
|
1529
|
+
fnNode: Node,
|
|
1530
|
+
args: TypeValue[],
|
|
1531
|
+
env: Environment,
|
|
1532
|
+
): TypeValue {
|
|
1533
|
+
return evaluateFunctionFull(fnNode, args, env).value;
|
|
1534
|
+
}
|
|
1535
|
+
|
|
1536
|
+
export function evaluateFunctionFull(
|
|
1537
|
+
fnNode: Node,
|
|
1538
|
+
args: TypeValue[],
|
|
1539
|
+
env: Environment,
|
|
1540
|
+
): CallResult {
|
|
1541
|
+
if (fnNode.type === "FunctionDeclaration" || fnNode.type === "FunctionExpression") {
|
|
1542
|
+
const callEnv = env.extend({});
|
|
1543
|
+
const isAsync = !!(fnNode as any).async;
|
|
1544
|
+
for (let i = 0; i < fnNode.params.length; i++) {
|
|
1545
|
+
bindPattern(fnNode.params[i], args[i] ?? T.undefined, callEnv);
|
|
1546
|
+
}
|
|
1547
|
+
const result = evaluate(fnNode.body, callEnv);
|
|
1548
|
+
if (isThrow(result)) return { value: T.never, throws: result.thrown, throwLoc: result.loc };
|
|
1549
|
+
const value = isReturn(result) ? result.value
|
|
1550
|
+
: isBranch(result) ? result.returnedValue
|
|
1551
|
+
: result;
|
|
1552
|
+
const wrapped = isAsync ? T.promise(value) : value;
|
|
1553
|
+
return { value: wrapped, throws: T.never };
|
|
1554
|
+
}
|
|
1555
|
+
return { value: T.unknown, throws: T.never };
|
|
1556
|
+
}
|
|
1557
|
+
|
|
1558
|
+
export function evaluateProgram(node: Node, env: Environment): TypeValue {
|
|
1559
|
+
const result = evaluate(node, env);
|
|
1560
|
+
if (isReturn(result)) return result.value;
|
|
1561
|
+
if (isBranch(result)) return result.returnedValue;
|
|
1562
|
+
if (isThrow(result)) return T.never;
|
|
1563
|
+
return result;
|
|
1564
|
+
}
|
|
1565
|
+
|
|
1566
|
+
export function getUnreachableRanges(): SourceRange[] {
|
|
1567
|
+
return _unreachableRanges;
|
|
1568
|
+
}
|
|
1569
|
+
|
|
1570
|
+
export function resetUnreachableRanges(): void {
|
|
1571
|
+
_unreachableRanges = [];
|
|
1572
|
+
}
|
|
1573
|
+
|
|
1574
|
+
export function setCurrentFileDir(dir: string): void {
|
|
1575
|
+
currentFileDir = dir;
|
|
1576
|
+
}
|