@nudojs/cli 0.2.1 → 0.3.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/package.json +12 -7
- package/src/abs-route.ts +273 -0
- package/src/builtins/builtin-prototype.ts +279 -0
- package/src/builtins/builtin-static.ts +260 -0
- package/src/eval-binary.ts +113 -0
- package/src/evaluator-api.ts +3 -0
- package/src/evaluator.ts +174 -633
- package/src/index.ts +373 -270
- package/src/narrowing.ts +7 -0
- package/src/phi-from-test.ts +42 -0
- package/src/run-test.ts +92 -0
- package/src/term-registry.ts +96 -0
- package/CHANGELOG.md +0 -41
- package/dist/chunk-3HCPXPEW.js +0 -5286
- package/dist/evaluator-api.d.ts +0 -151
- package/dist/evaluator-api.js +0 -214
- package/dist/index.d.ts +0 -2
- package/dist/index.js +0 -4035
- package/examples/real-world.js +0 -66
- package/examples/validate-real-world.ts +0 -160
- package/src/__tests__/builtin-advanced.test.ts +0 -292
- package/src/__tests__/builtin-api.test.ts +0 -308
- package/src/__tests__/builtin-functions.test.ts +0 -313
- package/src/__tests__/builtin-map.test.ts +0 -129
- package/src/__tests__/builtin-promise.test.ts +0 -150
- package/src/__tests__/builtin-regexp.test.ts +0 -133
- package/src/__tests__/builtin-set.test.ts +0 -163
- package/src/__tests__/builtin-url.test.ts +0 -256
- package/src/__tests__/builtin-web.test.ts +0 -340
- package/src/__tests__/combination-scenarios.test.ts +0 -384
- package/src/__tests__/edge-cases.test.ts +0 -749
- package/src/__tests__/evaluator-loops.test.ts +0 -283
- package/src/__tests__/evaluator-nullish-coalescing.test.ts +0 -86
- package/src/__tests__/evaluator-optional-chaining.test.ts +0 -37
- package/src/__tests__/evaluator-phase2.test.ts +0 -564
- package/src/__tests__/evaluator-phase3.test.ts +0 -864
- package/src/__tests__/evaluator-refined.test.ts +0 -121
- package/src/__tests__/evaluator-string-methods.test.ts +0 -151
- package/src/__tests__/evaluator.test.ts +0 -943
- package/src/__tests__/mock-arrow-function.test.ts +0 -169
- package/src/__tests__/mock-comparison.test.ts +0 -221
- package/src/__tests__/mock-comprehensive.test.ts +0 -250
- package/src/__tests__/mock-final-integration.test.ts +0 -263
- package/src/__tests__/mock-helpers.test.ts +0 -174
- package/src/__tests__/mock-realworld.test.ts +0 -191
- package/src/__tests__/mock-rest-debug.test.ts +0 -110
- package/src/__tests__/mock-rest-edge.test.ts +0 -176
- package/src/__tests__/mock-sinon-runtime.test.ts +0 -168
- package/src/__tests__/narrowing-discriminated.test.ts +0 -35
- package/src/__tests__/narrowing-in-operator.test.ts +0 -56
- package/src/__tests__/narrowing-switch.test.ts +0 -56
- package/src/__tests__/narrowing-truthiness.test.ts +0 -52
- package/src/__tests__/narrowing.test.ts +0 -82
- package/src/__tests__/nudo-mock-syntax.test.ts +0 -245
- package/src/__tests__/realworld-patterns.test.ts +0 -430
- package/src/__tests__/syntax-sugar.test.ts +0 -345
- package/src/builtins/index.ts +0 -61
- package/tsconfig.json +0 -9
package/src/evaluator.ts
CHANGED
|
@@ -3,7 +3,6 @@ import {
|
|
|
3
3
|
type TypeValue,
|
|
4
4
|
T,
|
|
5
5
|
simplifyUnion,
|
|
6
|
-
applyBinaryOp,
|
|
7
6
|
dispatchBinaryOp,
|
|
8
7
|
dispatchMethod,
|
|
9
8
|
dispatchProperty,
|
|
@@ -23,7 +22,22 @@ import {
|
|
|
23
22
|
} from "@nudojs/core";
|
|
24
23
|
import { extractInlineDirectives, type InlineDirective } from "@nudojs/parser";
|
|
25
24
|
import { narrow } from "./narrowing.ts";
|
|
26
|
-
import {
|
|
25
|
+
import {
|
|
26
|
+
tryAbsObjectSpread,
|
|
27
|
+
tryAbsJoinObjects,
|
|
28
|
+
tryAbsUnary,
|
|
29
|
+
pushPhi,
|
|
30
|
+
popPhi,
|
|
31
|
+
currentPhi,
|
|
32
|
+
resetPhi,
|
|
33
|
+
} from "./abs-route.ts";
|
|
34
|
+
import {
|
|
35
|
+
evalBinaryValue,
|
|
36
|
+
distributeBinaryOverUnion,
|
|
37
|
+
} from "./eval-binary.ts";
|
|
38
|
+
import { phiFromTest, combinePhi } from "./phi-from-test.ts";
|
|
39
|
+
import { tagParamArg } from "./term-registry.ts";
|
|
40
|
+
import { evaluatePromiseStaticMethod, evaluatePromiseInstanceMethod } from "./builtins/builtin-promise.ts";
|
|
27
41
|
import { MAP_INSTANCE_METHODS, createMapType, mapEntriesIterable, exactMapEntries } from "./builtins/builtin-map.ts";
|
|
28
42
|
import { SET_INSTANCE_METHODS, createSetType, setValuesIterable, exactSetValues } from "./builtins/builtin-set.ts";
|
|
29
43
|
import { REGEXP_INSTANCE_METHODS, createRegExpType } from "./builtins/builtin-regexp.ts";
|
|
@@ -39,427 +53,26 @@ import {
|
|
|
39
53
|
createAbortControllerType,
|
|
40
54
|
} from "./builtins/builtin-web.ts";
|
|
41
55
|
import { WEAKMAP_INSTANCE_METHODS, WEAKSET_INSTANCE_METHODS, createWeakMapType, createWeakSetType } from "./builtins/builtin-weak.ts";
|
|
42
|
-
import {
|
|
56
|
+
import { SYMBOL_STATIC_PROPS } from "./builtins/builtin-symbol.ts";
|
|
43
57
|
import { REFLECT_METHODS } from "./builtins/builtin-reflect.ts";
|
|
44
58
|
import { INTL_DATETIMEFORMAT_METHODS, INTL_NUMBERFORMAT_METHODS, createDateTimeFormatType, createNumberFormatType } from "./builtins/builtin-intl.ts";
|
|
59
|
+
import {
|
|
60
|
+
BUILTIN_ERROR_CLASSES,
|
|
61
|
+
BUILTIN_PROTOTYPE_CLASSES,
|
|
62
|
+
OBJECT_PROTOTYPE_METHODS,
|
|
63
|
+
BUILTIN_PROTOTYPE_METHOD_APPROXIMATIONS,
|
|
64
|
+
builtinClassValue,
|
|
65
|
+
builtinPrototype,
|
|
66
|
+
} from "./builtins/builtin-prototype.ts";
|
|
67
|
+
import { BUILTIN_STATIC_METHODS, builtinInstanceTest, jsonToTypeValue } from "./builtins/builtin-static.ts";
|
|
68
|
+
|
|
45
69
|
|
|
46
|
-
// Built-in JavaScript API type mappings
|
|
47
|
-
// Namespace objects (e.g. Math.floor) and direct global values (e.g. parseInt)
|
|
48
|
-
const BUILTIN_STATIC_METHODS: Record<string, Record<string, TypeValue> | TypeValue> = {
|
|
49
|
-
// Node CJS 库普遍依赖的 process 全局——缺它时 process.nextTick(cb) 走
|
|
50
|
-
// 未知全局 → undefined → no-method 假错(hoek wait 的 usage-site 执行)
|
|
51
|
-
process: {
|
|
52
|
-
nextTick: T.fn(["callback", "...args"], { type: "BlockStatement", body: [], directives: [] } as any, undefined as any),
|
|
53
|
-
env: T.object({}),
|
|
54
|
-
platform: T.string,
|
|
55
|
-
versions: T.object({}),
|
|
56
|
-
argv: T.array(T.string),
|
|
57
|
-
cwd: T.fn([], { type: "BlockStatement", body: [], directives: [] } as any, undefined as any),
|
|
58
|
-
exit: T.fn(["code"], { type: "BlockStatement", body: [], directives: [] } as any, undefined as any),
|
|
59
|
-
hrtime: T.fn(["time"], { type: "BlockStatement", body: [], directives: [] } as any, undefined as any),
|
|
60
|
-
stdout: T.instanceOf("Socket"),
|
|
61
|
-
stderr: T.instanceOf("Socket"),
|
|
62
|
-
},
|
|
63
|
-
Date: {
|
|
64
|
-
now: T.number,
|
|
65
|
-
parse: T.number,
|
|
66
|
-
UTC: T.number,
|
|
67
|
-
},
|
|
68
|
-
Math: {
|
|
69
|
-
random: T.number,
|
|
70
|
-
floor: T.fn(["x"], { type: "BlockStatement", body: [] } as any, undefined as any),
|
|
71
|
-
ceil: T.fn(["x"], { type: "BlockStatement", body: [] } as any, undefined as any),
|
|
72
|
-
round: T.fn(["x"], { type: "BlockStatement", body: [] } as any, undefined as any),
|
|
73
|
-
abs: T.fn(["x"], { type: "BlockStatement", body: [] } as any, undefined as any),
|
|
74
|
-
max: T.fn(["...args"], { type: "BlockStatement", body: [] } as any, undefined as any),
|
|
75
|
-
min: T.fn(["...args"], { type: "BlockStatement", body: [] } as any, undefined as any),
|
|
76
|
-
sqrt: T.fn(["x"], { type: "BlockStatement", body: [] } as any, undefined as any),
|
|
77
|
-
pow: T.fn(["base", "exp"], { type: "BlockStatement", body: [] } as any, undefined as any),
|
|
78
|
-
// Math 常量与 @nudo:env es 的 Math 定义对齐(env 绑定优先);无 env
|
|
79
|
-
// 指令时也在此解析,避免 Math.PI 走 unknown-property。
|
|
80
|
-
PI: T.number,
|
|
81
|
-
E: T.number,
|
|
82
|
-
LN2: T.number,
|
|
83
|
-
LN10: T.number,
|
|
84
|
-
LOG2E: T.number,
|
|
85
|
-
LOG10E: T.number,
|
|
86
|
-
SQRT2: T.number,
|
|
87
|
-
SQRT1_2: T.number,
|
|
88
|
-
},
|
|
89
|
-
JSON: {
|
|
90
|
-
// JSON.parse(literal-string): evaluate with the real JSON.parse and
|
|
91
|
-
// re-encode through jsonToTypeValue, so fixture-style calls
|
|
92
|
-
// (`JSON.parse('{"a":1}')`) keep literal-level precision instead of
|
|
93
|
-
// poisoning downstream clone/deepEqual args with unknown. Degrades to
|
|
94
|
-
// the unknown fallback when the argument is not a literal string or
|
|
95
|
-
// the text is not valid JSON. A reviver argument also degrades: it can
|
|
96
|
-
// replace every value (and the root) with an arbitrary one, so the
|
|
97
|
-
// parse structure is not a sound answer (hoek/json-ext never pass one).
|
|
98
|
-
parse: T.fnSig([T.string], T.unknown, T.never, (args) => {
|
|
99
|
-
const text = args[0];
|
|
100
|
-
if (text?.kind !== "literal" || typeof text.value !== "string") return undefined;
|
|
101
|
-
const reviver = args[1];
|
|
102
|
-
if (reviver && !(reviver.kind === "literal" && reviver.value === undefined)) return undefined;
|
|
103
|
-
try {
|
|
104
|
-
return jsonToTypeValue(JSON.parse(text.value));
|
|
105
|
-
} catch {
|
|
106
|
-
return undefined;
|
|
107
|
-
}
|
|
108
|
-
}),
|
|
109
|
-
stringify: T.string,
|
|
110
|
-
},
|
|
111
|
-
Object: {
|
|
112
|
-
keys: T.array(T.string),
|
|
113
|
-
values: T.array(T.unknown),
|
|
114
|
-
entries: T.array(T.tuple([T.string, T.unknown])),
|
|
115
|
-
assign: T.fnSig([T.unknown], T.unknown, T.never, (args) => {
|
|
116
|
-
// Object.assign(target, ...sources): later sources overwrite; a
|
|
117
|
-
// symbolic source keeps the symbolic unknown fallback.
|
|
118
|
-
const target = args[0];
|
|
119
|
-
if (target?.kind !== "object") return undefined;
|
|
120
|
-
const props: Record<string, TypeValue> = { ...target.properties };
|
|
121
|
-
for (const src of args.slice(1)) {
|
|
122
|
-
if (src?.kind !== "object") return undefined;
|
|
123
|
-
Object.assign(props, src.properties);
|
|
124
|
-
}
|
|
125
|
-
return T.object(props);
|
|
126
|
-
}),
|
|
127
|
-
// Prototype-reflection statics. getPrototypeOf maps receivers onto the
|
|
128
|
-
// cached prototype singletons so `getPrototypeOf(x) !== getPrototypeOf(y)`
|
|
129
|
-
// resolves literally; create/setPrototypeOf cover the clone-style
|
|
130
|
-
// prototype dance; getOwnPropertyDescriptor feeds descriptor.get/set
|
|
131
|
-
// branches with the property's value type.
|
|
132
|
-
getPrototypeOf: T.fnSig([T.unknown], T.unknown, T.never, (args) => protoOfValue(args[0])),
|
|
133
|
-
create: T.fnSig([T.unknown], T.object({}), T.never, () => T.object({})),
|
|
134
|
-
setPrototypeOf: T.fnSig([T.unknown, T.unknown], T.unknown, T.never, (args) => args[0]),
|
|
135
|
-
defineProperty: T.fnSig([T.unknown, T.unknown, T.unknown], T.unknown, T.never, (args) => args[0]),
|
|
136
|
-
getOwnPropertyDescriptor: T.fnSig([T.unknown, T.unknown], T.union(T.object({}), T.undefined), T.never, (args) => {
|
|
137
|
-
const obj = args[0];
|
|
138
|
-
const key = args[1];
|
|
139
|
-
if (obj?.kind !== "object" && obj?.kind !== "instance") return T.undefined;
|
|
140
|
-
if (!key) return T.undefined;
|
|
141
|
-
if (key.kind === "literal" && typeof key.value === "string") {
|
|
142
|
-
const prop = hasOwnProp(obj.properties, key.value) ? obj.properties[key.value] : undefined;
|
|
143
|
-
if (!prop) return T.undefined;
|
|
144
|
-
return descriptorOf(prop);
|
|
145
|
-
}
|
|
146
|
-
if (key.kind === "primitive" && key.type === "string") {
|
|
147
|
-
const props = Object.keys(obj.properties);
|
|
148
|
-
if (props.length === 0) return T.undefined;
|
|
149
|
-
return descriptorOf(simplifyUnion(props.map((k) => obj.properties[k])));
|
|
150
|
-
}
|
|
151
|
-
return T.undefined;
|
|
152
|
-
}),
|
|
153
|
-
},
|
|
154
|
-
Buffer: {
|
|
155
|
-
from: T.fnSig([T.unknown], T.instanceOf("Buffer")),
|
|
156
|
-
alloc: T.fnSig([T.number], T.instanceOf("Buffer")),
|
|
157
|
-
concat: T.fnSig([T.unknown], T.instanceOf("Buffer")),
|
|
158
|
-
byteLength: T.fnSig([T.unknown], T.number),
|
|
159
|
-
isEncoding: T.fnSig([T.unknown], T.boolean),
|
|
160
|
-
isBuffer: T.fnSig([T.unknown], T.boolean, T.never, (args) => {
|
|
161
|
-
const lit = builtinInstanceTest(args[0], "Buffer");
|
|
162
|
-
return lit === undefined ? undefined : T.literal(lit);
|
|
163
|
-
}),
|
|
164
|
-
},
|
|
165
|
-
Array: {
|
|
166
|
-
isArray: T.boolean,
|
|
167
|
-
from: T.array(T.unknown),
|
|
168
|
-
},
|
|
169
|
-
Number: {
|
|
170
|
-
isNaN: T.boolean,
|
|
171
|
-
isFinite: T.boolean,
|
|
172
|
-
parseInt: T.number,
|
|
173
|
-
parseFloat: T.number,
|
|
174
|
-
// Number 常量与 @nudo:env es 的 Number 定义对齐(env 绑定优先);
|
|
175
|
-
// 均为 number(case 序列化器对非有限数返回 null,不产出字面量值)。
|
|
176
|
-
MAX_SAFE_INTEGER: T.number,
|
|
177
|
-
MIN_SAFE_INTEGER: T.number,
|
|
178
|
-
MAX_VALUE: T.number,
|
|
179
|
-
MIN_VALUE: T.number,
|
|
180
|
-
POSITIVE_INFINITY: T.number,
|
|
181
|
-
NEGATIVE_INFINITY: T.number,
|
|
182
|
-
NaN: T.number,
|
|
183
|
-
EPSILON: T.number,
|
|
184
|
-
},
|
|
185
|
-
String: {
|
|
186
|
-
// fromCharCode/fromCodePoint over all-number-literal args evaluate
|
|
187
|
-
// with the real String static (hoek's escaper builds its 0x2028
|
|
188
|
-
// separator literal this way before handing it to escapeJson); any
|
|
189
|
-
// non-literal arg keeps only the string return type (the result is
|
|
190
|
-
// always a string, just not a knowable one). fromCodePoint also
|
|
191
|
-
// falls back on out-of-range literals (real RangeError → string).
|
|
192
|
-
fromCharCode: T.fnSig([T.number], T.string, T.never, (args) => {
|
|
193
|
-
const codes = literalCodeUnits(args);
|
|
194
|
-
return codes === null ? T.string : T.literal(String.fromCharCode(...codes));
|
|
195
|
-
}),
|
|
196
|
-
fromCodePoint: T.fnSig([T.number], T.string, T.never, (args) => {
|
|
197
|
-
const codes = literalCodeUnits(args);
|
|
198
|
-
if (codes === null) return T.string;
|
|
199
|
-
try {
|
|
200
|
-
return T.literal(String.fromCodePoint(...codes));
|
|
201
|
-
} catch {
|
|
202
|
-
return undefined;
|
|
203
|
-
}
|
|
204
|
-
}),
|
|
205
|
-
},
|
|
206
|
-
Promise: PROMISE_STATIC_METHODS,
|
|
207
|
-
Symbol: { ...SYMBOL_STATIC_METHODS, ...SYMBOL_STATIC_PROPS },
|
|
208
|
-
Reflect: REFLECT_METHODS as unknown as Record<string, TypeValue>,
|
|
209
|
-
Intl: {
|
|
210
|
-
DateTimeFormat: T.fn(["...args"], { type: "BlockStatement", body: [] } as any, undefined as any),
|
|
211
|
-
NumberFormat: T.fn(["...args"], { type: "BlockStatement", body: [] } as any, undefined as any),
|
|
212
|
-
},
|
|
213
|
-
parseInt: T.number,
|
|
214
|
-
parseFloat: T.number,
|
|
215
|
-
isNaN: T.boolean,
|
|
216
|
-
isFinite: T.boolean,
|
|
217
|
-
// 全局数值常量:与 @nudo:env es 的 globals 对齐(env 绑定优先)。无 env
|
|
218
|
-
// 指令时在此解析为 number——否则 Infinity/NaN 命中大写开头的未知全局
|
|
219
|
-
// 检测,触发 nudo:unknown-global 与 nudo:builtin-unknown 诊断。
|
|
220
|
-
Infinity: T.number,
|
|
221
|
-
NaN: T.number,
|
|
222
|
-
};
|
|
223
70
|
|
|
224
|
-
/** Harvest String.fromCharCode/fromCodePoint arguments: a list of number
|
|
225
|
-
* literals ready for real evaluation, or null when any argument is
|
|
226
|
-
* non-literal (symbolic → the caller keeps its plain string fallback).
|
|
227
|
-
* An empty argument list yields [] (both statics return ""). */
|
|
228
|
-
function literalCodeUnits(args: TypeValue[]): number[] | null {
|
|
229
|
-
const codes: number[] = [];
|
|
230
|
-
for (const a of args) {
|
|
231
|
-
if (a?.kind !== "literal" || typeof a.value !== "number") return null;
|
|
232
|
-
codes.push(a.value);
|
|
233
|
-
}
|
|
234
|
-
return codes;
|
|
235
|
-
}
|
|
236
71
|
|
|
237
|
-
/** `==`/`!=` 静态求值可用的字面量值:number/string/boolean/null/undefined
|
|
238
|
-
* 这五种原始值走 JS 宽松相等的 ToNumber 强转路径,可直接用宿主 `==`
|
|
239
|
-
* 求值(与规范一致);bigint/symbol 无该路径,hit=false 交给调用方退化
|
|
240
|
-
* 为 T.boolean。undefined 不能作哨兵值(它本身就是合法字面量值)。 */
|
|
241
|
-
function looseLiteralValue(
|
|
242
|
-
tv: TypeValue,
|
|
243
|
-
): { hit: boolean; v: number | string | boolean | null | undefined } {
|
|
244
|
-
if (tv.kind === "literal") {
|
|
245
|
-
const v = tv.value;
|
|
246
|
-
if (
|
|
247
|
-
typeof v === "number" ||
|
|
248
|
-
typeof v === "string" ||
|
|
249
|
-
typeof v === "boolean" ||
|
|
250
|
-
v === null ||
|
|
251
|
-
v === undefined
|
|
252
|
-
) {
|
|
253
|
-
return { hit: true, v };
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
return { hit: false, v: undefined };
|
|
257
|
-
}
|
|
258
72
|
|
|
259
|
-
const BUILTIN_INSTANCE_METHODS: Record<string, Record<string, (...args: TypeValue[]) => TypeValue>> = {
|
|
260
|
-
Date: {
|
|
261
|
-
getTime: () => T.number,
|
|
262
|
-
getFullYear: () => T.number,
|
|
263
|
-
getMonth: () => T.number,
|
|
264
|
-
getDate: () => T.number,
|
|
265
|
-
getHours: () => T.number,
|
|
266
|
-
getMinutes: () => T.number,
|
|
267
|
-
getSeconds: () => T.number,
|
|
268
|
-
getMilliseconds: () => T.number,
|
|
269
|
-
toISOString: () => T.string,
|
|
270
|
-
toString: () => T.string,
|
|
271
|
-
valueOf: () => T.number,
|
|
272
|
-
},
|
|
273
|
-
WeakMap: WEAKMAP_INSTANCE_METHODS,
|
|
274
|
-
WeakSet: WEAKSET_INSTANCE_METHODS,
|
|
275
|
-
DateTimeFormat: INTL_DATETIMEFORMAT_METHODS,
|
|
276
|
-
NumberFormat: INTL_NUMBERFORMAT_METHODS,
|
|
277
|
-
};
|
|
278
73
|
|
|
279
|
-
const BUILTIN_ERROR_CLASSES = new Set([
|
|
280
|
-
"Error", "TypeError", "SyntaxError", "RangeError", "ReferenceError", "URIError", "EvalError",
|
|
281
|
-
]);
|
|
282
|
-
|
|
283
|
-
// Constructible built-in classes. A bare reference to one of these names
|
|
284
|
-
// resolves to a namespace object (like the BUILTIN_STATIC_METHODS entries),
|
|
285
|
-
// and `X.prototype` evaluates to an instance of X instead of degrading to
|
|
286
|
-
// undefined/unknown.
|
|
287
|
-
const BUILTIN_PROTOTYPE_CLASSES = new Set([
|
|
288
|
-
...BUILTIN_ERROR_CLASSES,
|
|
289
|
-
"Date", "Object", "Map", "Set", "Promise", "RegExp", "Array", "Function",
|
|
290
|
-
"String", "Number", "Boolean", "Symbol", "WeakMap", "WeakSet", "Buffer",
|
|
291
|
-
]);
|
|
292
|
-
|
|
293
|
-
// Object.prototype members. Real property access reads through the
|
|
294
|
-
// prototype chain, so every object-typed receiver materializes these —
|
|
295
|
-
// destructuring `const { hasOwnProperty } = obj` yields a function (not
|
|
296
|
-
// undefined) and `Object.prototype.hasOwnProperty` types as boolean.
|
|
297
|
-
// Lookup must be own-property guarded: a plain `{}` record would otherwise
|
|
298
|
-
// leak native JS functions (e.g. for "constructor") into the type system.
|
|
299
|
-
//
|
|
300
|
-
// toString/valueOf carry impls so the receiver (thisVal) shapes the result:
|
|
301
|
-
// `Object.prototype.toString.call(x)` yields the brand literal
|
|
302
|
-
// ('[object Map]', '[object Null]', ...) that Map-based type dispatch
|
|
303
|
-
// (hoek internals.typeMap) keys on; valueOf returns its receiver.
|
|
304
|
-
const OBJECT_PROTOTYPE_METHODS: Record<string, TypeValue> = {
|
|
305
|
-
hasOwnProperty: T.fnSig([T.unknown], T.boolean),
|
|
306
|
-
isPrototypeOf: T.fnSig([T.unknown], T.boolean),
|
|
307
|
-
propertyIsEnumerable: T.fnSig([T.unknown], T.boolean),
|
|
308
|
-
toString: T.fnSig([], T.string, T.never, (_args, thisVal) => objectToStringBrand(thisVal)),
|
|
309
|
-
toLocaleString: T.fnSig([], T.string),
|
|
310
|
-
valueOf: T.fnSig([], T.unknown, T.never, (_args, thisVal) => thisVal),
|
|
311
|
-
};
|
|
312
74
|
|
|
313
|
-
// Common prototype members approximated as unknown-result signatures
|
|
314
|
-
// (mirrors the knownInstanceMethods fallback pattern). `X.prototype.m`
|
|
315
|
-
// stays a callable function value instead of degrading to undefined.
|
|
316
|
-
const BUILTIN_PROTOTYPE_METHOD_APPROXIMATIONS: Record<string, Record<string, TypeValue>> = {
|
|
317
|
-
Object: { ...OBJECT_PROTOTYPE_METHODS },
|
|
318
|
-
Array: {
|
|
319
|
-
push: T.fnSig([T.unknown], T.number),
|
|
320
|
-
pop: T.fnSig([], T.unknown),
|
|
321
|
-
shift: T.fnSig([], T.unknown),
|
|
322
|
-
unshift: T.fnSig([T.unknown], T.number),
|
|
323
|
-
slice: T.fnSig([T.number, T.number], T.array(T.unknown)),
|
|
324
|
-
splice: T.fnSig([T.number, T.number], T.array(T.unknown)),
|
|
325
|
-
concat: T.fnSig([T.unknown], T.array(T.unknown)),
|
|
326
|
-
join: T.fnSig([T.string], T.string),
|
|
327
|
-
indexOf: T.fnSig([T.unknown], T.number),
|
|
328
|
-
lastIndexOf: T.fnSig([T.unknown], T.number),
|
|
329
|
-
includes: T.fnSig([T.unknown], T.boolean),
|
|
330
|
-
map: T.fnSig([T.unknown], T.array(T.unknown)),
|
|
331
|
-
filter: T.fnSig([T.unknown], T.array(T.unknown)),
|
|
332
|
-
forEach: T.fnSig([T.unknown], T.undefined),
|
|
333
|
-
find: T.fnSig([T.unknown], T.unknown),
|
|
334
|
-
findIndex: T.fnSig([T.unknown], T.number),
|
|
335
|
-
some: T.fnSig([T.unknown], T.boolean),
|
|
336
|
-
every: T.fnSig([T.unknown], T.boolean),
|
|
337
|
-
reduce: T.fnSig([T.unknown, T.unknown], T.unknown),
|
|
338
|
-
sort: T.fnSig([T.unknown], T.array(T.unknown)),
|
|
339
|
-
reverse: T.fnSig([], T.array(T.unknown)),
|
|
340
|
-
toString: T.fnSig([], T.string),
|
|
341
|
-
},
|
|
342
|
-
Function: {
|
|
343
|
-
call: T.fnSig([T.unknown], T.unknown),
|
|
344
|
-
apply: T.fnSig([T.unknown, T.unknown], T.unknown),
|
|
345
|
-
bind: T.fnSig([T.unknown], T.unknown),
|
|
346
|
-
toString: T.fnSig([], T.string),
|
|
347
|
-
},
|
|
348
|
-
Map: {
|
|
349
|
-
// Impl-routed: `Map.prototype.m.call(instance)` (and direct prototype
|
|
350
|
-
// calls) consult the receiver's exact entry side table, making the
|
|
351
|
-
// hoek deepEqual reflection idioms decide literally.
|
|
352
|
-
get: T.fnSig([T.unknown], T.unknown, T.never, (args, thisVal) => MAP_INSTANCE_METHODS.get(args[0] ?? T.unknown, thisVal ?? T.unknown)),
|
|
353
|
-
set: T.fnSig([T.unknown, T.unknown], T.unknown, T.never, (args, thisVal) => MAP_INSTANCE_METHODS.set(args[0] ?? T.unknown, args[1] ?? T.unknown, thisVal ?? T.unknown)),
|
|
354
|
-
has: T.fnSig([T.unknown], T.boolean, T.never, (args, thisVal) => MAP_INSTANCE_METHODS.has(args[0] ?? T.unknown, thisVal ?? T.unknown)),
|
|
355
|
-
delete: T.fnSig([T.unknown], T.boolean, T.never, (args, thisVal) => MAP_INSTANCE_METHODS.delete(args[0] ?? T.unknown, thisVal ?? T.unknown)),
|
|
356
|
-
clear: T.fnSig([], T.undefined, T.never, (args, thisVal) => MAP_INSTANCE_METHODS.clear(args[0] ?? T.unknown, thisVal ?? T.unknown)),
|
|
357
|
-
forEach: T.fnSig([T.unknown], T.undefined),
|
|
358
|
-
keys: T.fnSig([], T.array(T.unknown), T.never, (_args, thisVal) => MAP_INSTANCE_METHODS.keys(thisVal ?? T.unknown)),
|
|
359
|
-
values: T.fnSig([], T.array(T.unknown), T.never, (_args, thisVal) => MAP_INSTANCE_METHODS.values(thisVal ?? T.unknown)),
|
|
360
|
-
entries: T.fnSig([], T.array(T.tuple([T.unknown, T.unknown])), T.never, (_args, thisVal) => mapEntriesIterable(thisVal ?? T.unknown)),
|
|
361
|
-
toString: T.fnSig([], T.string),
|
|
362
|
-
},
|
|
363
|
-
Set: {
|
|
364
|
-
// Impl-routed like Map above (Set.prototype.values.call(s) ≡ s.values()).
|
|
365
|
-
add: T.fnSig([T.unknown], T.unknown, T.never, (args, thisVal) => SET_INSTANCE_METHODS.add(args[0] ?? T.unknown, thisVal ?? T.unknown)),
|
|
366
|
-
has: T.fnSig([T.unknown], T.boolean, T.never, (args, thisVal) => SET_INSTANCE_METHODS.has(args[0] ?? T.unknown, thisVal ?? T.unknown)),
|
|
367
|
-
delete: T.fnSig([T.unknown], T.boolean, T.never, (args, thisVal) => SET_INSTANCE_METHODS.delete(args[0] ?? T.unknown, thisVal ?? T.unknown)),
|
|
368
|
-
clear: T.fnSig([], T.undefined, T.never, (args, thisVal) => SET_INSTANCE_METHODS.clear(args[0] ?? T.unknown, thisVal ?? T.unknown)),
|
|
369
|
-
forEach: T.fnSig([T.unknown], T.undefined),
|
|
370
|
-
keys: T.fnSig([], T.array(T.unknown), T.never, (_args, thisVal) => SET_INSTANCE_METHODS.keys(thisVal ?? T.unknown)),
|
|
371
|
-
values: T.fnSig([], T.array(T.unknown), T.never, (_args, thisVal) => SET_INSTANCE_METHODS.values(thisVal ?? T.unknown)),
|
|
372
|
-
entries: T.fnSig([], T.array(T.tuple([T.unknown, T.unknown])), T.never, (_args, thisVal) => SET_INSTANCE_METHODS.entries(thisVal ?? T.unknown)),
|
|
373
|
-
toString: T.fnSig([], T.string),
|
|
374
|
-
},
|
|
375
|
-
WeakMap: {
|
|
376
|
-
get: T.fnSig([T.unknown], T.unknown),
|
|
377
|
-
set: T.fnSig([T.unknown, T.unknown], T.unknown),
|
|
378
|
-
has: T.fnSig([T.unknown], T.boolean),
|
|
379
|
-
delete: T.fnSig([T.unknown], T.boolean),
|
|
380
|
-
toString: T.fnSig([], T.string),
|
|
381
|
-
},
|
|
382
|
-
WeakSet: {
|
|
383
|
-
add: T.fnSig([T.unknown], T.unknown),
|
|
384
|
-
has: T.fnSig([T.unknown], T.boolean),
|
|
385
|
-
delete: T.fnSig([T.unknown], T.boolean),
|
|
386
|
-
toString: T.fnSig([], T.string),
|
|
387
|
-
},
|
|
388
|
-
Promise: {
|
|
389
|
-
then: T.fnSig([T.unknown], T.promise(T.unknown)),
|
|
390
|
-
catch: T.fnSig([T.unknown], T.promise(T.unknown)),
|
|
391
|
-
finally: T.fnSig([T.unknown], T.promise(T.unknown)),
|
|
392
|
-
toString: T.fnSig([], T.string),
|
|
393
|
-
},
|
|
394
|
-
Date: {
|
|
395
|
-
getTime: T.fnSig([], T.number),
|
|
396
|
-
valueOf: T.fnSig([], T.number),
|
|
397
|
-
toISOString: T.fnSig([], T.string),
|
|
398
|
-
toJSON: T.fnSig([], T.string),
|
|
399
|
-
toLocaleString: T.fnSig([], T.string),
|
|
400
|
-
toString: T.fnSig([], T.string),
|
|
401
|
-
},
|
|
402
|
-
RegExp: {
|
|
403
|
-
test: T.fnSig([T.string], T.boolean),
|
|
404
|
-
exec: T.fnSig([T.string], T.union(T.object({}), T.null)),
|
|
405
|
-
toString: T.fnSig([], T.string),
|
|
406
|
-
},
|
|
407
|
-
String: {
|
|
408
|
-
charAt: T.fnSig([T.number], T.string),
|
|
409
|
-
charCodeAt: T.fnSig([T.number], T.number),
|
|
410
|
-
indexOf: T.fnSig([T.string], T.number),
|
|
411
|
-
lastIndexOf: T.fnSig([T.string], T.number),
|
|
412
|
-
includes: T.fnSig([T.string], T.boolean),
|
|
413
|
-
startsWith: T.fnSig([T.string], T.boolean),
|
|
414
|
-
endsWith: T.fnSig([T.string], T.boolean),
|
|
415
|
-
slice: T.fnSig([T.number, T.number], T.string),
|
|
416
|
-
substring: T.fnSig([T.number, T.number], T.string),
|
|
417
|
-
toUpperCase: T.fnSig([], T.string),
|
|
418
|
-
toLowerCase: T.fnSig([], T.string),
|
|
419
|
-
trim: T.fnSig([], T.string),
|
|
420
|
-
replace: T.fnSig([T.unknown, T.string], T.string),
|
|
421
|
-
split: T.fnSig([T.string], T.array(T.string)),
|
|
422
|
-
toString: T.fnSig([], T.string),
|
|
423
|
-
valueOf: T.fnSig([], T.string),
|
|
424
|
-
},
|
|
425
|
-
Number: {
|
|
426
|
-
toFixed: T.fnSig([T.number], T.string),
|
|
427
|
-
toPrecision: T.fnSig([T.number], T.string),
|
|
428
|
-
valueOf: T.fnSig([], T.number),
|
|
429
|
-
toString: T.fnSig([T.number], T.string),
|
|
430
|
-
},
|
|
431
|
-
Boolean: {
|
|
432
|
-
valueOf: T.fnSig([], T.boolean),
|
|
433
|
-
toString: T.fnSig([], T.string),
|
|
434
|
-
},
|
|
435
|
-
Symbol: {
|
|
436
|
-
toString: T.fnSig([], T.string),
|
|
437
|
-
valueOf: T.fnSig([], T.symbol),
|
|
438
|
-
},
|
|
439
|
-
Buffer: {
|
|
440
|
-
equals: T.fnSig([T.unknown], T.boolean),
|
|
441
|
-
compare: T.fnSig([T.unknown], T.number),
|
|
442
|
-
toString: T.fnSig([T.unknown], T.string),
|
|
443
|
-
toJSON: T.fnSig([], T.unknown),
|
|
444
|
-
},
|
|
445
|
-
Error: {
|
|
446
|
-
toString: T.fnSig([], T.string),
|
|
447
|
-
},
|
|
448
|
-
};
|
|
449
75
|
|
|
450
|
-
// Memoized namespace values for built-in classes. Reference-stable so
|
|
451
|
-
// `'x'.constructor === String` compares identical objects (typeValueEquals
|
|
452
|
-
// falls back to reference equality for object kinds).
|
|
453
|
-
const _builtinClassValues = new Map<string, TypeValue>();
|
|
454
|
-
function builtinClassValue(name: string): TypeValue {
|
|
455
|
-
let v = _builtinClassValues.get(name);
|
|
456
|
-
if (v === undefined) {
|
|
457
|
-
v = T.object({});
|
|
458
|
-
(v as any)._builtinName = name;
|
|
459
|
-
_builtinClassValues.set(name, v);
|
|
460
|
-
}
|
|
461
|
-
return v;
|
|
462
|
-
}
|
|
463
76
|
|
|
464
77
|
// Wrapper-prototype fallback for primitive receivers: real JS auto-boxes
|
|
465
78
|
// member access on primitives ('x'.constructor === String), so a named
|
|
@@ -503,109 +116,10 @@ function wrapperPrototypeMember(obj: TypeValue, propName: string): TypeValue | u
|
|
|
503
116
|
return undefined;
|
|
504
117
|
}
|
|
505
118
|
|
|
506
|
-
// Cached `X.prototype` singletons. hoek-style modules assign
|
|
507
|
-
// `exports.array = Array.prototype` and later compare
|
|
508
|
-
// `baseProto === Types.buffer`: strict-equality on instances only stays
|
|
509
|
-
// precise when every evaluation of `X.prototype` yields the same TypeValue.
|
|
510
|
-
const BUILTIN_PROTOTYPE_SINGLETONS = new Map<string, TypeValue>();
|
|
511
119
|
|
|
512
|
-
function builtinPrototype(className: string): TypeValue {
|
|
513
|
-
let proto = BUILTIN_PROTOTYPE_SINGLETONS.get(className);
|
|
514
|
-
if (!proto) {
|
|
515
|
-
const methods = hasOwnProp(BUILTIN_PROTOTYPE_METHOD_APPROXIMATIONS, className)
|
|
516
|
-
? BUILTIN_PROTOTYPE_METHOD_APPROXIMATIONS[className]
|
|
517
|
-
: BUILTIN_ERROR_CLASSES.has(className)
|
|
518
|
-
? BUILTIN_PROTOTYPE_METHOD_APPROXIMATIONS.Error
|
|
519
|
-
: {};
|
|
520
|
-
proto = T.instanceOf(className, { ...methods });
|
|
521
|
-
(proto as any)._builtinProto = className;
|
|
522
|
-
BUILTIN_PROTOTYPE_SINGLETONS.set(className, proto);
|
|
523
|
-
}
|
|
524
|
-
return proto;
|
|
525
|
-
}
|
|
526
120
|
|
|
527
|
-
// `Object.prototype.toString` brand string ('[object Map]', '[object Null]',
|
|
528
|
-
// ...). Returns undefined when the receiver has no representable brand, so
|
|
529
|
-
// the fnSig falls back to its plain `string` return type.
|
|
530
|
-
function objectToStringBrand(v: TypeValue | undefined): TypeValue | undefined {
|
|
531
|
-
if (!v) return undefined;
|
|
532
|
-
if (v.kind === "union") {
|
|
533
|
-
const parts = v.members.map((m) => objectToStringBrand(m));
|
|
534
|
-
if (parts.some((p) => p === undefined)) return undefined;
|
|
535
|
-
return simplifyUnion(parts as TypeValue[]);
|
|
536
|
-
}
|
|
537
|
-
let base: TypeValue = v;
|
|
538
|
-
while (base.kind === "refined") base = base.base;
|
|
539
|
-
switch (base.kind) {
|
|
540
|
-
case "object": return T.literal("[object Object]");
|
|
541
|
-
case "array":
|
|
542
|
-
case "tuple": return T.literal("[object Array]");
|
|
543
|
-
case "function": return T.literal("[object Function]");
|
|
544
|
-
case "promise": return T.literal("[object Promise]");
|
|
545
|
-
case "instance": return T.literal(`[object ${base.className}]`);
|
|
546
|
-
case "literal": {
|
|
547
|
-
if (base.value === null) return T.literal("[object Null]");
|
|
548
|
-
if (base.value === undefined) return T.literal("[object Undefined]");
|
|
549
|
-
const t = typeof base.value;
|
|
550
|
-
return T.literal(`[object ${t === "number" ? "Number" : t === "string" ? "String" : "Boolean"}]`);
|
|
551
|
-
}
|
|
552
|
-
case "primitive": {
|
|
553
|
-
const brands: Record<string, string> = {
|
|
554
|
-
number: "Number", string: "String", boolean: "Boolean", bigint: "BigInt", symbol: "Symbol",
|
|
555
|
-
};
|
|
556
|
-
return T.literal(`[object ${brands[base.type] ?? "Object"}]`);
|
|
557
|
-
}
|
|
558
|
-
default: return undefined;
|
|
559
|
-
}
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
// Object.getPrototypeOf: map a receiver onto its class's cached prototype
|
|
563
|
-
// singleton (plain objects → Object.prototype, arrays → Array.prototype,
|
|
564
|
-
// instances → their class's prototype). Unrepresentable receivers degrade
|
|
565
|
-
// to unknown.
|
|
566
|
-
function protoOfValue(v: TypeValue | undefined): TypeValue {
|
|
567
|
-
if (!v) return T.unknown;
|
|
568
|
-
if (v.kind === "union") return simplifyUnion(v.members.map((m) => protoOfValue(m)));
|
|
569
|
-
let base: TypeValue = v;
|
|
570
|
-
while (base.kind === "refined") base = base.base;
|
|
571
|
-
switch (base.kind) {
|
|
572
|
-
case "object": return builtinPrototype("Object");
|
|
573
|
-
case "array":
|
|
574
|
-
case "tuple": return builtinPrototype("Array");
|
|
575
|
-
case "function": return builtinPrototype("Function");
|
|
576
|
-
case "promise": return builtinPrototype("Promise");
|
|
577
|
-
case "instance": return builtinPrototype(base.className);
|
|
578
|
-
default: return T.unknown;
|
|
579
|
-
}
|
|
580
|
-
}
|
|
581
121
|
|
|
582
|
-
function descriptorOf(prop: TypeValue): TypeValue {
|
|
583
|
-
return T.object({
|
|
584
|
-
value: prop,
|
|
585
|
-
writable: T.boolean,
|
|
586
|
-
enumerable: T.boolean,
|
|
587
|
-
configurable: T.boolean,
|
|
588
|
-
});
|
|
589
|
-
}
|
|
590
122
|
|
|
591
|
-
// `x instanceof C` / `Buffer.isBuffer(x)` / `Array.isArray(x)` literal
|
|
592
|
-
// answer for structurally-known receivers; undefined keeps the symbolic
|
|
593
|
-
// fallback (boolean).
|
|
594
|
-
function builtinInstanceTest(v: TypeValue | undefined, className: string): boolean | undefined {
|
|
595
|
-
if (!v) return undefined;
|
|
596
|
-
let base: TypeValue = v;
|
|
597
|
-
while (base.kind === "refined") base = base.base;
|
|
598
|
-
switch (base.kind) {
|
|
599
|
-
case "array":
|
|
600
|
-
case "tuple": return className === "Array" || className === "Object";
|
|
601
|
-
case "object": return className === "Object";
|
|
602
|
-
case "function": return className === "Function" || className === "Object";
|
|
603
|
-
case "promise": return className === "Promise" || className === "Object";
|
|
604
|
-
case "literal":
|
|
605
|
-
case "primitive": return false;
|
|
606
|
-
default: return undefined;
|
|
607
|
-
}
|
|
608
|
-
}
|
|
609
123
|
|
|
610
124
|
function arrayIsArrayLiteral(v: TypeValue): boolean | undefined {
|
|
611
125
|
let base: TypeValue = v;
|
|
@@ -653,6 +167,18 @@ function definiteBoolean(tv: TypeValue): boolean | null {
|
|
|
653
167
|
}
|
|
654
168
|
}
|
|
655
169
|
|
|
170
|
+
/**
|
|
171
|
+
* 分支合并:双对象先走 Abs join(sum-of-products / 槽位合并),
|
|
172
|
+
* 失败或非对象再 simplifyUnion。保证 join 代数接到 evaluator 主路径。
|
|
173
|
+
*/
|
|
174
|
+
function joinOrUnion(a: TypeValue, b: TypeValue): TypeValue {
|
|
175
|
+
if (a.kind === "object" && b.kind === "object") {
|
|
176
|
+
const joined = tryAbsJoinObjects(a, b);
|
|
177
|
+
if (joined) return joined;
|
|
178
|
+
}
|
|
179
|
+
return simplifyUnion([a, b]);
|
|
180
|
+
}
|
|
181
|
+
|
|
656
182
|
// Receiver-binding for thisVal-dependent Object.prototype methods
|
|
657
183
|
// (toString/valueOf): `const v = obj.valueOf; v()` loses the member-access
|
|
658
184
|
// receiver, so accesses off a concrete receiver return a copy whose impl
|
|
@@ -1094,6 +620,9 @@ export type CallRecord = {
|
|
|
1094
620
|
argTypes: TypeValue[];
|
|
1095
621
|
resultType: TypeValue;
|
|
1096
622
|
throws: TypeValue;
|
|
623
|
+
/** Line-relative. Per-fn cache replay shifts this by lineDelta — if you
|
|
624
|
+
* add another position field (callee loc, arg loc), extend
|
|
625
|
+
* shiftCallRecordLines in analyzer.ts in the same change. */
|
|
1097
626
|
callLoc?: { line: number; column: number };
|
|
1098
627
|
targetModule?: string;
|
|
1099
628
|
targetExport?: string;
|
|
@@ -1309,6 +838,13 @@ export function setProvenanceTracking(enabled: boolean): void {
|
|
|
1309
838
|
_provenanceEnabled = enabled;
|
|
1310
839
|
}
|
|
1311
840
|
|
|
841
|
+
/** 克隆 TypeValue 后拷贝 provenance(tagParamArg 会 clone,丢 origin 会导致 M5 回归) */
|
|
842
|
+
function copyOrigin(from: TypeValue, to: TypeValue): void {
|
|
843
|
+
if (!_provenanceEnabled || !from || !to) return;
|
|
844
|
+
const o = _originMap.get(from);
|
|
845
|
+
if (o) _originMap.set(to, o);
|
|
846
|
+
}
|
|
847
|
+
|
|
1312
848
|
function extractOrigin(
|
|
1313
849
|
tv: TypeValue | undefined,
|
|
1314
850
|
): { line: number; column: number } | undefined {
|
|
@@ -1566,31 +1102,6 @@ function evaluateComputedMemberAccess(objVal: TypeValue, propVal: TypeValue, nod
|
|
|
1566
1102
|
return result;
|
|
1567
1103
|
}
|
|
1568
1104
|
|
|
1569
|
-
const MAX_UNION_PRODUCT = 50;
|
|
1570
|
-
|
|
1571
|
-
function distributeBinaryOverUnion(
|
|
1572
|
-
left: TypeValue,
|
|
1573
|
-
right: TypeValue,
|
|
1574
|
-
fn: (l: TypeValue, r: TypeValue) => TypeValue,
|
|
1575
|
-
): TypeValue {
|
|
1576
|
-
if (left.kind === "union" && right.kind === "union") {
|
|
1577
|
-
// Cap combinatorial blowup
|
|
1578
|
-
if (left.members.length * right.members.length > MAX_UNION_PRODUCT) {
|
|
1579
|
-
return T.unknown;
|
|
1580
|
-
}
|
|
1581
|
-
return simplifyUnion(
|
|
1582
|
-
left.members.flatMap((l) => right.members.map((r) => fn(l, r))),
|
|
1583
|
-
);
|
|
1584
|
-
}
|
|
1585
|
-
if (left.kind === "union") {
|
|
1586
|
-
return simplifyUnion(left.members.map((l) => fn(l, right)));
|
|
1587
|
-
}
|
|
1588
|
-
if (right.kind === "union") {
|
|
1589
|
-
return simplifyUnion(right.members.map((r) => fn(left, r)));
|
|
1590
|
-
}
|
|
1591
|
-
return fn(left, right);
|
|
1592
|
-
}
|
|
1593
|
-
|
|
1594
1105
|
let _unreachableRanges: SourceRange[] = [];
|
|
1595
1106
|
|
|
1596
1107
|
function collectUnreachable(stmts: readonly Node[], fromIndex: number): void {
|
|
@@ -1815,66 +1326,39 @@ function evaluateNode(node: Node, env: Environment): EvalResult {
|
|
|
1815
1326
|
return evaluateInstanceof(leftVal, rightVal, node.right, env);
|
|
1816
1327
|
}
|
|
1817
1328
|
if (node.operator === "in") {
|
|
1818
|
-
// Literal `key in obj` / `Symbol.iterator in x` decisions; unions
|
|
1819
|
-
// on either side distribute (mixed literals collapse per
|
|
1820
|
-
// simplifyUnion), unknown receivers stay unknown.
|
|
1821
1329
|
return distributeBinaryOverUnion(leftVal, rightVal, (l, r) => evaluateInMember(l, r));
|
|
1822
1330
|
}
|
|
1823
1331
|
if (node.operator === "===" || node.operator === "!==") {
|
|
1824
|
-
// Prototype-singleton identity (`baseProto === Types.buffer`): the
|
|
1825
|
-
// generic Op only literal-compares, so same-class instance pairs
|
|
1826
|
-
// would collapse to boolean. Decide it when both sides are instances
|
|
1827
|
-
// and at least one is a cached builtin prototype.
|
|
1828
1332
|
const identity = builtinProtoIdentityEq(leftVal, rightVal);
|
|
1829
1333
|
if (identity !== null) {
|
|
1830
1334
|
return T.literal(node.operator === "===" ? identity : !identity);
|
|
1831
1335
|
}
|
|
1832
|
-
// Class-namespace identity (`x.constructor === Array`): memoized
|
|
1833
|
-
// builtinClassValue objects compare by class name.
|
|
1834
1336
|
const classIdentity = builtinClassIdentityEq(leftVal, rightVal);
|
|
1835
1337
|
if (classIdentity !== null) {
|
|
1836
1338
|
return T.literal(node.operator === "===" ? classIdentity : !classIdentity);
|
|
1837
1339
|
}
|
|
1838
1340
|
}
|
|
1839
|
-
|
|
1840
|
-
//
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
// 时退化为 T.boolean。
|
|
1845
|
-
if (node.operator === "==" || node.operator === "!=") {
|
|
1846
|
-
const eq = node.operator === "==";
|
|
1847
|
-
return distributeBinaryOverUnion(leftVal, rightVal, (l, r) => {
|
|
1848
|
-
const lv = looseLiteralValue(l);
|
|
1849
|
-
const rv = looseLiteralValue(r);
|
|
1850
|
-
if (lv.hit && rv.hit) return T.literal(eq ? lv.v == rv.v : lv.v != rv.v);
|
|
1851
|
-
return T.boolean;
|
|
1852
|
-
});
|
|
1853
|
-
}
|
|
1854
|
-
return distributeBinaryOverUnion(leftVal, rightVal, (l, r) =>
|
|
1855
|
-
dispatchBinaryOp(node.operator, l, r),
|
|
1856
|
-
);
|
|
1341
|
+
|
|
1342
|
+
// 统一二元:代数优先 → 宽松相等 → 外延 Ops
|
|
1343
|
+
const bin = evalBinaryValue(node.operator, leftVal, rightVal);
|
|
1344
|
+
if (bin !== undefined) return bin;
|
|
1345
|
+
return T.unknown;
|
|
1857
1346
|
}
|
|
1858
1347
|
|
|
1859
1348
|
case "UnaryExpression": {
|
|
1860
1349
|
const argVal = evaluate(node.argument, env);
|
|
1861
1350
|
if (isReturn(argVal) || isBranch(argVal) || isThrow(argVal)) return argVal;
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1351
|
+
// 一元语言表面经代数(typeof / ! / -)
|
|
1352
|
+
const unary = tryAbsUnary(node.operator, argVal);
|
|
1353
|
+
if (unary !== undefined) return unary;
|
|
1865
1354
|
if (node.operator === "!") {
|
|
1355
|
+
// 代数未接住:对象恒真等 host 知识
|
|
1866
1356
|
return distributeOverUnion(argVal, (v) => {
|
|
1867
|
-
// Object-ish receivers are statically truthy: `!ref` must resolve
|
|
1868
|
-
// to literal false or enclosing `if (!ref || …)` guards explore
|
|
1869
|
-
// both branches and the ref-reassigning branch poisons the loop.
|
|
1870
1357
|
const decided = definiteBoolean(v);
|
|
1871
1358
|
if (decided !== null) return T.literal(!decided);
|
|
1872
1359
|
return Ops.not(v);
|
|
1873
1360
|
});
|
|
1874
1361
|
}
|
|
1875
|
-
if (node.operator === "-") {
|
|
1876
|
-
return distributeOverUnion(argVal, (v) => Ops.neg(v));
|
|
1877
|
-
}
|
|
1878
1362
|
return T.unknown;
|
|
1879
1363
|
}
|
|
1880
1364
|
|
|
@@ -1964,7 +1448,7 @@ function evaluateNode(node: Node, env: Environment): EvalResult {
|
|
|
1964
1448
|
const aResult = evaluate(node.alternate, falseEnv);
|
|
1965
1449
|
const cVal = isReturn(cResult) ? cResult.value : isBranch(cResult) ? cResult.returnedValue : isThrow(cResult) ? T.never : cResult;
|
|
1966
1450
|
const aVal = isReturn(aResult) ? aResult.value : isBranch(aResult) ? aResult.returnedValue : isThrow(aResult) ? T.never : aResult;
|
|
1967
|
-
return
|
|
1451
|
+
return joinOrUnion(cVal, aVal);
|
|
1968
1452
|
}
|
|
1969
1453
|
|
|
1970
1454
|
case "IfStatement": {
|
|
@@ -1973,6 +1457,9 @@ function evaluateNode(node: Node, env: Environment): EvalResult {
|
|
|
1973
1457
|
const testVal = evaluate(test, env);
|
|
1974
1458
|
if (isReturn(testVal) || isBranch(testVal) || isThrow(testVal)) return testVal;
|
|
1975
1459
|
|
|
1460
|
+
// Kernel Φ:从测试表达式提取约束,真/假分支分别合取
|
|
1461
|
+
const phis = phiFromTest(test);
|
|
1462
|
+
|
|
1976
1463
|
if (testVal.kind === "literal") {
|
|
1977
1464
|
if (testVal.value) {
|
|
1978
1465
|
if (node.alternate?.loc) {
|
|
@@ -1981,7 +1468,12 @@ function evaluateNode(node: Node, env: Environment): EvalResult {
|
|
|
1981
1468
|
end: { line: node.alternate.loc.end.line, column: node.alternate.loc.end.column },
|
|
1982
1469
|
});
|
|
1983
1470
|
}
|
|
1984
|
-
|
|
1471
|
+
pushPhi(combinePhi(currentPhi(), phis.whenTrue));
|
|
1472
|
+
try {
|
|
1473
|
+
return evaluate(node.consequent, trueEnv);
|
|
1474
|
+
} finally {
|
|
1475
|
+
popPhi();
|
|
1476
|
+
}
|
|
1985
1477
|
}
|
|
1986
1478
|
if (node.consequent.loc) {
|
|
1987
1479
|
_unreachableRanges.push({
|
|
@@ -1989,26 +1481,52 @@ function evaluateNode(node: Node, env: Environment): EvalResult {
|
|
|
1989
1481
|
end: { line: node.consequent.loc.end.line, column: node.consequent.loc.end.column },
|
|
1990
1482
|
});
|
|
1991
1483
|
}
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1484
|
+
if (!node.alternate) return T.undefined;
|
|
1485
|
+
pushPhi(combinePhi(currentPhi(), phis.whenFalse));
|
|
1486
|
+
try {
|
|
1487
|
+
return evaluate(node.alternate, falseEnv);
|
|
1488
|
+
} finally {
|
|
1489
|
+
popPhi();
|
|
1490
|
+
}
|
|
1995
1491
|
}
|
|
1996
1492
|
// Object-ish / otherwise-decidable tests pick their branch statically
|
|
1997
1493
|
// (e.g. `if (proto && proto.isImmutable)` once && resolves).
|
|
1998
1494
|
const decided = definiteBoolean(testVal);
|
|
1999
1495
|
if (decided !== null) {
|
|
2000
1496
|
if (decided) {
|
|
2001
|
-
|
|
1497
|
+
pushPhi(combinePhi(currentPhi(), phis.whenTrue));
|
|
1498
|
+
try {
|
|
1499
|
+
return evaluate(node.consequent, trueEnv);
|
|
1500
|
+
} finally {
|
|
1501
|
+
popPhi();
|
|
1502
|
+
}
|
|
1503
|
+
}
|
|
1504
|
+
if (!node.alternate) return T.undefined;
|
|
1505
|
+
pushPhi(combinePhi(currentPhi(), phis.whenFalse));
|
|
1506
|
+
try {
|
|
1507
|
+
return evaluate(node.alternate, falseEnv);
|
|
1508
|
+
} finally {
|
|
1509
|
+
popPhi();
|
|
2002
1510
|
}
|
|
2003
|
-
return node.alternate
|
|
2004
|
-
? evaluate(node.alternate, falseEnv)
|
|
2005
|
-
: T.undefined;
|
|
2006
1511
|
}
|
|
2007
1512
|
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
1513
|
+
// 两边都探索:分别用各自 Φ
|
|
1514
|
+
pushPhi(combinePhi(currentPhi(), phis.whenTrue));
|
|
1515
|
+
let consequentResult: EvalResult;
|
|
1516
|
+
try {
|
|
1517
|
+
consequentResult = evaluate(node.consequent, trueEnv);
|
|
1518
|
+
} finally {
|
|
1519
|
+
popPhi();
|
|
1520
|
+
}
|
|
1521
|
+
pushPhi(combinePhi(currentPhi(), phis.whenFalse));
|
|
1522
|
+
let alternateResult: EvalResult | null = null;
|
|
1523
|
+
try {
|
|
1524
|
+
alternateResult = node.alternate
|
|
1525
|
+
? evaluate(node.alternate, falseEnv)
|
|
1526
|
+
: null;
|
|
1527
|
+
} finally {
|
|
1528
|
+
popPhi();
|
|
1529
|
+
}
|
|
2012
1530
|
|
|
2013
1531
|
const cReturns = isReturn(consequentResult);
|
|
2014
1532
|
const cBranches = isBranch(consequentResult);
|
|
@@ -2033,21 +1551,21 @@ function evaluateNode(node: Node, env: Environment): EvalResult {
|
|
|
2033
1551
|
}
|
|
2034
1552
|
|
|
2035
1553
|
if (aThrows) {
|
|
2036
|
-
const cVal = cReturns ? consequentResult.value
|
|
2037
|
-
: cBranches ? consequentResult.returnedValue
|
|
1554
|
+
const cVal = cReturns ? (consequentResult as ReturnSignal).value
|
|
1555
|
+
: cBranches ? (consequentResult as BranchSignal).returnedValue
|
|
2038
1556
|
: consequentResult as TypeValue;
|
|
2039
1557
|
return makeBranch(cVal, trueEnv);
|
|
2040
1558
|
}
|
|
2041
1559
|
|
|
2042
|
-
const cVal = cReturns ? consequentResult.value
|
|
2043
|
-
: cBranches ? consequentResult.returnedValue
|
|
1560
|
+
const cVal = cReturns ? (consequentResult as ReturnSignal).value
|
|
1561
|
+
: cBranches ? (consequentResult as BranchSignal).returnedValue
|
|
2044
1562
|
: consequentResult as TypeValue;
|
|
2045
1563
|
const aVal = aReturns ? (alternateResult as ReturnSignal).value
|
|
2046
1564
|
: aBranches ? (alternateResult as BranchSignal).returnedValue
|
|
2047
1565
|
: alternateResult as TypeValue | null;
|
|
2048
1566
|
|
|
2049
1567
|
if (cReturns && aReturns) {
|
|
2050
|
-
return makeReturn(
|
|
1568
|
+
return makeReturn(joinOrUnion(cVal, aVal!));
|
|
2051
1569
|
}
|
|
2052
1570
|
|
|
2053
1571
|
if (cReturns && !node.alternate) {
|
|
@@ -2056,7 +1574,7 @@ function evaluateNode(node: Node, env: Environment): EvalResult {
|
|
|
2056
1574
|
|
|
2057
1575
|
if (cReturns && node.alternate) {
|
|
2058
1576
|
if (aReturns) {
|
|
2059
|
-
return makeReturn(
|
|
1577
|
+
return makeReturn(joinOrUnion(cVal, aVal!));
|
|
2060
1578
|
}
|
|
2061
1579
|
return makeBranch(cVal, falseEnv);
|
|
2062
1580
|
}
|
|
@@ -2115,7 +1633,7 @@ function evaluateNode(node: Node, env: Environment): EvalResult {
|
|
|
2115
1633
|
if (leftVal && leftVal.kind !== "unknown") {
|
|
2116
1634
|
// Extract the binary operator (e.g., "+=" -> "+")
|
|
2117
1635
|
const binaryOp = node.operator.slice(0, -1);
|
|
2118
|
-
val =
|
|
1636
|
+
val = evalBinaryValue(binaryOp, leftVal, rightVal) ?? T.unknown;
|
|
2119
1637
|
}
|
|
2120
1638
|
}
|
|
2121
1639
|
|
|
@@ -2761,6 +2279,15 @@ function evaluateNode(node: Node, env: Environment): EvalResult {
|
|
|
2761
2279
|
} else if (prop.type === "SpreadElement") {
|
|
2762
2280
|
const spreadVal = evaluate(prop.argument, env);
|
|
2763
2281
|
if (isReturn(spreadVal) || isBranch(spreadVal) || isThrow(spreadVal)) return spreadVal;
|
|
2282
|
+
// M4:代数 spread(保留字面量/term);失败回退 Object.assign
|
|
2283
|
+
const absSpread = tryAbsObjectSpread(T.object(props), spreadVal);
|
|
2284
|
+
if (absSpread !== undefined && absSpread.kind === "object") {
|
|
2285
|
+
// 合并到 props 继续处理后续属性
|
|
2286
|
+
for (const [k, v] of Object.entries(absSpread.properties)) {
|
|
2287
|
+
props[k] = v;
|
|
2288
|
+
}
|
|
2289
|
+
continue;
|
|
2290
|
+
}
|
|
2764
2291
|
if (spreadVal.kind === "object") {
|
|
2765
2292
|
Object.assign(props, spreadVal.properties);
|
|
2766
2293
|
} else if (spreadVal.kind === "union") {
|
|
@@ -3996,23 +3523,36 @@ function evaluateArrayMethodValues(
|
|
|
3996
3523
|
|
|
3997
3524
|
const fn = callbackFn as TypeValue & { kind: "function" };
|
|
3998
3525
|
|
|
3526
|
+
// HOF:给回调元素挂 term 身份,使 Φ 中的约束可传播到 map/reduce 体
|
|
3527
|
+
const tagEl = (el: TypeValue, name: string): TypeValue => {
|
|
3528
|
+
const tagged = tagParamArg(el, name);
|
|
3529
|
+
copyOrigin(el, tagged);
|
|
3530
|
+
return tagged;
|
|
3531
|
+
};
|
|
3532
|
+
|
|
3999
3533
|
if (method === "map") {
|
|
4000
3534
|
if (arr.kind === "tuple") {
|
|
4001
3535
|
const mapped = arr.elements.map((el, i) =>
|
|
4002
|
-
callFunction(fn, [el, T.literal(i), arr]),
|
|
3536
|
+
callFunction(fn, [tagEl(el, fn.params[0] ?? "item"), T.literal(i), arr]),
|
|
4003
3537
|
);
|
|
4004
3538
|
return T.tuple(mapped);
|
|
4005
3539
|
}
|
|
4006
|
-
return T.array(
|
|
3540
|
+
return T.array(
|
|
3541
|
+
callFunction(fn, [tagEl(arr.element, fn.params[0] ?? "item"), T.number, arr]),
|
|
3542
|
+
);
|
|
4007
3543
|
}
|
|
4008
3544
|
|
|
4009
3545
|
if (method === "filter") {
|
|
4010
3546
|
if (arr.kind === "tuple") {
|
|
4011
3547
|
const kept: TypeValue[] = [];
|
|
4012
3548
|
for (let i = 0; i < arr.elements.length; i++) {
|
|
4013
|
-
const result = callFunction(fn, [
|
|
3549
|
+
const result = callFunction(fn, [
|
|
3550
|
+
tagEl(arr.elements[i]!, fn.params[0] ?? "item"),
|
|
3551
|
+
T.literal(i),
|
|
3552
|
+
arr,
|
|
3553
|
+
]);
|
|
4014
3554
|
if (result.kind === "literal" && !result.value) continue;
|
|
4015
|
-
kept.push(arr.elements[i]);
|
|
3555
|
+
kept.push(arr.elements[i]!);
|
|
4016
3556
|
}
|
|
4017
3557
|
if (kept.length === 0) return T.tuple([]);
|
|
4018
3558
|
return T.array(simplifyUnion(kept));
|
|
@@ -4026,17 +3566,30 @@ function evaluateArrayMethodValues(
|
|
|
4026
3566
|
let acc = init ?? arr.elements[0] ?? T.unknown;
|
|
4027
3567
|
const startIdx = init ? 0 : 1;
|
|
4028
3568
|
for (let i = startIdx; i < arr.elements.length; i++) {
|
|
4029
|
-
acc = callFunction(fn, [
|
|
3569
|
+
acc = callFunction(fn, [
|
|
3570
|
+
acc,
|
|
3571
|
+
tagEl(arr.elements[i]!, fn.params[1] ?? "item"),
|
|
3572
|
+
T.literal(i),
|
|
3573
|
+
arr,
|
|
3574
|
+
]);
|
|
4030
3575
|
}
|
|
4031
3576
|
return acc;
|
|
4032
3577
|
}
|
|
4033
|
-
//
|
|
4034
|
-
|
|
4035
|
-
|
|
4036
|
-
|
|
4037
|
-
|
|
4038
|
-
|
|
4039
|
-
|
|
3578
|
+
// 抽象数组:不动点迭代(代数纪律)。unknown 不 join 进 acc。
|
|
3579
|
+
const acc0 = init ?? arr.element;
|
|
3580
|
+
let acc = acc0;
|
|
3581
|
+
for (let i = 0; i < 6; i++) {
|
|
3582
|
+
const next = callFunction(fn, [
|
|
3583
|
+
acc,
|
|
3584
|
+
tagEl(arr.element, fn.params[1] ?? "item"),
|
|
3585
|
+
T.number,
|
|
3586
|
+
arr,
|
|
3587
|
+
]);
|
|
3588
|
+
if (typeValueEquals(acc, next)) return next;
|
|
3589
|
+
if (next.kind === "unknown") return acc;
|
|
3590
|
+
acc = simplifyUnion([acc, next]);
|
|
3591
|
+
}
|
|
3592
|
+
return acc;
|
|
4040
3593
|
}
|
|
4041
3594
|
|
|
4042
3595
|
if (method === "find") {
|
|
@@ -4543,31 +4096,6 @@ function commonJsExportsValue(moduleEnv: Environment): TypeValue {
|
|
|
4543
4096
|
return T.unknown;
|
|
4544
4097
|
}
|
|
4545
4098
|
|
|
4546
|
-
/** JSON 值 → TypeValue(字面量级精确:package.json 版本号等成为 string 字面量) */
|
|
4547
|
-
function jsonToTypeValue(v: unknown): TypeValue {
|
|
4548
|
-
if (v === null) return T.literal(null);
|
|
4549
|
-
if (typeof v === "string") return T.literal(v);
|
|
4550
|
-
if (typeof v === "number") return T.literal(v);
|
|
4551
|
-
if (typeof v === "boolean") return T.literal(v);
|
|
4552
|
-
if (Array.isArray(v)) return T.tuple(v.map(jsonToTypeValue));
|
|
4553
|
-
if (typeof v === "object") {
|
|
4554
|
-
const props: Record<string, TypeValue> = {};
|
|
4555
|
-
for (const [k, val] of Object.entries(v)) {
|
|
4556
|
-
// Define-as-own: plain assignment would let a "__proto__" key trip
|
|
4557
|
-
// the setter on the props record (dropping it and corrupting the
|
|
4558
|
-
// record's prototype) — JSON.parse creates an own property, and
|
|
4559
|
-
// hoek's prototype-poisoning fixtures exercise exactly that key.
|
|
4560
|
-
Object.defineProperty(props, k, {
|
|
4561
|
-
value: jsonToTypeValue(val),
|
|
4562
|
-
enumerable: true,
|
|
4563
|
-
writable: true,
|
|
4564
|
-
configurable: true,
|
|
4565
|
-
});
|
|
4566
|
-
}
|
|
4567
|
-
return T.object(props);
|
|
4568
|
-
}
|
|
4569
|
-
return T.unknown;
|
|
4570
|
-
}
|
|
4571
4099
|
|
|
4572
4100
|
function evaluateRequireCall(node: Node & { type: "CallExpression" }, _env: Environment): EvalResult { const arg = (node.arguments as Node[])[0];
|
|
4573
4101
|
if (!arg || arg.type !== "StringLiteral") {
|
|
@@ -4980,7 +4508,12 @@ function bindFunctionParams(
|
|
|
4980
4508
|
callEnv.bind(paramName.slice(3), restValue); // Remove "..." prefix
|
|
4981
4509
|
}
|
|
4982
4510
|
} else {
|
|
4983
|
-
|
|
4511
|
+
let argVal = args[i] ?? T.undefined;
|
|
4512
|
+
{
|
|
4513
|
+
const tagged = tagParamArg(argVal, paramName);
|
|
4514
|
+
copyOrigin(argVal, tagged);
|
|
4515
|
+
argVal = tagged;
|
|
4516
|
+
}
|
|
4984
4517
|
if (paramPatterns && paramPatterns[i]) {
|
|
4985
4518
|
bindPattern(paramPatterns[i], argVal, callEnv);
|
|
4986
4519
|
} else {
|
|
@@ -5582,7 +5115,14 @@ export function evaluateFunctionFull(
|
|
|
5582
5115
|
callEnv.bind(fnName, selfRef);
|
|
5583
5116
|
}
|
|
5584
5117
|
for (let i = 0; i < actualNode.params.length; i++) {
|
|
5585
|
-
|
|
5118
|
+
const pname = describeParam(actualNode.params[i]!);
|
|
5119
|
+
let argVal = args[i] ?? T.undefined;
|
|
5120
|
+
{
|
|
5121
|
+
const tagged = tagParamArg(argVal, pname);
|
|
5122
|
+
copyOrigin(argVal, tagged);
|
|
5123
|
+
argVal = tagged;
|
|
5124
|
+
}
|
|
5125
|
+
bindPattern(actualNode.params[i]!, argVal, callEnv);
|
|
5586
5126
|
}
|
|
5587
5127
|
const result = evaluate(actualNode.body, callEnv);
|
|
5588
5128
|
if (isThrow(result)) return { value: T.never, throws: result.thrown, throwLoc: result.loc };
|
|
@@ -5603,6 +5143,7 @@ export function evaluateFunctionFull(
|
|
|
5603
5143
|
|
|
5604
5144
|
export function evaluateProgram(node: Node, env: Environment): TypeValue {
|
|
5605
5145
|
bindCommonJsGlobals(env);
|
|
5146
|
+
resetPhi();
|
|
5606
5147
|
const result = evaluate(node, env);
|
|
5607
5148
|
if (isReturn(result)) return result.value;
|
|
5608
5149
|
if (isBranch(result)) return result.returnedValue;
|