@nudojs/core 0.1.0 → 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/CHANGELOG.md +27 -0
- package/LICENSE +21 -0
- package/README.md +26 -0
- package/dist/index.d.ts +1633 -11
- package/dist/index.js +9965 -107
- package/package.json +28 -2
- package/src/__tests__/fn-sig-impl.test.ts +57 -0
- package/src/__tests__/ops-refined.test.ts +196 -0
- package/src/__tests__/ops.test.ts +8 -33
- package/src/__tests__/type-value-refined.test.ts +210 -0
- package/src/__tests__/type-value.test.ts +106 -0
- package/src/algebra/__tests__/abs-native.test.ts +176 -0
- package/src/algebra/__tests__/arithmetic.test.ts +170 -0
- package/src/algebra/__tests__/ast-eval.test.ts +154 -0
- package/src/algebra/__tests__/bridge.test.ts +160 -0
- package/src/algebra/__tests__/call-budget.test.ts +117 -0
- package/src/algebra/__tests__/check-case-consistency.test.ts +69 -0
- package/src/algebra/__tests__/check-gold.test.ts +290 -0
- package/src/algebra/__tests__/check-json.test.ts +57 -0
- package/src/algebra/__tests__/check-mini-repo.test.ts +49 -0
- package/src/algebra/__tests__/check-real-commander.test.ts +43 -0
- package/src/algebra/__tests__/check-real-packages.test.ts +165 -0
- package/src/algebra/__tests__/check-recall-gold.test.ts +1024 -0
- package/src/algebra/__tests__/check-require-dep-memo.test.ts +45 -0
- package/src/algebra/__tests__/check-return-contract.test.ts +90 -0
- package/src/algebra/__tests__/check-shape-gold.test.ts +303 -0
- package/src/algebra/__tests__/check-source-memo.test.ts +204 -0
- package/src/algebra/__tests__/check.test.ts +69 -0
- package/src/algebra/__tests__/denote.test.ts +77 -0
- package/src/algebra/__tests__/diagnostics.test.ts +64 -0
- package/src/algebra/__tests__/differential.test.ts +124 -0
- package/src/algebra/__tests__/div-mod.test.ts +38 -0
- package/src/algebra/__tests__/example0.test.ts +103 -0
- package/src/algebra/__tests__/exec-analyze-throws.test.ts +60 -0
- package/src/algebra/__tests__/exec-async.test.ts +46 -0
- package/src/algebra/__tests__/exec-bpath.test.ts +193 -0
- package/src/algebra/__tests__/exec-class.test.ts +56 -0
- package/src/algebra/__tests__/exec-e2e.test.ts +97 -0
- package/src/algebra/__tests__/exec-generator.test.ts +53 -0
- package/src/algebra/__tests__/exec-inherit-default.test.ts +111 -0
- package/src/algebra/__tests__/exec-optional-nested.test.ts +87 -0
- package/src/algebra/__tests__/exec-rest.test.ts +55 -0
- package/src/algebra/__tests__/exec-spread-forof.test.ts +85 -0
- package/src/algebra/__tests__/exec-static-computed.test.ts +61 -0
- package/src/algebra/__tests__/exec-switch.test.ts +74 -0
- package/src/algebra/__tests__/exec-trycatch.test.ts +71 -0
- package/src/algebra/__tests__/fn-fp-memo.test.ts +166 -0
- package/src/algebra/__tests__/fn-fp-multidecl.test.ts +49 -0
- package/src/algebra/__tests__/format.test.ts +34 -0
- package/src/algebra/__tests__/generalize-memo.test.ts +285 -0
- package/src/algebra/__tests__/generalize.test.ts +113 -0
- package/src/algebra/__tests__/hash-source.test.ts +19 -0
- package/src/algebra/__tests__/hof.test.ts +80 -0
- package/src/algebra/__tests__/inlay.test.ts +30 -0
- package/src/algebra/__tests__/language.test.ts +77 -0
- package/src/algebra/__tests__/leq.test.ts +109 -0
- package/src/algebra/__tests__/load-deps-fp-memo.test.ts +132 -0
- package/src/algebra/__tests__/methods.test.ts +37 -0
- package/src/algebra/__tests__/node-types.test.ts +26 -0
- package/src/algebra/__tests__/nudo-constraints.ts +46 -0
- package/src/algebra/__tests__/objects.test.ts +117 -0
- package/src/algebra/__tests__/parse-source-cache.test.ts +42 -0
- package/src/algebra/__tests__/refine-import.test.ts +109 -0
- package/src/algebra/__tests__/stable-source-key-block.test.ts +23 -0
- package/src/algebra/__tests__/stable-source-key.test.ts +31 -0
- package/src/algebra/__tests__/surface.test.ts +41 -0
- package/src/algebra/__tests__/this-inherit.test.ts +130 -0
- package/src/algebra/abs-fn.ts +49 -0
- package/src/algebra/abs-modules.ts +139 -0
- package/src/algebra/abs.ts +206 -0
- package/src/algebra/arithmetic.ts +691 -0
- package/src/algebra/ast-eval.ts +1763 -0
- package/src/algebra/bridge.ts +350 -0
- package/src/algebra/builtins.ts +373 -0
- package/src/algebra/check-report.ts +150 -0
- package/src/algebra/check.ts +1867 -0
- package/src/algebra/constraint.ts +304 -0
- package/src/algebra/denote.ts +166 -0
- package/src/algebra/diagnostics.ts +210 -0
- package/src/algebra/exec/call.ts +29 -0
- package/src/algebra/exec/calls.ts +96 -0
- package/src/algebra/exec/class-registry.ts +29 -0
- package/src/algebra/exec/class.ts +329 -0
- package/src/algebra/exec/index.ts +6 -0
- package/src/algebra/exec/member-diag.ts +141 -0
- package/src/algebra/exec/run.ts +341 -0
- package/src/algebra/exec/runtime.ts +583 -0
- package/src/algebra/exec/transpile.ts +993 -0
- package/src/algebra/fn-fp.ts +294 -0
- package/src/algebra/format.ts +91 -0
- package/src/algebra/generalize.ts +776 -0
- package/src/algebra/hash-source.ts +27 -0
- package/src/algebra/index.ts +34 -0
- package/src/algebra/inlay.ts +198 -0
- package/src/algebra/language.ts +289 -0
- package/src/algebra/leak.ts +68 -0
- package/src/algebra/leq.ts +301 -0
- package/src/algebra/load-deps-fp.ts +97 -0
- package/src/algebra/methods.ts +128 -0
- package/src/algebra/objects.ts +251 -0
- package/src/algebra/parse-source.ts +64 -0
- package/src/algebra/phi.ts +42 -0
- package/src/algebra/pred.ts +395 -0
- package/src/algebra/refine.ts +227 -0
- package/src/algebra/stable-source-key.ts +81 -0
- package/src/algebra/surface.ts +206 -0
- package/src/algebra/template.ts +143 -0
- package/src/algebra/term.ts +119 -0
- package/src/environment.ts +5 -0
- package/src/index.ts +52 -1
- package/src/mock-helpers.ts +166 -0
- package/src/ops.ts +106 -62
- package/src/refinements/range.ts +86 -0
- package/src/refinements/template-predicates.ts +11 -0
- package/src/refinements/template.ts +176 -0
- package/src/strip-types.ts +236 -0
- package/src/type-value.ts +193 -12
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import type { Node } from "@babel/types";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* TS 剥除 pass(Babel typescript 插件 AST → 纯 JS AST)。
|
|
5
|
+
*
|
|
6
|
+
* 为什么放在 parser 层而不是 evaluator 里逐节点 unwrap:
|
|
7
|
+
* parse() 的产物被所有下游消费——CLI resolveModule/generate、service
|
|
8
|
+
* analyzer/case-emitter、LSP server/agent-tools、evaluator 的整条求值链。
|
|
9
|
+
* 在 evaluator 层做 unwrap 只能修一条链,其余消费方(buildModuleGraph 的
|
|
10
|
+
* import 提取、directive 提取、semanticTokens 等)仍会撞上 TS-only 节点。
|
|
11
|
+
* parse() 统一剥除后所有消费方零改动即可分析 .ts 文件。
|
|
12
|
+
*
|
|
13
|
+
* 对纯 JS 源码本 pass 是结构性 no-op(TS-only 节点/字段根本不会出现),
|
|
14
|
+
* 向后兼容;节点均原地改写,loc/start/end 与兄弟节点顺序保持不变
|
|
15
|
+
* (@nudo:case 注释按 comment loc 对齐,删语句只从语句数组中剔除)。
|
|
16
|
+
*
|
|
17
|
+
* 剥除规则:
|
|
18
|
+
* - 表达式解包:TSAsExpression(含 as const)/ TSSatisfiesExpression /
|
|
19
|
+
* TSTypeAssertion(<T>x)/ TSNonNullExpression(x!)/ TSInstantiationExpression
|
|
20
|
+
* (裸泛型引用 const f = foo<number>)→ 直接取 .expression
|
|
21
|
+
* - 类型声明删除:TSInterfaceDeclaration / TSTypeAliasDeclaration /
|
|
22
|
+
* TSDeclareFunction / declare 修饰的 TSModuleDeclaration、VariableDeclaration、
|
|
23
|
+
* ClassDeclaration,以及包装它们的 ExportNamedDeclaration / ExportDefaultDeclaration
|
|
24
|
+
* - TSEnumDeclaration 直接删除。风险:TS enum 有运行时语义(编译为带反向映射的
|
|
25
|
+
* 对象),删除后源码中 enum 成员引用会退化为 unknown-global 诊断;nudo 的
|
|
26
|
+
* 求值器没有 enum 求值语义,保真合成对象超出本次范围
|
|
27
|
+
* - type-only import/export:importKind/exportKind === "type" 的声明删除;
|
|
28
|
+
* 值 import 里混入的 type specifier(import { type A, b })按 specifier 剔除,
|
|
29
|
+
* 全部为 type 时整个声明删除
|
|
30
|
+
* - 类成员:TSIndexSignature / TSDeclareMethod(含 abstract 方法)/ declare·abstract
|
|
31
|
+
* 的 ClassProperty 删除;implements / superTypeParameters 字段删除
|
|
32
|
+
* - 求值器不读的类型字段:typeParameters(声明与调用点 TSTypeParameterInstantiation,
|
|
33
|
+
* 即 foo<string>(1))、typeAnnotation、returnType(含 TSTypePredicate)、
|
|
34
|
+
* 参数 Identifier/ObjectPattern/ArrayPattern/RestElement 上的 optional 一律 delete
|
|
35
|
+
*
|
|
36
|
+
* 已知不处理(会以 unknown 求值,不崩溃):非 declare 的 namespace(运行时语义)、
|
|
37
|
+
* TSImportEqualsDeclaration(import x = require(...))、构造器参数属性
|
|
38
|
+
* (constructor(private x))——均在 .ts 推断目标场景之外。
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
/** 数组槽位删除哨兵:语句/成员/specifier 只出现在数组上下文中 */
|
|
42
|
+
const REMOVE: unique symbol = Symbol("nudo.strip.remove");
|
|
43
|
+
|
|
44
|
+
/** 不含 AST 子节点的键:位置/注释/打印器元数据 */
|
|
45
|
+
const SKIP_KEYS = new Set([
|
|
46
|
+
"loc",
|
|
47
|
+
"start",
|
|
48
|
+
"end",
|
|
49
|
+
"range",
|
|
50
|
+
"leadingComments",
|
|
51
|
+
"trailingComments",
|
|
52
|
+
"innerComments",
|
|
53
|
+
"comments",
|
|
54
|
+
"tokens",
|
|
55
|
+
"extra",
|
|
56
|
+
]);
|
|
57
|
+
|
|
58
|
+
/** 实参/形参模式等数组中的空洞(ArrayPattern 的 elision)保持原样 */
|
|
59
|
+
function transformValue(value: unknown): unknown {
|
|
60
|
+
if (Array.isArray(value)) {
|
|
61
|
+
let changed = false;
|
|
62
|
+
const out: unknown[] = [];
|
|
63
|
+
for (const el of value) {
|
|
64
|
+
if (el === null || el === undefined) {
|
|
65
|
+
out.push(el);
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
const r = transformValue(el);
|
|
69
|
+
if (r === REMOVE) {
|
|
70
|
+
changed = true;
|
|
71
|
+
} else {
|
|
72
|
+
if (r !== el) changed = true;
|
|
73
|
+
out.push(r);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return changed ? out : value;
|
|
77
|
+
}
|
|
78
|
+
if (typeof value !== "object" || value === null) return value;
|
|
79
|
+
if (typeof (value as { type?: unknown }).type !== "string") return value;
|
|
80
|
+
return transformNode(value as Node & Record<string, unknown>);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function isTsDeclareLike(node: Node): boolean {
|
|
84
|
+
return (node as { declare?: unknown }).declare === true;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** 类型声明本身是否 TS-only(供 export 包装语句判断整体删除) */
|
|
88
|
+
function isTypeOnlyDeclaration(decl: Node): boolean {
|
|
89
|
+
switch (decl.type) {
|
|
90
|
+
case "TSInterfaceDeclaration":
|
|
91
|
+
case "TSTypeAliasDeclaration":
|
|
92
|
+
case "TSEnumDeclaration":
|
|
93
|
+
case "TSDeclareFunction":
|
|
94
|
+
return true;
|
|
95
|
+
case "VariableDeclaration":
|
|
96
|
+
case "ClassDeclaration":
|
|
97
|
+
return isTsDeclareLike(decl);
|
|
98
|
+
default:
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* 语句/类成员/specifier 级删除判定(只会在数组上下文中出现,
|
|
105
|
+
* 返回 REMOVE 时父数组剔除该元素)。
|
|
106
|
+
*/
|
|
107
|
+
function isTypeOnlyStatement(node: Node): boolean {
|
|
108
|
+
const anyNode = node as Node & { importKind?: string; exportKind?: string; specifiers?: unknown[] };
|
|
109
|
+
switch (node.type) {
|
|
110
|
+
case "TSInterfaceDeclaration":
|
|
111
|
+
case "TSTypeAliasDeclaration":
|
|
112
|
+
case "TSEnumDeclaration":
|
|
113
|
+
case "TSDeclareFunction":
|
|
114
|
+
return true;
|
|
115
|
+
case "TSModuleDeclaration":
|
|
116
|
+
// declare module / declare namespace 纯类型;非 declare 的 namespace 有运行时语义,保留
|
|
117
|
+
return isTsDeclareLike(node);
|
|
118
|
+
case "VariableDeclaration":
|
|
119
|
+
case "ClassDeclaration":
|
|
120
|
+
return isTsDeclareLike(node);
|
|
121
|
+
case "ImportDeclaration":
|
|
122
|
+
if (anyNode.importKind === "type") return true;
|
|
123
|
+
// import { type A, type B } from "m" —— 全 type specifier 则整个 import 无值语义
|
|
124
|
+
{
|
|
125
|
+
const specs = anyNode.specifiers as { importKind?: string }[] | undefined;
|
|
126
|
+
if (specs && specs.length > 0 && specs.every((s) => s.importKind === "type" || s.importKind === "typeof")) {
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return false;
|
|
131
|
+
case "ExportNamedDeclaration": {
|
|
132
|
+
if (anyNode.exportKind === "type") return true;
|
|
133
|
+
const decl = (node as { declaration: Node | null }).declaration;
|
|
134
|
+
if (decl && isTypeOnlyDeclaration(decl)) return true;
|
|
135
|
+
// export { type A } / export { type A } from "m"
|
|
136
|
+
{
|
|
137
|
+
const specs = anyNode.specifiers as { exportKind?: string }[] | null;
|
|
138
|
+
if (!decl && specs && specs.length > 0 && specs.every((s) => s.exportKind === "type")) return true;
|
|
139
|
+
}
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
case "ExportAllDeclaration":
|
|
143
|
+
return anyNode.exportKind === "type";
|
|
144
|
+
case "ExportDefaultDeclaration":
|
|
145
|
+
return isTypeOnlyDeclaration((node as { declaration: Node }).declaration);
|
|
146
|
+
// ---- 类成员 ----
|
|
147
|
+
case "TSIndexSignature":
|
|
148
|
+
case "TSDeclareMethod": // declare / abstract 方法,无运行时实现
|
|
149
|
+
return true;
|
|
150
|
+
case "ClassProperty":
|
|
151
|
+
case "ClassPrivateProperty":
|
|
152
|
+
return isTsDeclareLike(node) || (node as { abstract?: unknown }).abstract === true;
|
|
153
|
+
default:
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** 求值器不读的类型字段:原地 delete */
|
|
159
|
+
function deleteTypeSyntaxFields(node: Node & Record<string, unknown>): void {
|
|
160
|
+
const tp = node.typeParameters as { type?: string } | undefined;
|
|
161
|
+
if (tp && typeof tp.type === "string" && tp.type.startsWith("TSTypeParameter")) {
|
|
162
|
+
// 声明处(TSTypeParameterDeclaration)与调用处(TSTypeParameterInstantiation,
|
|
163
|
+
// 即 foo<string>(1) / new Foo<string>())一并删除
|
|
164
|
+
delete node.typeParameters;
|
|
165
|
+
}
|
|
166
|
+
const ta = node.typeAnnotation as { type?: string } | undefined;
|
|
167
|
+
if (ta && ta.type === "TSTypeAnnotation") delete node.typeAnnotation;
|
|
168
|
+
const rt = node.returnType as { type?: string } | undefined;
|
|
169
|
+
if (rt && (rt.type === "TSTypeAnnotation" || rt.type === "TSTypePredicate")) delete node.returnType;
|
|
170
|
+
// `a?: T` 的可选标记。只在这些模式节点上删:OptionalMemberExpression 等
|
|
171
|
+
// 表达式节点自己的 optional 语义(a?.b)不属于 TS 语法,不能动
|
|
172
|
+
if (
|
|
173
|
+
node.optional === true &&
|
|
174
|
+
(node.type === "Identifier" || node.type === "ObjectPattern" || node.type === "ArrayPattern" || node.type === "RestElement")
|
|
175
|
+
) {
|
|
176
|
+
delete node.optional;
|
|
177
|
+
}
|
|
178
|
+
// 类的 implements 子句与继承泛型实参
|
|
179
|
+
if ("implements" in node) delete node.implements;
|
|
180
|
+
if ("superTypeParameters" in node) delete node.superTypeParameters;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** 表达式级解包:类型断言/满足断言/非空断言/裸泛型引用 → 内层表达式 */
|
|
184
|
+
function unwrapExpression(node: Node & Record<string, unknown>): Node | null {
|
|
185
|
+
switch (node.type) {
|
|
186
|
+
case "TSAsExpression": // 含 as const
|
|
187
|
+
case "TSSatisfiesExpression":
|
|
188
|
+
case "TSTypeAssertion":
|
|
189
|
+
case "TSNonNullExpression":
|
|
190
|
+
case "TSInstantiationExpression":
|
|
191
|
+
return node.expression as Node;
|
|
192
|
+
default:
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function transformNode(node: Node & Record<string, unknown>): Node | typeof REMOVE {
|
|
198
|
+
if (isTypeOnlyStatement(node)) return REMOVE;
|
|
199
|
+
|
|
200
|
+
deleteTypeSyntaxFields(node);
|
|
201
|
+
|
|
202
|
+
// 值 import/export 里混入的 type-only specifier 逐个剔除
|
|
203
|
+
if (node.type === "ImportDeclaration") {
|
|
204
|
+
node.specifiers = node.specifiers.filter(
|
|
205
|
+
(s) => !((s as { importKind?: string }).importKind === "type" || (s as { importKind?: string }).importKind === "typeof"),
|
|
206
|
+
);
|
|
207
|
+
} else if (node.type === "ExportNamedDeclaration") {
|
|
208
|
+
if (node.specifiers) {
|
|
209
|
+
node.specifiers = node.specifiers.filter((s) => (s as { exportKind?: string }).exportKind !== "type");
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
for (const key of Object.keys(node)) {
|
|
214
|
+
if (SKIP_KEYS.has(key)) continue;
|
|
215
|
+
const child = node[key];
|
|
216
|
+
if (child === null || typeof child !== "object") continue;
|
|
217
|
+
const r = transformValue(child);
|
|
218
|
+
if (r === REMOVE) {
|
|
219
|
+
// 单槽位理论上不会收到 REMOVE(删除项均为数组上下文),防御性清槽
|
|
220
|
+
delete node[key];
|
|
221
|
+
} else {
|
|
222
|
+
node[key] = r;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return unwrapExpression(node) ?? node;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* 原地剥除 AST 中的 TS-only 语法节点/字段,返回同一棵 AST(便于链式使用)。
|
|
231
|
+
* 不改写任何 loc/start/end;语句删除只影响所在数组,不重排兄弟节点。
|
|
232
|
+
*/
|
|
233
|
+
export function stripTypes<T extends Node>(ast: T): T {
|
|
234
|
+
transformValue(ast);
|
|
235
|
+
return ast;
|
|
236
|
+
}
|
package/src/type-value.ts
CHANGED
|
@@ -1,9 +1,33 @@
|
|
|
1
1
|
import type { Node } from "@babel/types";
|
|
2
2
|
import type { Environment } from "./environment.ts";
|
|
3
|
+
// 注意:isTemplate 必须来自 template-predicates.ts(只含 `import type` 反向边)
|
|
4
|
+
// 而非 template.ts——后者运行时依赖本模块(T、typeValueToString),会构成环。
|
|
5
|
+
import { isTemplate } from "./refinements/template-predicates.ts";
|
|
3
6
|
|
|
4
|
-
// ---
|
|
7
|
+
// --- 评估 IR:不是类型系统本体,是 Abs 的外延投影格式 ---
|
|
5
8
|
|
|
6
|
-
export type LiteralValue = string | number | boolean | null | undefined;
|
|
9
|
+
export type LiteralValue = string | number | boolean | bigint | null | undefined;
|
|
10
|
+
|
|
11
|
+
// `thisVal` is the bound receiver when the signature is invoked as a method
|
|
12
|
+
// or through Function.prototype.call/apply (e.g. Object.prototype.toString
|
|
13
|
+
// brand computation needs it); direct calls leave it undefined.
|
|
14
|
+
export type SigImpl = (args: TypeValue[], thisVal?: TypeValue) => TypeValue | undefined;
|
|
15
|
+
|
|
16
|
+
export type FunctionSignature = {
|
|
17
|
+
paramTypes: TypeValue[];
|
|
18
|
+
returnType: TypeValue;
|
|
19
|
+
throwsType: TypeValue;
|
|
20
|
+
impl?: SigImpl;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export type Refinement = {
|
|
24
|
+
name: string;
|
|
25
|
+
meta: Record<string, unknown>;
|
|
26
|
+
check?: (value: unknown) => boolean;
|
|
27
|
+
ops?: Record<string, (self: TypeValue, other: TypeValue) => TypeValue | undefined>;
|
|
28
|
+
methods?: Record<string, (self: TypeValue, args: TypeValue[]) => TypeValue | undefined>;
|
|
29
|
+
properties?: Record<string, (self: TypeValue) => TypeValue | undefined>;
|
|
30
|
+
};
|
|
7
31
|
|
|
8
32
|
export type TypeValue =
|
|
9
33
|
| { kind: "literal"; value: LiteralValue }
|
|
@@ -11,6 +35,7 @@ export type TypeValue =
|
|
|
11
35
|
kind: "primitive";
|
|
12
36
|
type: "number" | "string" | "boolean" | "bigint" | "symbol";
|
|
13
37
|
}
|
|
38
|
+
| { kind: "refined"; base: TypeValue; refinement: Refinement }
|
|
14
39
|
| { kind: "object"; properties: Record<string, TypeValue>; id: symbol }
|
|
15
40
|
| { kind: "array"; element: TypeValue }
|
|
16
41
|
| { kind: "tuple"; elements: TypeValue[] }
|
|
@@ -68,6 +93,26 @@ export const T = {
|
|
|
68
93
|
body,
|
|
69
94
|
closure,
|
|
70
95
|
}),
|
|
96
|
+
refine: (base: TypeValue, refinement: Refinement): TypeValue => ({
|
|
97
|
+
kind: "refined",
|
|
98
|
+
base,
|
|
99
|
+
refinement,
|
|
100
|
+
}),
|
|
101
|
+
fnSig: (
|
|
102
|
+
paramTypes: TypeValue[],
|
|
103
|
+
returnType: TypeValue,
|
|
104
|
+
throwsType: TypeValue = { kind: "never" },
|
|
105
|
+
impl?: SigImpl,
|
|
106
|
+
): TypeValue => {
|
|
107
|
+
const dummy = {
|
|
108
|
+
kind: "function" as const,
|
|
109
|
+
params: paramTypes.map((_p, i) => `_arg${i}`),
|
|
110
|
+
body: { type: "BlockStatement", body: [], directives: [] } as unknown as Node,
|
|
111
|
+
closure: null as unknown as Environment,
|
|
112
|
+
};
|
|
113
|
+
(dummy as any)._signature = { paramTypes, returnType, throwsType, impl } satisfies FunctionSignature;
|
|
114
|
+
return dummy;
|
|
115
|
+
},
|
|
71
116
|
} as const;
|
|
72
117
|
|
|
73
118
|
// --- Helpers ---
|
|
@@ -78,6 +123,9 @@ export function typeValueEquals(a: TypeValue, b: TypeValue): boolean {
|
|
|
78
123
|
if (a.kind === "primitive" && b.kind === "primitive") return a.type === b.type;
|
|
79
124
|
if (a.kind === "never" && b.kind === "never") return true;
|
|
80
125
|
if (a.kind === "unknown" && b.kind === "unknown") return true;
|
|
126
|
+
if (a.kind === "refined" && b.kind === "refined") {
|
|
127
|
+
return a.refinement.name === b.refinement.name && typeValueEquals(a.base, b.base);
|
|
128
|
+
}
|
|
81
129
|
if (a.kind === "promise" && b.kind === "promise") {
|
|
82
130
|
return typeValueEquals(a.value, b.value);
|
|
83
131
|
}
|
|
@@ -94,6 +142,29 @@ export function typeValueEquals(a: TypeValue, b: TypeValue): boolean {
|
|
|
94
142
|
}
|
|
95
143
|
|
|
96
144
|
export function simplifyUnion(members: TypeValue[]): TypeValue {
|
|
145
|
+
// Fast path: empty or single element
|
|
146
|
+
if (members.length === 0) return T.never;
|
|
147
|
+
if (members.length === 1) return members[0].kind === "never" ? T.never : members[0];
|
|
148
|
+
|
|
149
|
+
// Fast path: two non-union elements, check equality first
|
|
150
|
+
if (members.length === 2) {
|
|
151
|
+
const a = members[0];
|
|
152
|
+
const b = members[1];
|
|
153
|
+
// Only use fast path if neither is a union (need flattening)
|
|
154
|
+
if (a.kind !== "union" && b.kind !== "union") {
|
|
155
|
+
if (a.kind === "never") return b.kind === "never" ? T.never : b;
|
|
156
|
+
if (b.kind === "never") return a;
|
|
157
|
+
if (typeValueEquals(a, b)) return a;
|
|
158
|
+
if (a.kind === "unknown" || b.kind === "unknown") return T.unknown;
|
|
159
|
+
// 吸收律:字面量被共存的 widening 基类型吸收(3 | number → number)
|
|
160
|
+
const aBase = absorbablePrimitiveBase(a);
|
|
161
|
+
if (aBase !== undefined && typeValueEquals(aBase, b)) return b;
|
|
162
|
+
const bBase = absorbablePrimitiveBase(b);
|
|
163
|
+
if (bBase !== undefined && typeValueEquals(bBase, a)) return a;
|
|
164
|
+
return { kind: "union", members: [a, b] };
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
97
168
|
const flat: TypeValue[] = [];
|
|
98
169
|
for (const m of members) {
|
|
99
170
|
if (m.kind === "never") continue;
|
|
@@ -111,10 +182,38 @@ export function simplifyUnion(members: TypeValue[]): TypeValue {
|
|
|
111
182
|
}
|
|
112
183
|
}
|
|
113
184
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
185
|
+
// 吸收律:成员中存在某字面量/模板字面量的 widening 基类型(非字面量
|
|
186
|
+
// number/string/boolean/bigint)时,删去该字面量成员(3 | number → number、
|
|
187
|
+
// "a" | string → string)。不同基底(1 | "a")、纯字面量联合("a" | "b")、
|
|
188
|
+
// null/undefined 字面量不受影响;filter 保序,成员顺序稳定。
|
|
189
|
+
const primitives = deduped.filter((m) => m.kind === "primitive");
|
|
190
|
+
const absorbed =
|
|
191
|
+
primitives.length > 0
|
|
192
|
+
? deduped.filter((m) => {
|
|
193
|
+
const base = absorbablePrimitiveBase(m);
|
|
194
|
+
return base === undefined || !primitives.some((p) => typeValueEquals(p, base));
|
|
195
|
+
})
|
|
196
|
+
: deduped;
|
|
197
|
+
|
|
198
|
+
if (absorbed.length === 0) return T.never;
|
|
199
|
+
if (absorbed.length === 1) return absorbed[0];
|
|
200
|
+
if (absorbed.some((m) => m.kind === "unknown")) return T.unknown;
|
|
201
|
+
return { kind: "union", members: absorbed };
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* 吸收律辅助:返回 tv 可被吸收进的原语基类型(number/string/boolean/bigint)。
|
|
206
|
+
* 仅数字/字符串/布尔/bigint 字面量与模板字面量(refined over T.string)有值;
|
|
207
|
+
* null/undefined 字面量 widen 后仍是字面量、基类型原语与其余类型不参与吸收
|
|
208
|
+
* (基类型自身不能被自己吸收,否则纯原语联合 number | string 会被误删)。
|
|
209
|
+
*/
|
|
210
|
+
function absorbablePrimitiveBase(tv: TypeValue): TypeValue | undefined {
|
|
211
|
+
if (tv.kind === "literal") {
|
|
212
|
+
const widened = widenLiteral(tv);
|
|
213
|
+
return widened.kind === "primitive" ? widened : undefined;
|
|
214
|
+
}
|
|
215
|
+
if (isTemplate(tv)) return T.string;
|
|
216
|
+
return undefined;
|
|
118
217
|
}
|
|
119
218
|
|
|
120
219
|
export function widenLiteral(tv: TypeValue): TypeValue {
|
|
@@ -123,11 +222,34 @@ export function widenLiteral(tv: TypeValue): TypeValue {
|
|
|
123
222
|
if (typeof v === "number") return T.number;
|
|
124
223
|
if (typeof v === "string") return T.string;
|
|
125
224
|
if (typeof v === "boolean") return T.boolean;
|
|
225
|
+
// 吸收律要求 bigint 字面量能 widen 到 T.bigint
|
|
226
|
+
if (typeof v === "bigint") return T.bigint;
|
|
126
227
|
if (v === null) return T.null;
|
|
127
228
|
if (v === undefined) return T.undefined;
|
|
128
229
|
return T.unknown;
|
|
129
230
|
}
|
|
130
231
|
|
|
232
|
+
/**
|
|
233
|
+
* union 成员全部为同原语基底的字面量且数量 > maxLiterals 时塌缩为该基类型;
|
|
234
|
+
* 非 union、混合类型、或数量不超限的原样返回。
|
|
235
|
+
*
|
|
236
|
+
* 复用 simplifyUnion(内含吸收律与去重):先让「基类型已在场」的字面量被吸收
|
|
237
|
+
* (3 | number → number),再对剩余的纯字面量联合做阈值坍缩(1|2|…|20 → number)。
|
|
238
|
+
* 两步不冲突也不重复:吸收只删基类型已在场的字面量,阈值坍缩只处理无基类型
|
|
239
|
+
* 在场的纯字面量联合。
|
|
240
|
+
*/
|
|
241
|
+
export function collapseLiteralUnion(tv: TypeValue, maxLiterals: number): TypeValue {
|
|
242
|
+
if (tv.kind !== "union") return tv;
|
|
243
|
+
if (tv.members.length <= maxLiterals) return tv;
|
|
244
|
+
const simplified = simplifyUnion(tv.members);
|
|
245
|
+
if (simplified.kind !== "union") return simplified;
|
|
246
|
+
// 保守:成员中含非字面量(如显式 T.number 未吸收掉的异构成员)时不塌缩
|
|
247
|
+
if (!simplified.members.every((m) => m.kind === "literal")) return simplified;
|
|
248
|
+
const first = widenLiteral(simplified.members[0]);
|
|
249
|
+
if (!simplified.members.every((m) => typeValueEquals(widenLiteral(m), first))) return simplified;
|
|
250
|
+
return first;
|
|
251
|
+
}
|
|
252
|
+
|
|
131
253
|
export function isSubtypeOf(a: TypeValue, b: TypeValue): boolean {
|
|
132
254
|
if (b.kind === "unknown") return true;
|
|
133
255
|
if (a.kind === "never") return true;
|
|
@@ -141,6 +263,18 @@ export function isSubtypeOf(a: TypeValue, b: TypeValue): boolean {
|
|
|
141
263
|
return false;
|
|
142
264
|
}
|
|
143
265
|
|
|
266
|
+
if (a.kind === "literal" && b.kind === "refined") {
|
|
267
|
+
if (!isSubtypeOf(a, b.base)) return false;
|
|
268
|
+
return b.refinement.check ? b.refinement.check(a.value) : false;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (a.kind === "refined") {
|
|
272
|
+
if (b.kind === "refined" && a.refinement.name === b.refinement.name) {
|
|
273
|
+
return isSubtypeOf(a.base, b.base);
|
|
274
|
+
}
|
|
275
|
+
return isSubtypeOf(a.base, b);
|
|
276
|
+
}
|
|
277
|
+
|
|
144
278
|
if (a.kind === "object" && b.kind === "object") {
|
|
145
279
|
return Object.entries(b.properties).every(
|
|
146
280
|
([k, bv]) => k in a.properties && isSubtypeOf(a.properties[k], bv),
|
|
@@ -226,6 +360,9 @@ export function deepCloneTypeValue(tv: TypeValue, idMap?: Map<symbol, symbol>):
|
|
|
226
360
|
}
|
|
227
361
|
return { kind: "instance", className: tv.className, properties: newProps };
|
|
228
362
|
}
|
|
363
|
+
if (tv.kind === "refined") {
|
|
364
|
+
return { kind: "refined", base: deepCloneTypeValue(tv.base, map), refinement: tv.refinement };
|
|
365
|
+
}
|
|
229
366
|
if (tv.kind === "union") {
|
|
230
367
|
return simplifyUnion(tv.members.map((m) => deepCloneTypeValue(m, map)));
|
|
231
368
|
}
|
|
@@ -251,6 +388,27 @@ export function mergeObjectProperties(
|
|
|
251
388
|
}
|
|
252
389
|
|
|
253
390
|
export function typeValueToString(tv: TypeValue): string {
|
|
391
|
+
// Self-referential structures (x.y = x surviving a clone) would recurse
|
|
392
|
+
// infinitely: a seen-set renders revisits as an ellipsis token.
|
|
393
|
+
return typeValueToStringInner(tv, new Set());
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
function typeValueToStringInner(tv: TypeValue, seen: Set<object>, depth = 0): string {
|
|
397
|
+
// Path-scoped seen-set: a node renders "…" only while its own expansion
|
|
398
|
+
// is on the stack (a true cycle); shared singletons (T.number in two
|
|
399
|
+
// slots) still render in full. A depth cap bounds fixture-scale DAGs
|
|
400
|
+
// whose repeated shared substructure would otherwise explode the string.
|
|
401
|
+
if (depth > 48) return "…";
|
|
402
|
+
const recur = (inner: TypeValue): string => {
|
|
403
|
+
if (inner && typeof inner === "object") {
|
|
404
|
+
if (seen.has(inner)) return "…";
|
|
405
|
+
seen.add(inner);
|
|
406
|
+
const out = typeValueToStringInner(inner, seen, depth + 1);
|
|
407
|
+
seen.delete(inner);
|
|
408
|
+
return out;
|
|
409
|
+
}
|
|
410
|
+
return typeValueToStringInner(inner, seen, depth + 1);
|
|
411
|
+
};
|
|
254
412
|
switch (tv.kind) {
|
|
255
413
|
case "literal": {
|
|
256
414
|
const v = tv.value;
|
|
@@ -261,36 +419,43 @@ export function typeValueToString(tv: TypeValue): string {
|
|
|
261
419
|
}
|
|
262
420
|
case "primitive":
|
|
263
421
|
return tv.type;
|
|
422
|
+
case "refined":
|
|
423
|
+
return tv.refinement.name;
|
|
264
424
|
case "object": {
|
|
265
425
|
const entries = Object.entries(tv.properties);
|
|
266
426
|
if (entries.length === 0) return "{}";
|
|
267
427
|
const inner = entries
|
|
268
|
-
.map(([k, v]) => `${k}: ${
|
|
428
|
+
.map(([k, v]) => `${k}: ${recur(v)}`)
|
|
269
429
|
.join(", ");
|
|
270
430
|
return `{ ${inner} }`;
|
|
271
431
|
}
|
|
272
432
|
case "array":
|
|
273
|
-
return `${
|
|
433
|
+
return `${recur(tv.element)}[]`;
|
|
274
434
|
case "tuple": {
|
|
275
|
-
const inner = tv.elements.map(
|
|
435
|
+
const inner = tv.elements.map(recur).join(", ");
|
|
276
436
|
return `[${inner}]`;
|
|
277
437
|
}
|
|
278
438
|
case "function": {
|
|
439
|
+
const sig = getFnSig(tv);
|
|
440
|
+
if (sig) {
|
|
441
|
+
const params = sig.paramTypes.map((p, i) => `${tv.params[i]}: ${recur(p)}`).join(", ");
|
|
442
|
+
return `(${params}) => ${recur(sig.returnType)}`;
|
|
443
|
+
}
|
|
279
444
|
const params = tv.params.join(", ");
|
|
280
445
|
return `(${params}) => ...`;
|
|
281
446
|
}
|
|
282
447
|
case "promise":
|
|
283
|
-
return `Promise<${
|
|
448
|
+
return `Promise<${recur(tv.value)}>`;
|
|
284
449
|
case "instance": {
|
|
285
450
|
const entries = Object.entries(tv.properties);
|
|
286
451
|
if (entries.length === 0) return tv.className;
|
|
287
452
|
const inner = entries
|
|
288
|
-
.map(([k, v]) => `${k}: ${
|
|
453
|
+
.map(([k, v]) => `${k}: ${recur(v)}`)
|
|
289
454
|
.join(", ");
|
|
290
455
|
return `${tv.className} { ${inner} }`;
|
|
291
456
|
}
|
|
292
457
|
case "union": {
|
|
293
|
-
return tv.members.map(
|
|
458
|
+
return tv.members.map(recur).join(" | ");
|
|
294
459
|
}
|
|
295
460
|
case "never":
|
|
296
461
|
return "never";
|
|
@@ -323,6 +488,7 @@ export function getPrimitiveTypeOf(tv: TypeValue): string | undefined {
|
|
|
323
488
|
return typeof v;
|
|
324
489
|
}
|
|
325
490
|
if (tv.kind === "primitive") return tv.type;
|
|
491
|
+
if (tv.kind === "refined") return getPrimitiveTypeOf(tv.base);
|
|
326
492
|
if (tv.kind === "object") return "object";
|
|
327
493
|
if (tv.kind === "array" || tv.kind === "tuple") return "object";
|
|
328
494
|
if (tv.kind === "function") return "function";
|
|
@@ -330,3 +496,18 @@ export function getPrimitiveTypeOf(tv: TypeValue): string | undefined {
|
|
|
330
496
|
if (tv.kind === "instance") return "object";
|
|
331
497
|
return undefined;
|
|
332
498
|
}
|
|
499
|
+
|
|
500
|
+
export function getRefinedBase(tv: TypeValue): TypeValue {
|
|
501
|
+
return tv.kind === "refined" ? getRefinedBase(tv.base) : tv;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
export function isFnSig(tv: TypeValue): boolean {
|
|
505
|
+
return tv.kind === "function" && "_signature" in tv;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
export function getFnSig(tv: TypeValue): FunctionSignature | undefined {
|
|
509
|
+
if (tv.kind === "function" && "_signature" in tv) {
|
|
510
|
+
return (tv as any)._signature as FunctionSignature;
|
|
511
|
+
}
|
|
512
|
+
return undefined;
|
|
513
|
+
}
|