@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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nudojs/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "CLI and evaluator API for the Nudo type inference engine",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -20,6 +20,10 @@
|
|
|
20
20
|
"bin": {
|
|
21
21
|
"nudo": "src/index.ts"
|
|
22
22
|
},
|
|
23
|
+
"files": [
|
|
24
|
+
"src",
|
|
25
|
+
"!src/__tests__"
|
|
26
|
+
],
|
|
23
27
|
"repository": {
|
|
24
28
|
"type": "git",
|
|
25
29
|
"url": "git+https://github.com/nudojs/nudo.git",
|
|
@@ -33,13 +37,14 @@
|
|
|
33
37
|
"registry": "https://registry.npmjs.org"
|
|
34
38
|
},
|
|
35
39
|
"dependencies": {
|
|
36
|
-
"
|
|
37
|
-
"@nudojs/
|
|
38
|
-
"@nudojs/
|
|
39
|
-
"@nudojs/
|
|
40
|
-
"@nudojs/
|
|
40
|
+
"commander": "^13.0.0",
|
|
41
|
+
"@nudojs/core": "0.3.0",
|
|
42
|
+
"@nudojs/parser": "0.3.0",
|
|
43
|
+
"@nudojs/service": "0.3.0",
|
|
44
|
+
"@nudojs/env": "0.2.1",
|
|
45
|
+
"@nudojs/harvester": "0.2.0"
|
|
41
46
|
},
|
|
42
47
|
"scripts": {
|
|
43
|
-
"build": "tsup
|
|
48
|
+
"build": "tsup"
|
|
44
49
|
}
|
|
45
50
|
}
|
package/src/abs-route.ts
ADDED
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TypeValue IR ⇄ Abs 代数的路由。
|
|
3
|
+
*
|
|
4
|
+
* 唯一计算路径:算术/比较/相等/一元语言表面先走 Abs;
|
|
5
|
+
* 仅 refined 宿主扩展(/ % 自定义 ops、方法表)与代数无法建模的操作数才回落。
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { TypeValue } from "@nudojs/core";
|
|
9
|
+
import {
|
|
10
|
+
typeValueToAbs,
|
|
11
|
+
absToTypeValue,
|
|
12
|
+
simplifyUnion,
|
|
13
|
+
add as kAdd,
|
|
14
|
+
sub as kSub,
|
|
15
|
+
mul as kMul,
|
|
16
|
+
div as kDiv,
|
|
17
|
+
mod as kMod,
|
|
18
|
+
cmp as kCmp,
|
|
19
|
+
typeofAbs as kTypeof,
|
|
20
|
+
negAbs as kNeg,
|
|
21
|
+
notAbs as kNot,
|
|
22
|
+
strictEqAbs as kStrictEq,
|
|
23
|
+
type Abs,
|
|
24
|
+
type Pred,
|
|
25
|
+
pTrue,
|
|
26
|
+
and as predAnd,
|
|
27
|
+
ge as predGe,
|
|
28
|
+
le as predLe,
|
|
29
|
+
lit as termLit,
|
|
30
|
+
getRangeMeta,
|
|
31
|
+
spread as kSpread,
|
|
32
|
+
joinAbs as kJoinAbs,
|
|
33
|
+
abs as makeAbs,
|
|
34
|
+
boolLit,
|
|
35
|
+
currentPhi,
|
|
36
|
+
pushPhi,
|
|
37
|
+
popPhi,
|
|
38
|
+
resetPhi,
|
|
39
|
+
withPhiConstraint,
|
|
40
|
+
describePhi,
|
|
41
|
+
} from "@nudojs/core";
|
|
42
|
+
import { getTerm, getPred } from "./term-registry.ts";
|
|
43
|
+
|
|
44
|
+
export { currentPhi, pushPhi, popPhi, resetPhi, withPhiConstraint, describePhi };
|
|
45
|
+
|
|
46
|
+
const ARITH_OPS = new Set(["+", "-", "*", "/", "%"]);
|
|
47
|
+
const CMP_OPS = new Set(["<", "<=", ">", ">="]);
|
|
48
|
+
const EQ_OPS = new Set(["===", "!=="]);
|
|
49
|
+
const UNARY_OPS = new Set(["typeof", "!", "-"]);
|
|
50
|
+
|
|
51
|
+
function isNumericOperand(tv: TypeValue): boolean {
|
|
52
|
+
if (!tv) return false;
|
|
53
|
+
if (tv.kind === "literal") return typeof tv.value === "number";
|
|
54
|
+
if (tv.kind === "primitive") return tv.type === "number";
|
|
55
|
+
if (tv.kind === "refined") return isNumericOperand(tv.base);
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function isStringOperand(tv: TypeValue): boolean {
|
|
60
|
+
if (!tv) return false;
|
|
61
|
+
if (tv.kind === "literal") return typeof tv.value === "string";
|
|
62
|
+
if (tv.kind === "primitive") return tv.type === "string";
|
|
63
|
+
if (tv.kind === "refined") return isStringOperand(tv.base);
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** 比较:数值或字符串字面量 */
|
|
68
|
+
function isComparableOperand(tv: TypeValue): boolean {
|
|
69
|
+
if (tv.kind === "literal") {
|
|
70
|
+
return typeof tv.value === "number" || typeof tv.value === "string";
|
|
71
|
+
}
|
|
72
|
+
if (tv.kind === "primitive") return tv.type === "number" || tv.type === "string";
|
|
73
|
+
if (tv.kind === "refined") return isComparableOperand(tv.base);
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* 宿主 refinement 自定义了该运算 → 让位。
|
|
79
|
+
* 仅限代数未覆盖的运算(/ %):template 的 + 等仍走代数(concatString)。
|
|
80
|
+
*/
|
|
81
|
+
function hasCustomRefinementOp(tv: TypeValue, op: string): boolean {
|
|
82
|
+
if (op !== "/" && op !== "%") return false;
|
|
83
|
+
let cur: TypeValue | undefined = tv;
|
|
84
|
+
while (cur && cur.kind === "refined") {
|
|
85
|
+
if (cur.refinement.ops?.[op]) return true;
|
|
86
|
+
cur = cur.base;
|
|
87
|
+
}
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** `+` 可接受:双数值;或任一侧 string/template(拼接) */
|
|
92
|
+
function canAdd(left: TypeValue, right: TypeValue): boolean {
|
|
93
|
+
if (isNumericOperand(left) && isNumericOperand(right)) return true;
|
|
94
|
+
if (isStringOperand(left) || isStringOperand(right)) return true;
|
|
95
|
+
if (left.kind === "refined" && isStringOperand(left.base)) return true;
|
|
96
|
+
if (right.kind === "refined" && isStringOperand(right.base)) return true;
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* 带 term 旁路的 TypeValue → Abs。
|
|
102
|
+
* range refined 编码为 term 上的 Pred(唯一约束通道),不再靠 refinement.ops。
|
|
103
|
+
*/
|
|
104
|
+
function toAbsWithTerms(tv: TypeValue): Abs {
|
|
105
|
+
const base = typeValueToAbs(tv);
|
|
106
|
+
const term = getTerm(tv);
|
|
107
|
+
const pred = getPred(tv);
|
|
108
|
+
let abs: Abs =
|
|
109
|
+
!term && !pred && !getRangeMeta(tv)
|
|
110
|
+
? base
|
|
111
|
+
: makeAbs(base.shape, term ?? base.term, pred ?? base.pred, base.conf);
|
|
112
|
+
|
|
113
|
+
const range = getRangeMeta(tv);
|
|
114
|
+
if (range && abs.term) {
|
|
115
|
+
const facts: Pred[] = [];
|
|
116
|
+
if (range.min != null) facts.push(predGe(abs.term, termLit(range.min)));
|
|
117
|
+
if (range.max != null) facts.push(predLe(abs.term, termLit(range.max)));
|
|
118
|
+
if (facts.length > 0) {
|
|
119
|
+
const existing = abs.pred && abs.pred.op !== "true" ? abs.pred : undefined;
|
|
120
|
+
abs = makeAbs(
|
|
121
|
+
abs.shape,
|
|
122
|
+
abs.term,
|
|
123
|
+
existing ? predAnd(existing, ...facts) : predAnd(...facts),
|
|
124
|
+
abs.conf,
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return abs;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* 代数计算二元算术/比较/相等。
|
|
133
|
+
* - union:逐成员代数后 simplifyUnion(约束不丢)
|
|
134
|
+
* - undefined = 不可处理 → 调用方走 refined 扩展 / 形状兜底
|
|
135
|
+
*/
|
|
136
|
+
export function tryAbsBinary(
|
|
137
|
+
op: string,
|
|
138
|
+
left: TypeValue,
|
|
139
|
+
right: TypeValue,
|
|
140
|
+
): TypeValue | undefined {
|
|
141
|
+
if (!ARITH_OPS.has(op) && !CMP_OPS.has(op) && !EQ_OPS.has(op)) return undefined;
|
|
142
|
+
|
|
143
|
+
if (left.kind === "union" || right.kind === "union") {
|
|
144
|
+
const ls = left.kind === "union" ? left.members : [left];
|
|
145
|
+
const rs = right.kind === "union" ? right.members : [right];
|
|
146
|
+
const results: TypeValue[] = [];
|
|
147
|
+
for (const l of ls) {
|
|
148
|
+
for (const r of rs) {
|
|
149
|
+
const one = tryAbsBinary(op, l, r);
|
|
150
|
+
if (one === undefined) return undefined;
|
|
151
|
+
results.push(one);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return simplifyUnion(results);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (hasCustomRefinementOp(left, op) || hasCustomRefinementOp(right, op)) {
|
|
158
|
+
return undefined;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (op === "+") {
|
|
162
|
+
if (!canAdd(left, right)) return undefined;
|
|
163
|
+
} else if (op === "-" || op === "*" || op === "/" || op === "%") {
|
|
164
|
+
if (!isNumericOperand(left) || !isNumericOperand(right)) return undefined;
|
|
165
|
+
} else if (op === "===" || op === "!==") {
|
|
166
|
+
// 任意操作数:先 strictEqAbs(字面量 / nullish / 同 var)
|
|
167
|
+
} else {
|
|
168
|
+
// 比较:数值或字符串
|
|
169
|
+
if (!isComparableOperand(left) || !isComparableOperand(right)) return undefined;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
try {
|
|
173
|
+
const la = toAbsWithTerms(left);
|
|
174
|
+
const ra = toAbsWithTerms(right);
|
|
175
|
+
const phi = currentPhi();
|
|
176
|
+
|
|
177
|
+
if (op === "===" || op === "!==") {
|
|
178
|
+
const eq = kStrictEq(la, ra);
|
|
179
|
+
if (eq !== undefined) {
|
|
180
|
+
return absToTypeValue(op === "===" ? boolLit(eq) : boolLit(!eq));
|
|
181
|
+
}
|
|
182
|
+
// 无法判定:数值同型走 cmp 挂 pred;否则交还调用方
|
|
183
|
+
if (isNumericOperand(left) && isNumericOperand(right)) {
|
|
184
|
+
return absToTypeValue(kCmp(op === "===" ? "eq" : "ne", la, ra, phi));
|
|
185
|
+
}
|
|
186
|
+
return undefined;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
let result: Abs;
|
|
190
|
+
if (op === "+") result = kAdd(la, ra, phi);
|
|
191
|
+
else if (op === "-") result = kSub(la, ra, phi);
|
|
192
|
+
else if (op === "*") result = kMul(la, ra, phi);
|
|
193
|
+
else if (op === "/") result = kDiv(la, ra, phi);
|
|
194
|
+
else if (op === "%") result = kMod(la, ra, phi);
|
|
195
|
+
else if (op === "<") result = kCmp("lt", la, ra, phi);
|
|
196
|
+
else if (op === "<=") result = kCmp("le", la, ra, phi);
|
|
197
|
+
else if (op === ">") result = kCmp("gt", la, ra, phi);
|
|
198
|
+
else if (op === ">=") result = kCmp("ge", la, ra, phi);
|
|
199
|
+
else return undefined;
|
|
200
|
+
|
|
201
|
+
return absToTypeValue(result);
|
|
202
|
+
} catch {
|
|
203
|
+
return undefined;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* 一元语言表面经代数:typeof / ! / -。
|
|
209
|
+
* 返回 undefined 表示调用方自行处理(union 分发等)。
|
|
210
|
+
*/
|
|
211
|
+
export function tryAbsUnary(
|
|
212
|
+
op: string,
|
|
213
|
+
operand: TypeValue,
|
|
214
|
+
): TypeValue | undefined {
|
|
215
|
+
if (!UNARY_OPS.has(op)) return undefined;
|
|
216
|
+
if (operand.kind === "union") {
|
|
217
|
+
const outs: TypeValue[] = [];
|
|
218
|
+
for (const m of operand.members) {
|
|
219
|
+
const one = tryAbsUnary(op, m);
|
|
220
|
+
if (one === undefined) return undefined;
|
|
221
|
+
outs.push(one);
|
|
222
|
+
}
|
|
223
|
+
return simplifyUnion(outs);
|
|
224
|
+
}
|
|
225
|
+
try {
|
|
226
|
+
const a = toAbsWithTerms(operand);
|
|
227
|
+
if (op === "typeof") return absToTypeValue(kTypeof(a));
|
|
228
|
+
if (op === "!") return absToTypeValue(kNot(a));
|
|
229
|
+
if (op === "-") {
|
|
230
|
+
if (!isNumericOperand(operand) && operand.kind !== "unknown") return undefined;
|
|
231
|
+
return absToTypeValue(kNeg(a, currentPhi()));
|
|
232
|
+
}
|
|
233
|
+
return undefined;
|
|
234
|
+
} catch {
|
|
235
|
+
return undefined;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* 对象 spread 经代数(右侧覆盖,保留字面量)。
|
|
241
|
+
* 返回 undefined 表示走 Object.assign 路径。
|
|
242
|
+
*/
|
|
243
|
+
export function tryAbsObjectSpread(
|
|
244
|
+
base: TypeValue,
|
|
245
|
+
over: TypeValue,
|
|
246
|
+
): TypeValue | undefined {
|
|
247
|
+
if (base.kind !== "object" && base.kind !== "union") return undefined;
|
|
248
|
+
if (over.kind !== "object" && over.kind !== "union") return undefined;
|
|
249
|
+
try {
|
|
250
|
+
const ba = toAbsWithTerms(base);
|
|
251
|
+
const oa = toAbsWithTerms(over);
|
|
252
|
+
const r = kSpread(ba, oa);
|
|
253
|
+
return absToTypeValue(r);
|
|
254
|
+
} catch {
|
|
255
|
+
return undefined;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* 对象 join(异 key 保持 sum,不折 optional)。
|
|
261
|
+
*/
|
|
262
|
+
export function tryAbsJoinObjects(
|
|
263
|
+
a: TypeValue,
|
|
264
|
+
b: TypeValue,
|
|
265
|
+
): TypeValue | undefined {
|
|
266
|
+
if (a.kind !== "object" || b.kind !== "object") return undefined;
|
|
267
|
+
try {
|
|
268
|
+
const r = kJoinAbs(toAbsWithTerms(a), toAbsWithTerms(b));
|
|
269
|
+
return absToTypeValue(r);
|
|
270
|
+
} catch {
|
|
271
|
+
return undefined;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 内置类原型机制:错误类集合、可构造内置类、Object/Array/Map/Set/…/Buffer
|
|
3
|
+
* 的原型成员近似表,以及 `X.prototype` 单例与类命名空间记忆化。
|
|
4
|
+
* 求值器成员分派与补全侧成员派生共用本表的唯一真值。
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { T, simplifyUnion, type TypeValue } from "@nudojs/core";
|
|
8
|
+
import { MAP_INSTANCE_METHODS, mapEntriesIterable } from "./builtin-map.ts";
|
|
9
|
+
import { SET_INSTANCE_METHODS } from "./builtin-set.ts";
|
|
10
|
+
|
|
11
|
+
// hasOwnProp 守卫:普通 Record 表查找会从原型链泄漏原生函数(如 "constructor")。
|
|
12
|
+
// 与 evaluator 内同名 helper 同语义;独立副本避免 evaluator → 本模块循环依赖。
|
|
13
|
+
function hasOwnProp(props: Record<string, unknown>, name: string): boolean {
|
|
14
|
+
return Object.prototype.hasOwnProperty.call(props, name);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const BUILTIN_ERROR_CLASSES = new Set([
|
|
18
|
+
"Error", "TypeError", "SyntaxError", "RangeError", "ReferenceError", "URIError", "EvalError",
|
|
19
|
+
]);
|
|
20
|
+
// Constructible built-in classes. A bare reference to one of these names
|
|
21
|
+
// resolves to a namespace object (like the BUILTIN_STATIC_METHODS entries),
|
|
22
|
+
// and `X.prototype` evaluates to an instance of X instead of degrading to
|
|
23
|
+
// undefined/unknown.
|
|
24
|
+
const BUILTIN_PROTOTYPE_CLASSES = new Set([
|
|
25
|
+
...BUILTIN_ERROR_CLASSES,
|
|
26
|
+
"Date", "Object", "Map", "Set", "Promise", "RegExp", "Array", "Function",
|
|
27
|
+
"String", "Number", "Boolean", "Symbol", "WeakMap", "WeakSet", "Buffer",
|
|
28
|
+
]);
|
|
29
|
+
// Object.prototype members. Real property access reads through the
|
|
30
|
+
// prototype chain, so every object-typed receiver materializes these —
|
|
31
|
+
// destructuring `const { hasOwnProperty } = obj` yields a function (not
|
|
32
|
+
// undefined) and `Object.prototype.hasOwnProperty` types as boolean.
|
|
33
|
+
// Lookup must be own-property guarded: a plain `{}` record would otherwise
|
|
34
|
+
// leak native JS functions (e.g. for "constructor") into the type system.
|
|
35
|
+
//
|
|
36
|
+
// toString/valueOf carry impls so the receiver (thisVal) shapes the result:
|
|
37
|
+
// `Object.prototype.toString.call(x)` yields the brand literal
|
|
38
|
+
// ('[object Map]', '[object Null]', ...) that Map-based type dispatch
|
|
39
|
+
// (hoek internals.typeMap) keys on; valueOf returns its receiver.
|
|
40
|
+
const OBJECT_PROTOTYPE_METHODS: Record<string, TypeValue> = {
|
|
41
|
+
hasOwnProperty: T.fnSig([T.unknown], T.boolean),
|
|
42
|
+
isPrototypeOf: T.fnSig([T.unknown], T.boolean),
|
|
43
|
+
propertyIsEnumerable: T.fnSig([T.unknown], T.boolean),
|
|
44
|
+
toString: T.fnSig([], T.string, T.never, (_args, thisVal) => objectToStringBrand(thisVal)),
|
|
45
|
+
toLocaleString: T.fnSig([], T.string),
|
|
46
|
+
valueOf: T.fnSig([], T.unknown, T.never, (_args, thisVal) => thisVal),
|
|
47
|
+
};
|
|
48
|
+
// Common prototype members approximated as unknown-result signatures
|
|
49
|
+
// (mirrors the knownInstanceMethods fallback pattern). `X.prototype.m`
|
|
50
|
+
// stays a callable function value instead of degrading to undefined.
|
|
51
|
+
const BUILTIN_PROTOTYPE_METHOD_APPROXIMATIONS: Record<string, Record<string, TypeValue>> = {
|
|
52
|
+
Object: { ...OBJECT_PROTOTYPE_METHODS },
|
|
53
|
+
Array: {
|
|
54
|
+
push: T.fnSig([T.unknown], T.number),
|
|
55
|
+
pop: T.fnSig([], T.unknown),
|
|
56
|
+
shift: T.fnSig([], T.unknown),
|
|
57
|
+
unshift: T.fnSig([T.unknown], T.number),
|
|
58
|
+
slice: T.fnSig([T.number, T.number], T.array(T.unknown)),
|
|
59
|
+
splice: T.fnSig([T.number, T.number], T.array(T.unknown)),
|
|
60
|
+
concat: T.fnSig([T.unknown], T.array(T.unknown)),
|
|
61
|
+
join: T.fnSig([T.string], T.string),
|
|
62
|
+
indexOf: T.fnSig([T.unknown], T.number),
|
|
63
|
+
lastIndexOf: T.fnSig([T.unknown], T.number),
|
|
64
|
+
includes: T.fnSig([T.unknown], T.boolean),
|
|
65
|
+
map: T.fnSig([T.unknown], T.array(T.unknown)),
|
|
66
|
+
flatMap: T.fnSig([T.unknown], T.array(T.unknown)),
|
|
67
|
+
filter: T.fnSig([T.unknown], T.array(T.unknown)),
|
|
68
|
+
forEach: T.fnSig([T.unknown], T.undefined),
|
|
69
|
+
find: T.fnSig([T.unknown], T.unknown),
|
|
70
|
+
findIndex: T.fnSig([T.unknown], T.number),
|
|
71
|
+
some: T.fnSig([T.unknown], T.boolean),
|
|
72
|
+
every: T.fnSig([T.unknown], T.boolean),
|
|
73
|
+
reduce: T.fnSig([T.unknown, T.unknown], T.unknown),
|
|
74
|
+
sort: T.fnSig([T.unknown], T.array(T.unknown)),
|
|
75
|
+
reverse: T.fnSig([], T.array(T.unknown)),
|
|
76
|
+
toString: T.fnSig([], T.string),
|
|
77
|
+
},
|
|
78
|
+
Function: {
|
|
79
|
+
call: T.fnSig([T.unknown], T.unknown),
|
|
80
|
+
apply: T.fnSig([T.unknown, T.unknown], T.unknown),
|
|
81
|
+
bind: T.fnSig([T.unknown], T.unknown),
|
|
82
|
+
toString: T.fnSig([], T.string),
|
|
83
|
+
},
|
|
84
|
+
Map: {
|
|
85
|
+
// Impl-routed: `Map.prototype.m.call(instance)` (and direct prototype
|
|
86
|
+
// calls) consult the receiver's exact entry side table, making the
|
|
87
|
+
// hoek deepEqual reflection idioms decide literally.
|
|
88
|
+
get: T.fnSig([T.unknown], T.unknown, T.never, (args, thisVal) => MAP_INSTANCE_METHODS.get(args[0] ?? T.unknown, thisVal ?? T.unknown)),
|
|
89
|
+
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)),
|
|
90
|
+
has: T.fnSig([T.unknown], T.boolean, T.never, (args, thisVal) => MAP_INSTANCE_METHODS.has(args[0] ?? T.unknown, thisVal ?? T.unknown)),
|
|
91
|
+
delete: T.fnSig([T.unknown], T.boolean, T.never, (args, thisVal) => MAP_INSTANCE_METHODS.delete(args[0] ?? T.unknown, thisVal ?? T.unknown)),
|
|
92
|
+
clear: T.fnSig([], T.undefined, T.never, (args, thisVal) => MAP_INSTANCE_METHODS.clear(args[0] ?? T.unknown, thisVal ?? T.unknown)),
|
|
93
|
+
forEach: T.fnSig([T.unknown], T.undefined),
|
|
94
|
+
keys: T.fnSig([], T.array(T.unknown), T.never, (_args, thisVal) => MAP_INSTANCE_METHODS.keys(thisVal ?? T.unknown)),
|
|
95
|
+
values: T.fnSig([], T.array(T.unknown), T.never, (_args, thisVal) => MAP_INSTANCE_METHODS.values(thisVal ?? T.unknown)),
|
|
96
|
+
entries: T.fnSig([], T.array(T.tuple([T.unknown, T.unknown])), T.never, (_args, thisVal) => mapEntriesIterable(thisVal ?? T.unknown)),
|
|
97
|
+
toString: T.fnSig([], T.string),
|
|
98
|
+
},
|
|
99
|
+
Set: {
|
|
100
|
+
// Impl-routed like Map above (Set.prototype.values.call(s) ≡ s.values()).
|
|
101
|
+
add: T.fnSig([T.unknown], T.unknown, T.never, (args, thisVal) => SET_INSTANCE_METHODS.add(args[0] ?? T.unknown, thisVal ?? T.unknown)),
|
|
102
|
+
has: T.fnSig([T.unknown], T.boolean, T.never, (args, thisVal) => SET_INSTANCE_METHODS.has(args[0] ?? T.unknown, thisVal ?? T.unknown)),
|
|
103
|
+
delete: T.fnSig([T.unknown], T.boolean, T.never, (args, thisVal) => SET_INSTANCE_METHODS.delete(args[0] ?? T.unknown, thisVal ?? T.unknown)),
|
|
104
|
+
clear: T.fnSig([], T.undefined, T.never, (args, thisVal) => SET_INSTANCE_METHODS.clear(args[0] ?? T.unknown, thisVal ?? T.unknown)),
|
|
105
|
+
forEach: T.fnSig([T.unknown], T.undefined),
|
|
106
|
+
keys: T.fnSig([], T.array(T.unknown), T.never, (_args, thisVal) => SET_INSTANCE_METHODS.keys(thisVal ?? T.unknown)),
|
|
107
|
+
values: T.fnSig([], T.array(T.unknown), T.never, (_args, thisVal) => SET_INSTANCE_METHODS.values(thisVal ?? T.unknown)),
|
|
108
|
+
entries: T.fnSig([], T.array(T.tuple([T.unknown, T.unknown])), T.never, (_args, thisVal) => SET_INSTANCE_METHODS.entries(thisVal ?? T.unknown)),
|
|
109
|
+
toString: T.fnSig([], T.string),
|
|
110
|
+
},
|
|
111
|
+
WeakMap: {
|
|
112
|
+
get: T.fnSig([T.unknown], T.unknown),
|
|
113
|
+
set: T.fnSig([T.unknown, T.unknown], T.unknown),
|
|
114
|
+
has: T.fnSig([T.unknown], T.boolean),
|
|
115
|
+
delete: T.fnSig([T.unknown], T.boolean),
|
|
116
|
+
toString: T.fnSig([], T.string),
|
|
117
|
+
},
|
|
118
|
+
WeakSet: {
|
|
119
|
+
add: T.fnSig([T.unknown], T.unknown),
|
|
120
|
+
has: T.fnSig([T.unknown], T.boolean),
|
|
121
|
+
delete: T.fnSig([T.unknown], T.boolean),
|
|
122
|
+
toString: T.fnSig([], T.string),
|
|
123
|
+
},
|
|
124
|
+
Promise: {
|
|
125
|
+
then: T.fnSig([T.unknown], T.promise(T.unknown)),
|
|
126
|
+
catch: T.fnSig([T.unknown], T.promise(T.unknown)),
|
|
127
|
+
finally: T.fnSig([T.unknown], T.promise(T.unknown)),
|
|
128
|
+
toString: T.fnSig([], T.string),
|
|
129
|
+
},
|
|
130
|
+
Date: {
|
|
131
|
+
getTime: T.fnSig([], T.number),
|
|
132
|
+
valueOf: T.fnSig([], T.number),
|
|
133
|
+
toISOString: T.fnSig([], T.string),
|
|
134
|
+
toJSON: T.fnSig([], T.string),
|
|
135
|
+
toLocaleString: T.fnSig([], T.string),
|
|
136
|
+
toString: T.fnSig([], T.string),
|
|
137
|
+
},
|
|
138
|
+
RegExp: {
|
|
139
|
+
test: T.fnSig([T.string], T.boolean),
|
|
140
|
+
exec: T.fnSig([T.string], T.union(T.object({}), T.null)),
|
|
141
|
+
toString: T.fnSig([], T.string),
|
|
142
|
+
},
|
|
143
|
+
String: {
|
|
144
|
+
charAt: T.fnSig([T.number], T.string),
|
|
145
|
+
charCodeAt: T.fnSig([T.number], T.number),
|
|
146
|
+
indexOf: T.fnSig([T.string], T.number),
|
|
147
|
+
lastIndexOf: T.fnSig([T.string], T.number),
|
|
148
|
+
includes: T.fnSig([T.string], T.boolean),
|
|
149
|
+
startsWith: T.fnSig([T.string], T.boolean),
|
|
150
|
+
endsWith: T.fnSig([T.string], T.boolean),
|
|
151
|
+
slice: T.fnSig([T.number, T.number], T.string),
|
|
152
|
+
substring: T.fnSig([T.number, T.number], T.string),
|
|
153
|
+
toUpperCase: T.fnSig([], T.string),
|
|
154
|
+
toLowerCase: T.fnSig([], T.string),
|
|
155
|
+
trim: T.fnSig([], T.string),
|
|
156
|
+
replace: T.fnSig([T.unknown, T.string], T.string),
|
|
157
|
+
split: T.fnSig([T.string], T.array(T.string)),
|
|
158
|
+
toString: T.fnSig([], T.string),
|
|
159
|
+
valueOf: T.fnSig([], T.string),
|
|
160
|
+
},
|
|
161
|
+
Number: {
|
|
162
|
+
toFixed: T.fnSig([T.number], T.string),
|
|
163
|
+
toPrecision: T.fnSig([T.number], T.string),
|
|
164
|
+
valueOf: T.fnSig([], T.number),
|
|
165
|
+
toString: T.fnSig([T.number], T.string),
|
|
166
|
+
},
|
|
167
|
+
Boolean: {
|
|
168
|
+
valueOf: T.fnSig([], T.boolean),
|
|
169
|
+
toString: T.fnSig([], T.string),
|
|
170
|
+
},
|
|
171
|
+
Symbol: {
|
|
172
|
+
toString: T.fnSig([], T.string),
|
|
173
|
+
valueOf: T.fnSig([], T.symbol),
|
|
174
|
+
},
|
|
175
|
+
Buffer: {
|
|
176
|
+
equals: T.fnSig([T.unknown], T.boolean),
|
|
177
|
+
compare: T.fnSig([T.unknown], T.number),
|
|
178
|
+
toString: T.fnSig([T.unknown], T.string),
|
|
179
|
+
toJSON: T.fnSig([], T.unknown),
|
|
180
|
+
},
|
|
181
|
+
Error: {
|
|
182
|
+
toString: T.fnSig([], T.string),
|
|
183
|
+
},
|
|
184
|
+
};
|
|
185
|
+
// Memoized namespace values for built-in classes. Reference-stable so
|
|
186
|
+
// `'x'.constructor === String` compares identical objects (typeValueEquals
|
|
187
|
+
// falls back to reference equality for object kinds).
|
|
188
|
+
const _builtinClassValues = new Map<string, TypeValue>();
|
|
189
|
+
function builtinClassValue(name: string): TypeValue {
|
|
190
|
+
let v = _builtinClassValues.get(name);
|
|
191
|
+
if (v === undefined) {
|
|
192
|
+
v = T.object({});
|
|
193
|
+
(v as any)._builtinName = name;
|
|
194
|
+
_builtinClassValues.set(name, v);
|
|
195
|
+
}
|
|
196
|
+
return v;
|
|
197
|
+
}
|
|
198
|
+
// Cached `X.prototype` singletons. hoek-style modules assign
|
|
199
|
+
// `exports.array = Array.prototype` and later compare
|
|
200
|
+
// `baseProto === Types.buffer`: strict-equality on instances only stays
|
|
201
|
+
// precise when every evaluation of `X.prototype` yields the same TypeValue.
|
|
202
|
+
const BUILTIN_PROTOTYPE_SINGLETONS = new Map<string, TypeValue>();
|
|
203
|
+
|
|
204
|
+
function builtinPrototype(className: string): TypeValue {
|
|
205
|
+
let proto = BUILTIN_PROTOTYPE_SINGLETONS.get(className);
|
|
206
|
+
if (!proto) {
|
|
207
|
+
const methods = hasOwnProp(BUILTIN_PROTOTYPE_METHOD_APPROXIMATIONS, className)
|
|
208
|
+
? BUILTIN_PROTOTYPE_METHOD_APPROXIMATIONS[className]
|
|
209
|
+
: BUILTIN_ERROR_CLASSES.has(className)
|
|
210
|
+
? BUILTIN_PROTOTYPE_METHOD_APPROXIMATIONS.Error
|
|
211
|
+
: {};
|
|
212
|
+
proto = T.instanceOf(className, { ...methods });
|
|
213
|
+
(proto as any)._builtinProto = className;
|
|
214
|
+
BUILTIN_PROTOTYPE_SINGLETONS.set(className, proto);
|
|
215
|
+
}
|
|
216
|
+
return proto;
|
|
217
|
+
}
|
|
218
|
+
// `Object.prototype.toString` brand string ('[object Map]', '[object Null]',
|
|
219
|
+
// ...). Returns undefined when the receiver has no representable brand, so
|
|
220
|
+
// the fnSig falls back to its plain `string` return type.
|
|
221
|
+
function objectToStringBrand(v: TypeValue | undefined): TypeValue | undefined {
|
|
222
|
+
if (!v) return undefined;
|
|
223
|
+
if (v.kind === "union") {
|
|
224
|
+
const parts = v.members.map((m) => objectToStringBrand(m));
|
|
225
|
+
if (parts.some((p) => p === undefined)) return undefined;
|
|
226
|
+
return simplifyUnion(parts as TypeValue[]);
|
|
227
|
+
}
|
|
228
|
+
let base: TypeValue = v;
|
|
229
|
+
while (base.kind === "refined") base = base.base;
|
|
230
|
+
switch (base.kind) {
|
|
231
|
+
case "object": return T.literal("[object Object]");
|
|
232
|
+
case "array":
|
|
233
|
+
case "tuple": return T.literal("[object Array]");
|
|
234
|
+
case "function": return T.literal("[object Function]");
|
|
235
|
+
case "promise": return T.literal("[object Promise]");
|
|
236
|
+
case "instance": return T.literal(`[object ${base.className}]`);
|
|
237
|
+
case "literal": {
|
|
238
|
+
if (base.value === null) return T.literal("[object Null]");
|
|
239
|
+
if (base.value === undefined) return T.literal("[object Undefined]");
|
|
240
|
+
const t = typeof base.value;
|
|
241
|
+
return T.literal(`[object ${t === "number" ? "Number" : t === "string" ? "String" : "Boolean"}]`);
|
|
242
|
+
}
|
|
243
|
+
case "primitive": {
|
|
244
|
+
const brands: Record<string, string> = {
|
|
245
|
+
number: "Number", string: "String", boolean: "Boolean", bigint: "BigInt", symbol: "Symbol",
|
|
246
|
+
};
|
|
247
|
+
return T.literal(`[object ${brands[base.type] ?? "Object"}]`);
|
|
248
|
+
}
|
|
249
|
+
default: return undefined;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
// Object.getPrototypeOf: map a receiver onto its class's cached prototype
|
|
253
|
+
// singleton (plain objects → Object.prototype, arrays → Array.prototype,
|
|
254
|
+
// instances → their class's prototype). Unrepresentable receivers degrade
|
|
255
|
+
// to unknown.
|
|
256
|
+
function protoOfValue(v: TypeValue | undefined): TypeValue {
|
|
257
|
+
if (!v) return T.unknown;
|
|
258
|
+
if (v.kind === "union") return simplifyUnion(v.members.map((m) => protoOfValue(m)));
|
|
259
|
+
let base: TypeValue = v;
|
|
260
|
+
while (base.kind === "refined") base = base.base;
|
|
261
|
+
switch (base.kind) {
|
|
262
|
+
case "object": return builtinPrototype("Object");
|
|
263
|
+
case "array":
|
|
264
|
+
case "tuple": return builtinPrototype("Array");
|
|
265
|
+
case "function": return builtinPrototype("Function");
|
|
266
|
+
case "promise": return builtinPrototype("Promise");
|
|
267
|
+
case "instance": return builtinPrototype(base.className);
|
|
268
|
+
default: return T.unknown;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
export {
|
|
272
|
+
BUILTIN_ERROR_CLASSES,
|
|
273
|
+
BUILTIN_PROTOTYPE_CLASSES,
|
|
274
|
+
OBJECT_PROTOTYPE_METHODS,
|
|
275
|
+
BUILTIN_PROTOTYPE_METHOD_APPROXIMATIONS,
|
|
276
|
+
builtinClassValue,
|
|
277
|
+
builtinPrototype,
|
|
278
|
+
protoOfValue,
|
|
279
|
+
};
|