@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
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 内置静态命名空间与直接全局值(process/Math/JSON/Object statics/Number
|
|
3
|
+
* 常量…)及其实现辅助(JSON 字面量解码、属性描述符、结构 instanceof 判定、
|
|
4
|
+
* fromCharCode 实参收割)。
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { T, simplifyUnion, type TypeValue } from "@nudojs/core";
|
|
8
|
+
import { protoOfValue } from "./builtin-prototype.ts";
|
|
9
|
+
import { PROMISE_STATIC_METHODS } from "./builtin-promise.ts";
|
|
10
|
+
import { SYMBOL_STATIC_METHODS, SYMBOL_STATIC_PROPS } from "./builtin-symbol.ts";
|
|
11
|
+
import { REFLECT_METHODS } from "./builtin-reflect.ts";
|
|
12
|
+
import { INTL_DATETIMEFORMAT_METHODS, INTL_NUMBERFORMAT_METHODS } from "./builtin-intl.ts";
|
|
13
|
+
|
|
14
|
+
// hasOwnProp 守卫:普通 Record 表查找会从原型链泄漏原生函数(与
|
|
15
|
+
// builtin-prototype.ts / evaluator 内同名 helper 同语义)。
|
|
16
|
+
function hasOwnProp(props: Record<string, unknown>, name: string): boolean {
|
|
17
|
+
return Object.prototype.hasOwnProperty.call(props, name);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Built-in JavaScript API type mappings
|
|
21
|
+
// Namespace objects (e.g. Math.floor) and direct global values (e.g. parseInt)
|
|
22
|
+
const BUILTIN_STATIC_METHODS: Record<string, Record<string, TypeValue> | TypeValue> = {
|
|
23
|
+
// Node CJS 库普遍依赖的 process 全局——缺它时 process.nextTick(cb) 走
|
|
24
|
+
// 未知全局 → undefined → no-method 假错(hoek wait 的 usage-site 执行)
|
|
25
|
+
process: {
|
|
26
|
+
nextTick: T.fn(["callback", "...args"], { type: "BlockStatement", body: [], directives: [] } as any, undefined as any),
|
|
27
|
+
env: T.object({}),
|
|
28
|
+
platform: T.string,
|
|
29
|
+
versions: T.object({}),
|
|
30
|
+
argv: T.array(T.string),
|
|
31
|
+
cwd: T.fn([], { type: "BlockStatement", body: [], directives: [] } as any, undefined as any),
|
|
32
|
+
exit: T.fn(["code"], { type: "BlockStatement", body: [], directives: [] } as any, undefined as any),
|
|
33
|
+
hrtime: T.fn(["time"], { type: "BlockStatement", body: [], directives: [] } as any, undefined as any),
|
|
34
|
+
stdout: T.instanceOf("Socket"),
|
|
35
|
+
stderr: T.instanceOf("Socket"),
|
|
36
|
+
},
|
|
37
|
+
Date: {
|
|
38
|
+
now: T.number,
|
|
39
|
+
parse: T.number,
|
|
40
|
+
UTC: T.number,
|
|
41
|
+
},
|
|
42
|
+
Math: {
|
|
43
|
+
random: T.number,
|
|
44
|
+
floor: T.fn(["x"], { type: "BlockStatement", body: [] } as any, undefined as any),
|
|
45
|
+
ceil: T.fn(["x"], { type: "BlockStatement", body: [] } as any, undefined as any),
|
|
46
|
+
round: T.fn(["x"], { type: "BlockStatement", body: [] } as any, undefined as any),
|
|
47
|
+
abs: T.fn(["x"], { type: "BlockStatement", body: [] } as any, undefined as any),
|
|
48
|
+
max: T.fn(["...args"], { type: "BlockStatement", body: [] } as any, undefined as any),
|
|
49
|
+
min: T.fn(["...args"], { type: "BlockStatement", body: [] } as any, undefined as any),
|
|
50
|
+
sqrt: T.fn(["x"], { type: "BlockStatement", body: [] } as any, undefined as any),
|
|
51
|
+
pow: T.fn(["base", "exp"], { type: "BlockStatement", body: [] } as any, undefined as any),
|
|
52
|
+
// Math 常量与 @nudo:env es 的 Math 定义对齐(env 绑定优先);无 env
|
|
53
|
+
// 指令时也在此解析,避免 Math.PI 走 unknown-property。
|
|
54
|
+
PI: T.number,
|
|
55
|
+
E: T.number,
|
|
56
|
+
LN2: T.number,
|
|
57
|
+
LN10: T.number,
|
|
58
|
+
LOG2E: T.number,
|
|
59
|
+
LOG10E: T.number,
|
|
60
|
+
SQRT2: T.number,
|
|
61
|
+
SQRT1_2: T.number,
|
|
62
|
+
},
|
|
63
|
+
JSON: {
|
|
64
|
+
// JSON.parse(literal-string): evaluate with the real JSON.parse and
|
|
65
|
+
// re-encode through jsonToTypeValue, so fixture-style calls
|
|
66
|
+
// (`JSON.parse('{"a":1}')`) keep literal-level precision instead of
|
|
67
|
+
// poisoning downstream clone/deepEqual args with unknown. Degrades to
|
|
68
|
+
// the unknown fallback when the argument is not a literal string or
|
|
69
|
+
// the text is not valid JSON. A reviver argument also degrades: it can
|
|
70
|
+
// replace every value (and the root) with an arbitrary one, so the
|
|
71
|
+
// parse structure is not a sound answer (hoek/json-ext never pass one).
|
|
72
|
+
parse: T.fnSig([T.string], T.unknown, T.never, (args) => {
|
|
73
|
+
const text = args[0];
|
|
74
|
+
if (text?.kind !== "literal" || typeof text.value !== "string") return undefined;
|
|
75
|
+
const reviver = args[1];
|
|
76
|
+
if (reviver && !(reviver.kind === "literal" && reviver.value === undefined)) return undefined;
|
|
77
|
+
try {
|
|
78
|
+
return jsonToTypeValue(JSON.parse(text.value));
|
|
79
|
+
} catch {
|
|
80
|
+
return undefined;
|
|
81
|
+
}
|
|
82
|
+
}),
|
|
83
|
+
stringify: T.string,
|
|
84
|
+
},
|
|
85
|
+
Object: {
|
|
86
|
+
keys: T.array(T.string),
|
|
87
|
+
values: T.array(T.unknown),
|
|
88
|
+
entries: T.array(T.tuple([T.string, T.unknown])),
|
|
89
|
+
assign: T.fnSig([T.unknown], T.unknown, T.never, (args) => {
|
|
90
|
+
// Object.assign(target, ...sources): later sources overwrite; a
|
|
91
|
+
// symbolic source keeps the symbolic unknown fallback.
|
|
92
|
+
const target = args[0];
|
|
93
|
+
if (target?.kind !== "object") return undefined;
|
|
94
|
+
const props: Record<string, TypeValue> = { ...target.properties };
|
|
95
|
+
for (const src of args.slice(1)) {
|
|
96
|
+
if (src?.kind !== "object") return undefined;
|
|
97
|
+
Object.assign(props, src.properties);
|
|
98
|
+
}
|
|
99
|
+
return T.object(props);
|
|
100
|
+
}),
|
|
101
|
+
// Prototype-reflection statics. getPrototypeOf maps receivers onto the
|
|
102
|
+
// cached prototype singletons so `getPrototypeOf(x) !== getPrototypeOf(y)`
|
|
103
|
+
// resolves literally; create/setPrototypeOf cover the clone-style
|
|
104
|
+
// prototype dance; getOwnPropertyDescriptor feeds descriptor.get/set
|
|
105
|
+
// branches with the property's value type.
|
|
106
|
+
getPrototypeOf: T.fnSig([T.unknown], T.unknown, T.never, (args) => protoOfValue(args[0])),
|
|
107
|
+
create: T.fnSig([T.unknown], T.object({}), T.never, () => T.object({})),
|
|
108
|
+
setPrototypeOf: T.fnSig([T.unknown, T.unknown], T.unknown, T.never, (args) => args[0]),
|
|
109
|
+
defineProperty: T.fnSig([T.unknown, T.unknown, T.unknown], T.unknown, T.never, (args) => args[0]),
|
|
110
|
+
getOwnPropertyDescriptor: T.fnSig([T.unknown, T.unknown], T.union(T.object({}), T.undefined), T.never, (args) => {
|
|
111
|
+
const obj = args[0];
|
|
112
|
+
const key = args[1];
|
|
113
|
+
if (obj?.kind !== "object" && obj?.kind !== "instance") return T.undefined;
|
|
114
|
+
if (!key) return T.undefined;
|
|
115
|
+
if (key.kind === "literal" && typeof key.value === "string") {
|
|
116
|
+
const prop = hasOwnProp(obj.properties, key.value) ? obj.properties[key.value] : undefined;
|
|
117
|
+
if (!prop) return T.undefined;
|
|
118
|
+
return descriptorOf(prop);
|
|
119
|
+
}
|
|
120
|
+
if (key.kind === "primitive" && key.type === "string") {
|
|
121
|
+
const props = Object.keys(obj.properties);
|
|
122
|
+
if (props.length === 0) return T.undefined;
|
|
123
|
+
return descriptorOf(simplifyUnion(props.map((k) => obj.properties[k])));
|
|
124
|
+
}
|
|
125
|
+
return T.undefined;
|
|
126
|
+
}),
|
|
127
|
+
},
|
|
128
|
+
Buffer: {
|
|
129
|
+
from: T.fnSig([T.unknown], T.instanceOf("Buffer")),
|
|
130
|
+
alloc: T.fnSig([T.number], T.instanceOf("Buffer")),
|
|
131
|
+
concat: T.fnSig([T.unknown], T.instanceOf("Buffer")),
|
|
132
|
+
byteLength: T.fnSig([T.unknown], T.number),
|
|
133
|
+
isEncoding: T.fnSig([T.unknown], T.boolean),
|
|
134
|
+
isBuffer: T.fnSig([T.unknown], T.boolean, T.never, (args) => {
|
|
135
|
+
const lit = builtinInstanceTest(args[0], "Buffer");
|
|
136
|
+
return lit === undefined ? undefined : T.literal(lit);
|
|
137
|
+
}),
|
|
138
|
+
},
|
|
139
|
+
Array: {
|
|
140
|
+
isArray: T.boolean,
|
|
141
|
+
from: T.array(T.unknown),
|
|
142
|
+
},
|
|
143
|
+
Number: {
|
|
144
|
+
isNaN: T.boolean,
|
|
145
|
+
isFinite: T.boolean,
|
|
146
|
+
parseInt: T.number,
|
|
147
|
+
parseFloat: T.number,
|
|
148
|
+
// Number 常量与 @nudo:env es 的 Number 定义对齐(env 绑定优先);
|
|
149
|
+
// 均为 number(case 序列化器对非有限数返回 null,不产出字面量值)。
|
|
150
|
+
MAX_SAFE_INTEGER: T.number,
|
|
151
|
+
MIN_SAFE_INTEGER: T.number,
|
|
152
|
+
MAX_VALUE: T.number,
|
|
153
|
+
MIN_VALUE: T.number,
|
|
154
|
+
POSITIVE_INFINITY: T.number,
|
|
155
|
+
NEGATIVE_INFINITY: T.number,
|
|
156
|
+
NaN: T.number,
|
|
157
|
+
EPSILON: T.number,
|
|
158
|
+
},
|
|
159
|
+
String: {
|
|
160
|
+
// fromCharCode/fromCodePoint over all-number-literal args evaluate
|
|
161
|
+
// with the real String static (hoek's escaper builds its 0x2028
|
|
162
|
+
// separator literal this way before handing it to escapeJson); any
|
|
163
|
+
// non-literal arg keeps only the string return type (the result is
|
|
164
|
+
// always a string, just not a knowable one). fromCodePoint also
|
|
165
|
+
// falls back on out-of-range literals (real RangeError → string).
|
|
166
|
+
fromCharCode: T.fnSig([T.number], T.string, T.never, (args) => {
|
|
167
|
+
const codes = literalCodeUnits(args);
|
|
168
|
+
return codes === null ? T.string : T.literal(String.fromCharCode(...codes));
|
|
169
|
+
}),
|
|
170
|
+
fromCodePoint: T.fnSig([T.number], T.string, T.never, (args) => {
|
|
171
|
+
const codes = literalCodeUnits(args);
|
|
172
|
+
if (codes === null) return T.string;
|
|
173
|
+
try {
|
|
174
|
+
return T.literal(String.fromCodePoint(...codes));
|
|
175
|
+
} catch {
|
|
176
|
+
return undefined;
|
|
177
|
+
}
|
|
178
|
+
}),
|
|
179
|
+
},
|
|
180
|
+
Promise: PROMISE_STATIC_METHODS,
|
|
181
|
+
Symbol: { ...SYMBOL_STATIC_METHODS, ...SYMBOL_STATIC_PROPS },
|
|
182
|
+
Reflect: REFLECT_METHODS as unknown as Record<string, TypeValue>,
|
|
183
|
+
Intl: {
|
|
184
|
+
DateTimeFormat: T.fn(["...args"], { type: "BlockStatement", body: [] } as any, undefined as any),
|
|
185
|
+
NumberFormat: T.fn(["...args"], { type: "BlockStatement", body: [] } as any, undefined as any),
|
|
186
|
+
},
|
|
187
|
+
parseInt: T.number,
|
|
188
|
+
parseFloat: T.number,
|
|
189
|
+
isNaN: T.boolean,
|
|
190
|
+
isFinite: T.boolean,
|
|
191
|
+
// 全局数值常量:与 @nudo:env es 的 globals 对齐(env 绑定优先)。无 env
|
|
192
|
+
// 指令时在此解析为 number——否则 Infinity/NaN 命中大写开头的未知全局
|
|
193
|
+
// 检测,触发 nudo:unknown-global 与 nudo:builtin-unknown 诊断。
|
|
194
|
+
Infinity: T.number,
|
|
195
|
+
NaN: T.number,
|
|
196
|
+
};
|
|
197
|
+
/** Harvest String.fromCharCode/fromCodePoint arguments: a list of number
|
|
198
|
+
* literals ready for real evaluation, or null when any argument is
|
|
199
|
+
* non-literal (symbolic → the caller keeps its plain string fallback).
|
|
200
|
+
* An empty argument list yields [] (both statics return ""). */
|
|
201
|
+
function literalCodeUnits(args: TypeValue[]): number[] | null {
|
|
202
|
+
const codes: number[] = [];
|
|
203
|
+
for (const a of args) {
|
|
204
|
+
if (a?.kind !== "literal" || typeof a.value !== "number") return null;
|
|
205
|
+
codes.push(a.value);
|
|
206
|
+
}
|
|
207
|
+
return codes;
|
|
208
|
+
}
|
|
209
|
+
function descriptorOf(prop: TypeValue): TypeValue {
|
|
210
|
+
return T.object({
|
|
211
|
+
value: prop,
|
|
212
|
+
writable: T.boolean,
|
|
213
|
+
enumerable: T.boolean,
|
|
214
|
+
configurable: T.boolean,
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
// `x instanceof C` / `Buffer.isBuffer(x)` / `Array.isArray(x)` literal
|
|
218
|
+
// answer for structurally-known receivers; undefined keeps the symbolic
|
|
219
|
+
// fallback (boolean).
|
|
220
|
+
function builtinInstanceTest(v: TypeValue | undefined, className: string): boolean | undefined {
|
|
221
|
+
if (!v) return undefined;
|
|
222
|
+
let base: TypeValue = v;
|
|
223
|
+
while (base.kind === "refined") base = base.base;
|
|
224
|
+
switch (base.kind) {
|
|
225
|
+
case "array":
|
|
226
|
+
case "tuple": return className === "Array" || className === "Object";
|
|
227
|
+
case "object": return className === "Object";
|
|
228
|
+
case "function": return className === "Function" || className === "Object";
|
|
229
|
+
case "promise": return className === "Promise" || className === "Object";
|
|
230
|
+
case "literal":
|
|
231
|
+
case "primitive": return false;
|
|
232
|
+
default: return undefined;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
/** JSON 值 → TypeValue(字面量级精确:package.json 版本号等成为 string 字面量) */
|
|
236
|
+
function jsonToTypeValue(v: unknown): TypeValue {
|
|
237
|
+
if (v === null) return T.literal(null);
|
|
238
|
+
if (typeof v === "string") return T.literal(v);
|
|
239
|
+
if (typeof v === "number") return T.literal(v);
|
|
240
|
+
if (typeof v === "boolean") return T.literal(v);
|
|
241
|
+
if (Array.isArray(v)) return T.tuple(v.map(jsonToTypeValue));
|
|
242
|
+
if (typeof v === "object") {
|
|
243
|
+
const props: Record<string, TypeValue> = {};
|
|
244
|
+
for (const [k, val] of Object.entries(v)) {
|
|
245
|
+
// Define-as-own: plain assignment would let a "__proto__" key trip
|
|
246
|
+
// the setter on the props record (dropping it and corrupting the
|
|
247
|
+
// record's prototype) — JSON.parse creates an own property, and
|
|
248
|
+
// hoek's prototype-poisoning fixtures exercise exactly that key.
|
|
249
|
+
Object.defineProperty(props, k, {
|
|
250
|
+
value: jsonToTypeValue(val),
|
|
251
|
+
enumerable: true,
|
|
252
|
+
writable: true,
|
|
253
|
+
configurable: true,
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
return T.object(props);
|
|
257
|
+
}
|
|
258
|
+
return T.unknown;
|
|
259
|
+
}
|
|
260
|
+
export { BUILTIN_STATIC_METHODS, builtinInstanceTest, jsonToTypeValue };
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 二元运算统一入口:代数优先,语言表面回落 Ops。
|
|
3
|
+
* TypeValue 是 IR;+ - * / % 比较与数值相等走 Abs,其余外延。
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
type TypeValue,
|
|
8
|
+
T,
|
|
9
|
+
simplifyUnion,
|
|
10
|
+
dispatchBinaryOp,
|
|
11
|
+
} from "@nudojs/core";
|
|
12
|
+
import { tryAbsBinary } from "./abs-route.ts";
|
|
13
|
+
|
|
14
|
+
const MAX_UNION_PRODUCT = 64;
|
|
15
|
+
|
|
16
|
+
export function looseLiteralValue(
|
|
17
|
+
tv: TypeValue,
|
|
18
|
+
): { hit: boolean; v: number | string | boolean | null | undefined } {
|
|
19
|
+
if (tv.kind === "literal") {
|
|
20
|
+
const v = tv.value;
|
|
21
|
+
if (
|
|
22
|
+
typeof v === "number" ||
|
|
23
|
+
typeof v === "string" ||
|
|
24
|
+
typeof v === "boolean" ||
|
|
25
|
+
v === null ||
|
|
26
|
+
v === undefined
|
|
27
|
+
) {
|
|
28
|
+
return { hit: true, v };
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return { hit: false, v: undefined };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function distributeBinaryOverUnion(
|
|
35
|
+
left: TypeValue,
|
|
36
|
+
right: TypeValue,
|
|
37
|
+
fn: (l: TypeValue, r: TypeValue) => TypeValue,
|
|
38
|
+
): TypeValue {
|
|
39
|
+
if (left.kind === "union" && right.kind === "union") {
|
|
40
|
+
if (left.members.length * right.members.length > MAX_UNION_PRODUCT) {
|
|
41
|
+
return T.unknown;
|
|
42
|
+
}
|
|
43
|
+
return simplifyUnion(
|
|
44
|
+
left.members.flatMap((l) => right.members.map((r) => fn(l, r))),
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
if (left.kind === "union") {
|
|
48
|
+
return simplifyUnion(left.members.map((l) => fn(l, right)));
|
|
49
|
+
}
|
|
50
|
+
if (right.kind === "union") {
|
|
51
|
+
return simplifyUnion(right.members.map((r) => fn(left, r)));
|
|
52
|
+
}
|
|
53
|
+
return fn(left, right);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const ALGEBRA_OPS = new Set([
|
|
57
|
+
"+", "-", "*", "/", "%",
|
|
58
|
+
"<", "<=", ">", ">=",
|
|
59
|
+
"===", "!==",
|
|
60
|
+
]);
|
|
61
|
+
|
|
62
|
+
/** 数值运算形状:任一侧 number/unknown 时,`- * / %` 结果至少是 number */
|
|
63
|
+
function numberish(tv: TypeValue): boolean {
|
|
64
|
+
if (!tv) return false;
|
|
65
|
+
if (tv.kind === "unknown") return true;
|
|
66
|
+
if (tv.kind === "literal") return typeof tv.value === "number";
|
|
67
|
+
if (tv.kind === "primitive") return tv.type === "number";
|
|
68
|
+
if (tv.kind === "refined") return numberish(tv.base);
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* 纯值级二元运算(不需要 AST/env)。
|
|
74
|
+
* 返回 undefined 表示调用方需处理 node 特例(instanceof / in / builtin identity)。
|
|
75
|
+
*/
|
|
76
|
+
export function evalBinaryValue(
|
|
77
|
+
operator: string,
|
|
78
|
+
left: TypeValue,
|
|
79
|
+
right: TypeValue,
|
|
80
|
+
): TypeValue | undefined {
|
|
81
|
+
if (operator === "instanceof" || operator === "in") return undefined;
|
|
82
|
+
|
|
83
|
+
if (ALGEBRA_OPS.has(operator)) {
|
|
84
|
+
const absResult = tryAbsBinary(operator, left, right);
|
|
85
|
+
if (absResult !== undefined) return absResult;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (operator === "==" || operator === "!=") {
|
|
89
|
+
const eq = operator === "==";
|
|
90
|
+
return distributeBinaryOverUnion(left, right, (l, r) => {
|
|
91
|
+
const lv = looseLiteralValue(l);
|
|
92
|
+
const rv = looseLiteralValue(r);
|
|
93
|
+
if (lv.hit && rv.hit) return T.literal(eq ? lv.v == rv.v : lv.v != rv.v);
|
|
94
|
+
return T.boolean;
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// refined 自定义 ops / Ops.add·比较 / nullish ===
|
|
99
|
+
const viaDispatch = distributeBinaryOverUnion(left, right, (l, r) =>
|
|
100
|
+
dispatchBinaryOp(operator, l, r),
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
// 代数与 refinement 都未接住时的数值形状:`unknown * 2` → number
|
|
104
|
+
if (
|
|
105
|
+
viaDispatch.kind === "unknown" &&
|
|
106
|
+
(operator === "-" || operator === "*" || operator === "/" || operator === "%") &&
|
|
107
|
+
(numberish(left) || numberish(right))
|
|
108
|
+
) {
|
|
109
|
+
return T.number;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return viaDispatch;
|
|
113
|
+
}
|
package/src/evaluator-api.ts
CHANGED
|
@@ -29,6 +29,9 @@ export {
|
|
|
29
29
|
|
|
30
30
|
export { narrow } from "./narrowing.ts";
|
|
31
31
|
|
|
32
|
+
// 补全成员派生的唯一真值(service/analyzer 经 @nudojs/cli/evaluator 取用)
|
|
33
|
+
export { BUILTIN_PROTOTYPE_METHOD_APPROXIMATIONS } from "./builtins/builtin-prototype.ts";
|
|
34
|
+
|
|
32
35
|
export { loadEnvs, loadEnvsAsync, preloadPathEnvs, type LoadedEnv } from "./env-loader.ts";
|
|
33
36
|
|
|
34
37
|
export { findProjectConfig, type NudoConfig } from "./config.ts";
|