@nudojs/core 0.3.0 → 0.4.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/dist/chunk-N7E2A7IK.js +7216 -0
- package/dist/exec.d.ts +2 -0
- package/dist/exec.js +174 -0
- package/dist/index-DgyudAkw.d.ts +911 -0
- package/dist/index.d.ts +64 -710
- package/dist/index.js +3269 -8426
- package/package.json +18 -5
- package/CHANGELOG.md +0 -34
- package/src/__tests__/environment.test.ts +0 -48
- package/src/__tests__/fn-sig-impl.test.ts +0 -57
- package/src/__tests__/ops-refined.test.ts +0 -196
- package/src/__tests__/ops.test.ts +0 -84
- package/src/__tests__/type-value-phase2.test.ts +0 -117
- package/src/__tests__/type-value-phase3.test.ts +0 -94
- package/src/__tests__/type-value-refined.test.ts +0 -210
- package/src/__tests__/type-value.test.ts +0 -319
- package/src/algebra/__tests__/abs-native.test.ts +0 -176
- package/src/algebra/__tests__/arithmetic.test.ts +0 -170
- package/src/algebra/__tests__/ast-eval.test.ts +0 -154
- package/src/algebra/__tests__/bridge.test.ts +0 -160
- package/src/algebra/__tests__/call-budget.test.ts +0 -117
- package/src/algebra/__tests__/check-case-consistency.test.ts +0 -69
- package/src/algebra/__tests__/check-gold.test.ts +0 -290
- package/src/algebra/__tests__/check-json.test.ts +0 -57
- package/src/algebra/__tests__/check-mini-repo.test.ts +0 -49
- package/src/algebra/__tests__/check-real-commander.test.ts +0 -43
- package/src/algebra/__tests__/check-real-packages.test.ts +0 -165
- package/src/algebra/__tests__/check-recall-gold.test.ts +0 -1024
- package/src/algebra/__tests__/check-require-dep-memo.test.ts +0 -45
- package/src/algebra/__tests__/check-return-contract.test.ts +0 -90
- package/src/algebra/__tests__/check-shape-gold.test.ts +0 -303
- package/src/algebra/__tests__/check-source-memo.test.ts +0 -204
- package/src/algebra/__tests__/check.test.ts +0 -69
- package/src/algebra/__tests__/denote.test.ts +0 -77
- package/src/algebra/__tests__/diagnostics.test.ts +0 -64
- package/src/algebra/__tests__/differential.test.ts +0 -124
- package/src/algebra/__tests__/div-mod.test.ts +0 -38
- package/src/algebra/__tests__/example0.test.ts +0 -103
- package/src/algebra/__tests__/exec-analyze-throws.test.ts +0 -60
- package/src/algebra/__tests__/exec-async.test.ts +0 -46
- package/src/algebra/__tests__/exec-bpath.test.ts +0 -193
- package/src/algebra/__tests__/exec-class.test.ts +0 -56
- package/src/algebra/__tests__/exec-e2e.test.ts +0 -97
- package/src/algebra/__tests__/exec-generator.test.ts +0 -53
- package/src/algebra/__tests__/exec-inherit-default.test.ts +0 -111
- package/src/algebra/__tests__/exec-optional-nested.test.ts +0 -87
- package/src/algebra/__tests__/exec-rest.test.ts +0 -55
- package/src/algebra/__tests__/exec-spread-forof.test.ts +0 -85
- package/src/algebra/__tests__/exec-static-computed.test.ts +0 -61
- package/src/algebra/__tests__/exec-switch.test.ts +0 -74
- package/src/algebra/__tests__/exec-trycatch.test.ts +0 -71
- package/src/algebra/__tests__/fn-fp-memo.test.ts +0 -166
- package/src/algebra/__tests__/fn-fp-multidecl.test.ts +0 -49
- package/src/algebra/__tests__/format.test.ts +0 -34
- package/src/algebra/__tests__/generalize-memo.test.ts +0 -285
- package/src/algebra/__tests__/generalize.test.ts +0 -113
- package/src/algebra/__tests__/hash-source.test.ts +0 -19
- package/src/algebra/__tests__/hof.test.ts +0 -80
- package/src/algebra/__tests__/inlay.test.ts +0 -30
- package/src/algebra/__tests__/language.test.ts +0 -77
- package/src/algebra/__tests__/leq.test.ts +0 -109
- package/src/algebra/__tests__/load-deps-fp-memo.test.ts +0 -132
- package/src/algebra/__tests__/methods.test.ts +0 -37
- package/src/algebra/__tests__/node-types.test.ts +0 -26
- package/src/algebra/__tests__/nudo-constraints.ts +0 -46
- package/src/algebra/__tests__/objects.test.ts +0 -117
- package/src/algebra/__tests__/parse-source-cache.test.ts +0 -42
- package/src/algebra/__tests__/refine-import.test.ts +0 -109
- package/src/algebra/__tests__/stable-source-key-block.test.ts +0 -23
- package/src/algebra/__tests__/stable-source-key.test.ts +0 -31
- package/src/algebra/__tests__/surface.test.ts +0 -41
- package/src/algebra/__tests__/this-inherit.test.ts +0 -130
- package/src/algebra/abs-fn.ts +0 -49
- package/src/algebra/abs-modules.ts +0 -139
- package/src/algebra/abs.ts +0 -206
- package/src/algebra/arithmetic.ts +0 -691
- package/src/algebra/ast-eval.ts +0 -1763
- package/src/algebra/bridge.ts +0 -350
- package/src/algebra/builtins.ts +0 -373
- package/src/algebra/check-report.ts +0 -150
- package/src/algebra/check.ts +0 -1867
- package/src/algebra/constraint.ts +0 -304
- package/src/algebra/denote.ts +0 -166
- package/src/algebra/diagnostics.ts +0 -210
- package/src/algebra/exec/call.ts +0 -29
- package/src/algebra/exec/calls.ts +0 -96
- package/src/algebra/exec/class-registry.ts +0 -29
- package/src/algebra/exec/class.ts +0 -329
- package/src/algebra/exec/index.ts +0 -6
- package/src/algebra/exec/member-diag.ts +0 -141
- package/src/algebra/exec/run.ts +0 -341
- package/src/algebra/exec/runtime.ts +0 -583
- package/src/algebra/exec/transpile.ts +0 -993
- package/src/algebra/fn-fp.ts +0 -294
- package/src/algebra/format.ts +0 -91
- package/src/algebra/generalize.ts +0 -776
- package/src/algebra/hash-source.ts +0 -27
- package/src/algebra/index.ts +0 -34
- package/src/algebra/inlay.ts +0 -198
- package/src/algebra/language.ts +0 -289
- package/src/algebra/leak.ts +0 -68
- package/src/algebra/leq.ts +0 -301
- package/src/algebra/load-deps-fp.ts +0 -97
- package/src/algebra/methods.ts +0 -128
- package/src/algebra/objects.ts +0 -251
- package/src/algebra/parse-source.ts +0 -64
- package/src/algebra/phi.ts +0 -42
- package/src/algebra/pred.ts +0 -395
- package/src/algebra/refine.ts +0 -227
- package/src/algebra/stable-source-key.ts +0 -81
- package/src/algebra/surface.ts +0 -206
- package/src/algebra/template.ts +0 -143
- package/src/algebra/term.ts +0 -119
- package/src/environment.ts +0 -74
- package/src/index.ts +0 -73
- package/src/mock-helpers.ts +0 -166
- package/src/ops.ts +0 -191
- package/src/refinements/range.ts +0 -86
- package/src/refinements/template-predicates.ts +0 -11
- package/src/refinements/template.ts +0 -176
- package/src/strip-types.ts +0 -236
- package/src/type-value.ts +0 -513
- package/tsconfig.json +0 -8
|
@@ -1,776 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 真·generalize:在新鲜类型变量 α 上执行用户函数,归纳多态签名,
|
|
3
|
-
* 并支持调用点实例化。
|
|
4
|
-
*
|
|
5
|
-
* - generalize 保留 term 结构(λα. α+1)
|
|
6
|
-
* - 调用点实例化把 α 换成具体 Abs 后再求值
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import { parseSource as babelParse } from "./parse-source.ts";
|
|
10
|
-
import type { Node } from "@babel/types";
|
|
11
|
-
import type { Term } from "./term.ts";
|
|
12
|
-
import { v as termVar, termToString } from "./term.ts";
|
|
13
|
-
import type { Phi, Pred } from "./pred.ts";
|
|
14
|
-
import { pTrue, predToString } from "./pred.ts";
|
|
15
|
-
import type { Abs, Shape } from "./abs.ts";
|
|
16
|
-
import { abs, unknown } from "./abs.ts";
|
|
17
|
-
import type { AstEnv } from "./ast-eval.ts";
|
|
18
|
-
import { evalNode, emptyEnv } from "./ast-eval.ts";
|
|
19
|
-
import { defaultLeakBudget, type LeakBudget } from "./leak.ts";
|
|
20
|
-
import { formatShape } from "./format.ts";
|
|
21
|
-
import {
|
|
22
|
-
extractRefinesFromSource,
|
|
23
|
-
type RefineResolveOpts,
|
|
24
|
-
} from "./refine.ts";
|
|
25
|
-
import { constraintToEntryAbs } from "./constraint.ts";
|
|
26
|
-
import { generalizeSourceKeyPart, resetFnFpCache } from "./fn-fp.ts";
|
|
27
|
-
import { resetHashSourceCache } from "./hash-source.ts";
|
|
28
|
-
import {
|
|
29
|
-
loadModuleDepsFingerprint,
|
|
30
|
-
normPath,
|
|
31
|
-
type LoadDepsFingerprint,
|
|
32
|
-
} from "./load-deps-fp.ts";
|
|
33
|
-
|
|
34
|
-
/** 进程内 L0:同 (source, fn, refine 指纹, budget, label) 的 generalize 结果 */
|
|
35
|
-
const generalizeMemo = new Map<string, PolyFn | undefined>();
|
|
36
|
-
/** key → 依赖的规范化路径(供定向逐出) */
|
|
37
|
-
const memoKeyDeps = new Map<string, string[]>();
|
|
38
|
-
/** 依赖路径 → keys */
|
|
39
|
-
const memoDepIndex = new Map<string, Set<string>>();
|
|
40
|
-
const loadModuleIds = new WeakMap<object, number>();
|
|
41
|
-
let nextLoadModuleId = 1;
|
|
42
|
-
|
|
43
|
-
/** L3:会话常驻 + 上限,超出按 LRU 逐出最旧条目 */
|
|
44
|
-
const MAX_GENERALIZE_MEMO = 1024;
|
|
45
|
-
|
|
46
|
-
export function resetGeneralizeMemo(): void {
|
|
47
|
-
generalizeMemo.clear();
|
|
48
|
-
memoKeyDeps.clear();
|
|
49
|
-
memoDepIndex.clear();
|
|
50
|
-
resetFnFpCache();
|
|
51
|
-
resetHashSourceCache();
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export function getGeneralizeMemoSize(): number {
|
|
55
|
-
return generalizeMemo.size;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
function unindexMemoKey(key: string): void {
|
|
59
|
-
const deps = memoKeyDeps.get(key);
|
|
60
|
-
if (!deps) return;
|
|
61
|
-
for (const p of deps) {
|
|
62
|
-
const set = memoDepIndex.get(p);
|
|
63
|
-
if (!set) continue;
|
|
64
|
-
set.delete(key);
|
|
65
|
-
if (set.size === 0) memoDepIndex.delete(p);
|
|
66
|
-
}
|
|
67
|
-
memoKeyDeps.delete(key);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* LSP/宿主:`*.nudo.js` 变更后按路径定向逐出依赖它的 L0 条目。
|
|
72
|
-
* 返回删除的条目数。路径需与 generalize 时 resolveDepPath 形态一致(建议先 norm)。
|
|
73
|
-
*/
|
|
74
|
-
export function evictGeneralizeMemoForPaths(paths: string[]): number {
|
|
75
|
-
let n = 0;
|
|
76
|
-
for (const raw of paths) {
|
|
77
|
-
const p = normPath(raw);
|
|
78
|
-
const keys = memoDepIndex.get(p);
|
|
79
|
-
if (!keys) continue;
|
|
80
|
-
for (const key of [...keys]) {
|
|
81
|
-
generalizeMemo.delete(key);
|
|
82
|
-
unindexMemoKey(key);
|
|
83
|
-
n++;
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
return n;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
function loadModuleId(fn?: (spec: string, fromFile: string) => string | undefined): number {
|
|
90
|
-
if (!fn) return 0;
|
|
91
|
-
let id = loadModuleIds.get(fn);
|
|
92
|
-
if (id === undefined) {
|
|
93
|
-
id = nextLoadModuleId++;
|
|
94
|
-
loadModuleIds.set(fn, id);
|
|
95
|
-
}
|
|
96
|
-
return id;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
type DepFingerprint = { fp: string; paths: string[]; truncated: boolean };
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* L3:loadModule 可达依赖内容指纹(与 check 整文件 memo 同一套 specs/传递规则)。
|
|
103
|
-
* 仅 @nudo:import 不够——body 求值会经 loadModule 读普通 require/from。
|
|
104
|
-
*/
|
|
105
|
-
function refineDepsFingerprint(source: string, refine?: RefineResolveOpts): DepFingerprint {
|
|
106
|
-
if (!refine?.loadModule || !refine.fromFile) {
|
|
107
|
-
return { fp: "-", paths: [], truncated: false };
|
|
108
|
-
}
|
|
109
|
-
return loadModuleDepsFingerprint(source, refine.loadModule, refine.fromFile);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
function generalizeMemoKey(
|
|
113
|
-
fnName: string,
|
|
114
|
-
source: string,
|
|
115
|
-
opts: {
|
|
116
|
-
budget?: LeakBudget;
|
|
117
|
-
label?: string;
|
|
118
|
-
refine?: RefineResolveOpts;
|
|
119
|
-
file?: ReturnType<typeof babelParse>;
|
|
120
|
-
/** checkSource 预计算:整文件一次指纹,所有 fn 的 L0 共用 */
|
|
121
|
-
depsFp?: LoadDepsFingerprint;
|
|
122
|
-
},
|
|
123
|
-
): { key: string; depPaths: string[]; truncated: boolean } {
|
|
124
|
-
const r = opts.refine;
|
|
125
|
-
const budget = opts.budget ?? defaultLeakBudget;
|
|
126
|
-
const deps =
|
|
127
|
-
opts.depsFp ??
|
|
128
|
-
(r ? refineDepsFingerprint(source, r) : { fp: "-", paths: [], truncated: false });
|
|
129
|
-
// AST 可用时用 per-function 指纹:改未引用的兄弟函数不 invalidate 本函数
|
|
130
|
-
const srcPart = generalizeSourceKeyPart(source, fnName, opts.file);
|
|
131
|
-
const key = [
|
|
132
|
-
srcPart,
|
|
133
|
-
fnName,
|
|
134
|
-
opts.label ?? "A",
|
|
135
|
-
r ? `${loadModuleId(r.loadModule)}:${r.fromFile ?? ""}` : "-",
|
|
136
|
-
deps.fp,
|
|
137
|
-
`${budget.maxDepth}/${budget.maxNodes}`,
|
|
138
|
-
].join("|");
|
|
139
|
-
return { key, depPaths: deps.paths, truncated: deps.truncated };
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
function generalizeMemoGet(key: string): PolyFn | undefined | null {
|
|
143
|
-
if (!generalizeMemo.has(key)) return null;
|
|
144
|
-
const v = generalizeMemo.get(key);
|
|
145
|
-
// LRU:命中后移到队尾
|
|
146
|
-
generalizeMemo.delete(key);
|
|
147
|
-
generalizeMemo.set(key, v);
|
|
148
|
-
return v;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
function generalizeMemoSet(
|
|
152
|
-
key: string,
|
|
153
|
-
value: PolyFn | undefined,
|
|
154
|
-
depPaths: string[],
|
|
155
|
-
): void {
|
|
156
|
-
if (generalizeMemo.size >= MAX_GENERALIZE_MEMO) {
|
|
157
|
-
const oldest = generalizeMemo.keys().next().value;
|
|
158
|
-
if (oldest !== undefined) {
|
|
159
|
-
generalizeMemo.delete(oldest);
|
|
160
|
-
unindexMemoKey(oldest);
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
generalizeMemo.set(key, value);
|
|
164
|
-
if (depPaths.length > 0) {
|
|
165
|
-
memoKeyDeps.set(key, depPaths);
|
|
166
|
-
for (const p of depPaths) {
|
|
167
|
-
let set = memoDepIndex.get(p);
|
|
168
|
-
if (!set) {
|
|
169
|
-
set = new Set();
|
|
170
|
-
memoDepIndex.set(p, set);
|
|
171
|
-
}
|
|
172
|
-
set.add(key);
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
/** L1/L2 只缓存完整可复用结果;截断/失败产生的 partial|opaque 不进缓存 */
|
|
178
|
-
function isCacheableAbs(a: Abs): boolean {
|
|
179
|
-
return (
|
|
180
|
-
a.conf === "exact" ||
|
|
181
|
-
a.conf === "path" ||
|
|
182
|
-
a.conf === "widened" ||
|
|
183
|
-
a.conf === "mock"
|
|
184
|
-
);
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
type VarRename = ReadonlyMap<string, string>;
|
|
188
|
-
|
|
189
|
-
function termKey(t: Term, rename?: VarRename): string {
|
|
190
|
-
switch (t.op) {
|
|
191
|
-
case "lit":
|
|
192
|
-
return `L:${typeof t.value}:${String(t.value)}`;
|
|
193
|
-
case "var":
|
|
194
|
-
return `V:${rename?.get(t.id) ?? t.id}`;
|
|
195
|
-
case "app":
|
|
196
|
-
return `A:${t.fn}(${t.args.map((a) => termKey(a, rename)).join(",")})`;
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
/** L2:and/or 子约束按键排序,交换律不造成 miss */
|
|
201
|
-
function predKey(p: Pred, rename?: VarRename): string {
|
|
202
|
-
switch (p.op) {
|
|
203
|
-
case "true":
|
|
204
|
-
return "T";
|
|
205
|
-
case "false":
|
|
206
|
-
return "F";
|
|
207
|
-
case "eq":
|
|
208
|
-
case "ne":
|
|
209
|
-
case "lt":
|
|
210
|
-
case "le":
|
|
211
|
-
case "gt":
|
|
212
|
-
case "ge":
|
|
213
|
-
return `${p.op}(${termKey(p.a, rename)},${termKey(p.b, rename)})`;
|
|
214
|
-
case "and":
|
|
215
|
-
case "or": {
|
|
216
|
-
const keys = p.args.map((a) => predKey(a, rename)).sort();
|
|
217
|
-
return `${p.op}(${keys.join(",")})`;
|
|
218
|
-
}
|
|
219
|
-
case "not":
|
|
220
|
-
return `not(${predKey(p.arg, rename)})`;
|
|
221
|
-
case "typeof":
|
|
222
|
-
return `typeof(${termKey(p.t, rename)},${p.type})`;
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
/** 结构键:shape + term + pred;不含 conf(置信度不参与语义输入) */
|
|
227
|
-
function shapeKey(s: Shape, seen: Set<object>, rename?: VarRename): string {
|
|
228
|
-
switch (s.k) {
|
|
229
|
-
case "never":
|
|
230
|
-
case "any":
|
|
231
|
-
case "unknown":
|
|
232
|
-
return s.k;
|
|
233
|
-
case "prim":
|
|
234
|
-
return `p:${s.type}`;
|
|
235
|
-
case "brand":
|
|
236
|
-
return `b:${s.name}(${absKeyInner(s.shape, seen, rename)})`;
|
|
237
|
-
case "eff":
|
|
238
|
-
return `e:${s.eff}<${absKeyInner(s.inner, seen, rename)}>`;
|
|
239
|
-
case "arr":
|
|
240
|
-
return `arr(${absKeyInner(s.element, seen, rename)})`;
|
|
241
|
-
case "tuple": {
|
|
242
|
-
const els = s.elements.map((e) => absKeyInner(e, seen, rename)).join(",");
|
|
243
|
-
const rest = s.rest ? `...${absKeyInner(s.rest, seen, rename)}` : "";
|
|
244
|
-
return `tup[${els}${rest}]`;
|
|
245
|
-
}
|
|
246
|
-
case "fn": {
|
|
247
|
-
const pts = (s.paramTypes ?? [])
|
|
248
|
-
.map((t) => absKeyInner(t, seen, rename))
|
|
249
|
-
.join(",");
|
|
250
|
-
const ret = s.returnType ? absKeyInner(s.returnType, seen, rename) : "?";
|
|
251
|
-
const name = s.name ? `#${s.name}` : "";
|
|
252
|
-
return `fn${name}(${s.params.join(",")}|${pts})=>${ret}`;
|
|
253
|
-
}
|
|
254
|
-
case "sum":
|
|
255
|
-
return `sum(${s.members.map((m) => absKeyInner(m, seen, rename)).join("|")})`;
|
|
256
|
-
case "obj": {
|
|
257
|
-
const slots = Object.keys(s.slots)
|
|
258
|
-
.sort()
|
|
259
|
-
.map((k) => {
|
|
260
|
-
const slot = s.slots[k]!;
|
|
261
|
-
const flags = (slot.optional ? "?" : "") + (slot.readonly ? "r" : "");
|
|
262
|
-
return `${k}${flags}:${absKeyInner(slot.value, seen, rename)}`;
|
|
263
|
-
})
|
|
264
|
-
.join(",");
|
|
265
|
-
const idx = s.index
|
|
266
|
-
? `idx(${absKeyInner(s.index.key, seen, rename)}→${absKeyInner(s.index.value, seen, rename)})`
|
|
267
|
-
: "";
|
|
268
|
-
const open = s.open ? "open" : "";
|
|
269
|
-
return `obj{${slots}}${idx}${open}`;
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
function absKeyInner(a: Abs, seen: Set<object>, rename?: VarRename): string {
|
|
275
|
-
if (seen.has(a)) return "cycle";
|
|
276
|
-
seen.add(a);
|
|
277
|
-
const t = a.term ? `=${termKey(a.term, rename)}` : "";
|
|
278
|
-
const p = a.pred ? `@${predKey(a.pred, rename)}` : "";
|
|
279
|
-
return `${shapeKey(a.shape, seen, rename)}${t}${p}`;
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
// --- free vars + α-rename (L2) ---
|
|
283
|
-
|
|
284
|
-
function collectTermVars(t: Term, acc: Set<string>): void {
|
|
285
|
-
if (t.op === "var") acc.add(t.id);
|
|
286
|
-
else if (t.op === "app") for (const a of t.args) collectTermVars(a, acc);
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
function collectPredVars(p: Pred, acc: Set<string>): void {
|
|
290
|
-
switch (p.op) {
|
|
291
|
-
case "true":
|
|
292
|
-
case "false":
|
|
293
|
-
return;
|
|
294
|
-
case "eq":
|
|
295
|
-
case "ne":
|
|
296
|
-
case "lt":
|
|
297
|
-
case "le":
|
|
298
|
-
case "gt":
|
|
299
|
-
case "ge":
|
|
300
|
-
collectTermVars(p.a, acc);
|
|
301
|
-
collectTermVars(p.b, acc);
|
|
302
|
-
return;
|
|
303
|
-
case "and":
|
|
304
|
-
case "or":
|
|
305
|
-
for (const a of p.args) collectPredVars(a, acc);
|
|
306
|
-
return;
|
|
307
|
-
case "not":
|
|
308
|
-
collectPredVars(p.arg, acc);
|
|
309
|
-
return;
|
|
310
|
-
case "typeof":
|
|
311
|
-
collectTermVars(p.t, acc);
|
|
312
|
-
return;
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
function collectAbsVars(a: Abs, acc: Set<string>, seen: Set<Abs>): void {
|
|
317
|
-
if (seen.has(a)) return;
|
|
318
|
-
seen.add(a);
|
|
319
|
-
if (a.term) collectTermVars(a.term, acc);
|
|
320
|
-
if (a.pred) collectPredVars(a.pred, acc);
|
|
321
|
-
const s = a.shape;
|
|
322
|
-
switch (s.k) {
|
|
323
|
-
case "brand":
|
|
324
|
-
collectAbsVars(s.shape, acc, seen);
|
|
325
|
-
return;
|
|
326
|
-
case "eff":
|
|
327
|
-
collectAbsVars(s.inner, acc, seen);
|
|
328
|
-
return;
|
|
329
|
-
case "arr":
|
|
330
|
-
collectAbsVars(s.element, acc, seen);
|
|
331
|
-
return;
|
|
332
|
-
case "tuple":
|
|
333
|
-
for (const e of s.elements) collectAbsVars(e, acc, seen);
|
|
334
|
-
if (s.rest) collectAbsVars(s.rest, acc, seen);
|
|
335
|
-
return;
|
|
336
|
-
case "fn":
|
|
337
|
-
for (const t of s.paramTypes ?? []) collectAbsVars(t, acc, seen);
|
|
338
|
-
if (s.returnType) collectAbsVars(s.returnType, acc, seen);
|
|
339
|
-
return;
|
|
340
|
-
case "sum":
|
|
341
|
-
for (const m of s.members) collectAbsVars(m, acc, seen);
|
|
342
|
-
return;
|
|
343
|
-
case "obj":
|
|
344
|
-
for (const slot of Object.values(s.slots)) collectAbsVars(slot.value, acc, seen);
|
|
345
|
-
if (s.index) {
|
|
346
|
-
collectAbsVars(s.index.key, acc, seen);
|
|
347
|
-
collectAbsVars(s.index.value, acc, seen);
|
|
348
|
-
}
|
|
349
|
-
return;
|
|
350
|
-
default:
|
|
351
|
-
return;
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
function renameTerm(t: Term, map: VarRename): Term {
|
|
356
|
-
if (t.op === "var") {
|
|
357
|
-
const to = map.get(t.id);
|
|
358
|
-
return to === undefined ? t : termVar(to);
|
|
359
|
-
}
|
|
360
|
-
if (t.op === "app") {
|
|
361
|
-
return { op: "app", fn: t.fn, args: t.args.map((a) => renameTerm(a, map)) };
|
|
362
|
-
}
|
|
363
|
-
return t;
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
function renamePred(p: Pred, map: VarRename): Pred {
|
|
367
|
-
switch (p.op) {
|
|
368
|
-
case "true":
|
|
369
|
-
case "false":
|
|
370
|
-
return p;
|
|
371
|
-
case "eq":
|
|
372
|
-
case "ne":
|
|
373
|
-
case "lt":
|
|
374
|
-
case "le":
|
|
375
|
-
case "gt":
|
|
376
|
-
case "ge":
|
|
377
|
-
return { op: p.op, a: renameTerm(p.a, map), b: renameTerm(p.b, map) };
|
|
378
|
-
case "and":
|
|
379
|
-
case "or":
|
|
380
|
-
return { op: p.op, args: p.args.map((a) => renamePred(a, map)) };
|
|
381
|
-
case "not":
|
|
382
|
-
return { op: "not", arg: renamePred(p.arg, map) };
|
|
383
|
-
case "typeof":
|
|
384
|
-
return { op: "typeof", t: renameTerm(p.t, map), type: p.type };
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
function renameAbs(a: Abs, map: VarRename): Abs {
|
|
389
|
-
const out: Abs = {
|
|
390
|
-
shape: renameShape(a.shape, map),
|
|
391
|
-
conf: a.conf,
|
|
392
|
-
};
|
|
393
|
-
if (a.term) out.term = renameTerm(a.term, map);
|
|
394
|
-
if (a.pred) out.pred = renamePred(a.pred, map);
|
|
395
|
-
return out;
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
function renameShape(s: Shape, map: VarRename): Shape {
|
|
399
|
-
switch (s.k) {
|
|
400
|
-
case "never":
|
|
401
|
-
case "any":
|
|
402
|
-
case "unknown":
|
|
403
|
-
case "prim":
|
|
404
|
-
return s;
|
|
405
|
-
case "brand":
|
|
406
|
-
return { k: "brand", name: s.name, shape: renameAbs(s.shape, map) };
|
|
407
|
-
case "eff":
|
|
408
|
-
return { k: "eff", eff: s.eff, inner: renameAbs(s.inner, map) };
|
|
409
|
-
case "arr":
|
|
410
|
-
return { k: "arr", element: renameAbs(s.element, map) };
|
|
411
|
-
case "tuple": {
|
|
412
|
-
const out: Shape = {
|
|
413
|
-
k: "tuple",
|
|
414
|
-
elements: s.elements.map((e) => renameAbs(e, map)),
|
|
415
|
-
};
|
|
416
|
-
if (s.rest) out.rest = renameAbs(s.rest, map);
|
|
417
|
-
return out;
|
|
418
|
-
}
|
|
419
|
-
case "fn": {
|
|
420
|
-
const out: Shape = { k: "fn", params: s.params };
|
|
421
|
-
if (s.name !== undefined) out.name = s.name;
|
|
422
|
-
if (s.paramTypes) out.paramTypes = s.paramTypes.map((t) => renameAbs(t, map));
|
|
423
|
-
if (s.returnType) out.returnType = renameAbs(s.returnType, map);
|
|
424
|
-
return out;
|
|
425
|
-
}
|
|
426
|
-
case "sum":
|
|
427
|
-
return { k: "sum", members: s.members.map((m) => renameAbs(m, map)) };
|
|
428
|
-
case "obj": {
|
|
429
|
-
const slots: Record<string, { value: Abs; optional?: boolean; readonly?: boolean }> = {};
|
|
430
|
-
for (const [k, slot] of Object.entries(s.slots)) {
|
|
431
|
-
slots[k] = {
|
|
432
|
-
value: renameAbs(slot.value, map),
|
|
433
|
-
...(slot.optional ? { optional: true } : {}),
|
|
434
|
-
...(slot.readonly ? { readonly: true } : {}),
|
|
435
|
-
};
|
|
436
|
-
}
|
|
437
|
-
const out: Shape = { k: "obj", slots };
|
|
438
|
-
if (s.index) {
|
|
439
|
-
out.index = { key: renameAbs(s.index.key, map), value: renameAbs(s.index.value, map) };
|
|
440
|
-
}
|
|
441
|
-
if (s.open) out.open = true;
|
|
442
|
-
return out;
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
type InstHit = { result: Abs; varOrder: string[] };
|
|
448
|
-
|
|
449
|
-
/**
|
|
450
|
-
* L2 键:args+Φ 中自由变元按 id 排序后 α-规范化(→ α0,α1,…)。
|
|
451
|
-
* 同构不同名(x+1 vs y+1)共享条目;命中时把结果变元改回当前名。
|
|
452
|
-
*/
|
|
453
|
-
function instantiateMemoKey(
|
|
454
|
-
args: Abs[],
|
|
455
|
-
phi: Phi,
|
|
456
|
-
): { key: string; varOrder: string[] } {
|
|
457
|
-
const acc = new Set<string>();
|
|
458
|
-
for (const a of args) collectAbsVars(a, acc, new Set());
|
|
459
|
-
collectPredVars(phi, acc);
|
|
460
|
-
const varOrder = [...acc].sort();
|
|
461
|
-
const rename = new Map(varOrder.map((id, i) => [id, `α${i}`]));
|
|
462
|
-
const key = `${args.map((a) => absKeyInner(a, new Set(), rename)).join(";")}#${predKey(phi, rename)}`;
|
|
463
|
-
return { key, varOrder };
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
function alphaRenameResult(
|
|
467
|
-
result: Abs,
|
|
468
|
-
fromOrder: string[],
|
|
469
|
-
toOrder: string[],
|
|
470
|
-
): Abs {
|
|
471
|
-
if (fromOrder.length !== toOrder.length) return result;
|
|
472
|
-
let same = true;
|
|
473
|
-
for (let i = 0; i < fromOrder.length; i++) {
|
|
474
|
-
if (fromOrder[i] !== toOrder[i]) {
|
|
475
|
-
same = false;
|
|
476
|
-
break;
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
|
-
if (same) return result;
|
|
480
|
-
const map = new Map<string, string>();
|
|
481
|
-
for (let i = 0; i < fromOrder.length; i++) {
|
|
482
|
-
map.set(fromOrder[i]!, toOrder[i]!);
|
|
483
|
-
}
|
|
484
|
-
return renameAbs(result, map);
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
export type TypeParam = {
|
|
488
|
-
id: string;
|
|
489
|
-
value: Abs;
|
|
490
|
-
};
|
|
491
|
-
|
|
492
|
-
export type PolyFn = {
|
|
493
|
-
name: string;
|
|
494
|
-
params: string[];
|
|
495
|
-
typeParams: TypeParam[];
|
|
496
|
-
instantiate: (args: Abs[], phi?: Phi) => Abs;
|
|
497
|
-
symbolic: Abs;
|
|
498
|
-
display: string;
|
|
499
|
-
/** 入口契约(@nudo:refine),供签名/inlay 展示 */
|
|
500
|
-
entryReqs?: Array<{ param: string; pred: import("./pred.ts").Pred }>;
|
|
501
|
-
};
|
|
502
|
-
|
|
503
|
-
function extractFn(
|
|
504
|
-
source: string,
|
|
505
|
-
fnName: string,
|
|
506
|
-
fileAst?: ReturnType<typeof babelParse>,
|
|
507
|
-
): { params: string[]; body: Node; env: AstEnv } | undefined {
|
|
508
|
-
const file = fileAst ?? babelParse(source);
|
|
509
|
-
const env = emptyEnv();
|
|
510
|
-
|
|
511
|
-
for (const stmt of file.program.body) {
|
|
512
|
-
// export function / export const = fn / export default function
|
|
513
|
-
let decl: Node = stmt;
|
|
514
|
-
if (stmt.type === "ExportNamedDeclaration" && stmt.declaration) {
|
|
515
|
-
decl = stmt.declaration;
|
|
516
|
-
}
|
|
517
|
-
if (stmt.type === "ExportDefaultDeclaration" && stmt.declaration) {
|
|
518
|
-
decl = stmt.declaration;
|
|
519
|
-
}
|
|
520
|
-
if (decl.type === "FunctionDeclaration" && decl.id) {
|
|
521
|
-
env.fns.set(decl.id.name, {
|
|
522
|
-
params: decl.params.map((p) => (p.type === "Identifier" ? p.name : "_")),
|
|
523
|
-
body: decl.body,
|
|
524
|
-
async: decl.async === true,
|
|
525
|
-
});
|
|
526
|
-
}
|
|
527
|
-
if (decl.type === "VariableDeclaration") {
|
|
528
|
-
for (const d of decl.declarations) {
|
|
529
|
-
if (
|
|
530
|
-
d.id.type === "Identifier" &&
|
|
531
|
-
d.init &&
|
|
532
|
-
(d.init.type === "ArrowFunctionExpression" ||
|
|
533
|
-
d.init.type === "FunctionExpression")
|
|
534
|
-
) {
|
|
535
|
-
const init = d.init as {
|
|
536
|
-
params: Array<{ type: string; name?: string }>;
|
|
537
|
-
body: Node;
|
|
538
|
-
async?: boolean;
|
|
539
|
-
};
|
|
540
|
-
env.fns.set(d.id.name, {
|
|
541
|
-
params: init.params.map((p) =>
|
|
542
|
-
p.type === "Identifier" ? (p.name ?? "_") : "_",
|
|
543
|
-
),
|
|
544
|
-
body: init.body,
|
|
545
|
-
async: init.async === true,
|
|
546
|
-
});
|
|
547
|
-
}
|
|
548
|
-
}
|
|
549
|
-
}
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
const fn = env.fns.get(fnName);
|
|
553
|
-
if (!fn) return undefined;
|
|
554
|
-
return { params: fn.params, body: fn.body, env };
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
export function generalizeFromAst(
|
|
558
|
-
fnName: string,
|
|
559
|
-
source: string,
|
|
560
|
-
opts: {
|
|
561
|
-
budget?: LeakBudget;
|
|
562
|
-
label?: string;
|
|
563
|
-
/** 传入则把 @nudo:refine 挂到入口 param Abs */
|
|
564
|
-
refine?: RefineResolveOpts;
|
|
565
|
-
/** 预解析 AST,避免 check 批量场景重复 parse */
|
|
566
|
-
file?: ReturnType<typeof babelParse>;
|
|
567
|
-
/** 预计算 load-deps 指纹(checkSource 整文件一次,避免 per-fn 重读) */
|
|
568
|
-
depsFp?: LoadDepsFingerprint;
|
|
569
|
-
} = {},
|
|
570
|
-
): PolyFn | undefined {
|
|
571
|
-
const { key, depPaths, truncated } = generalizeMemoKey(fnName, source, opts);
|
|
572
|
-
// 截断指纹不可信:不读也不写 L0
|
|
573
|
-
if (!truncated) {
|
|
574
|
-
const cached = generalizeMemoGet(key);
|
|
575
|
-
if (cached !== null) {
|
|
576
|
-
return cached;
|
|
577
|
-
}
|
|
578
|
-
}
|
|
579
|
-
const result = generalizeFromAstUncached(fnName, source, opts);
|
|
580
|
-
if (!truncated) {
|
|
581
|
-
generalizeMemoSet(key, result, depPaths);
|
|
582
|
-
}
|
|
583
|
-
return result;
|
|
584
|
-
}
|
|
585
|
-
|
|
586
|
-
function generalizeFromAstUncached(
|
|
587
|
-
fnName: string,
|
|
588
|
-
source: string,
|
|
589
|
-
opts: {
|
|
590
|
-
budget?: LeakBudget;
|
|
591
|
-
label?: string;
|
|
592
|
-
refine?: RefineResolveOpts;
|
|
593
|
-
file?: ReturnType<typeof babelParse>;
|
|
594
|
-
} = {},
|
|
595
|
-
): PolyFn | undefined {
|
|
596
|
-
const extracted = extractFn(source, fnName, opts.file);
|
|
597
|
-
if (!extracted) return undefined;
|
|
598
|
-
const { params, body, env } = extracted;
|
|
599
|
-
const budget = opts.budget ?? defaultLeakBudget;
|
|
600
|
-
const label = opts.label ?? "A";
|
|
601
|
-
|
|
602
|
-
const typeParams: TypeParam[] = params.map((p, i) => ({
|
|
603
|
-
id: `${label}${i + 1}`,
|
|
604
|
-
// 无约束参数 = any(任意 JS 值),不是 unknown(分析无信息)
|
|
605
|
-
value: abs({ k: "any" }, termVar(`${label}${i + 1}`), pTrue, "path"),
|
|
606
|
-
}));
|
|
607
|
-
|
|
608
|
-
let entryReqs: Array<{ param: string; pred: import("./pred.ts").Pred }> | undefined;
|
|
609
|
-
if (opts.refine) {
|
|
610
|
-
try {
|
|
611
|
-
const reqs = extractRefinesFromSource(source, fnName, opts.refine);
|
|
612
|
-
if (reqs.length > 0) {
|
|
613
|
-
entryReqs = reqs.map((r) => ({ param: r.param, pred: r.pred }));
|
|
614
|
-
for (const r of reqs) {
|
|
615
|
-
const idx = params.indexOf(r.param);
|
|
616
|
-
if (idx >= 0) {
|
|
617
|
-
// 契约挂入口:term 用真实参数名,pred 一并带上
|
|
618
|
-
typeParams[idx] = {
|
|
619
|
-
id: typeParams[idx]!.id,
|
|
620
|
-
value: constraintToEntryAbs(r.constraint, r.param),
|
|
621
|
-
};
|
|
622
|
-
}
|
|
623
|
-
}
|
|
624
|
-
}
|
|
625
|
-
} catch {
|
|
626
|
-
// refine 解析失败时退回 any
|
|
627
|
-
}
|
|
628
|
-
}
|
|
629
|
-
|
|
630
|
-
// L1+L2:同一 PolyFn 上按 (α-规范化 args, Φ) 缓存实例化。
|
|
631
|
-
// 随 L0 的 PolyFn 共享;resetGeneralizeMemo 一并丢弃。
|
|
632
|
-
const instMemo = new Map<string, InstHit>();
|
|
633
|
-
|
|
634
|
-
const run = (args: Abs[], phi: Phi = pTrue): Abs => {
|
|
635
|
-
const { key, varOrder } = instantiateMemoKey(args, phi);
|
|
636
|
-
const hit = instMemo.get(key);
|
|
637
|
-
if (hit !== undefined) {
|
|
638
|
-
return alphaRenameResult(hit.result, hit.varOrder, varOrder);
|
|
639
|
-
}
|
|
640
|
-
const local: AstEnv = { vars: new Map(env.vars), fns: env.fns };
|
|
641
|
-
params.forEach((p, i) => {
|
|
642
|
-
local.vars.set(p, args[i] ?? unknown);
|
|
643
|
-
});
|
|
644
|
-
const result = evalNode(body, local, phi, budget).value;
|
|
645
|
-
// 截断/失败结果不缓存,避免固化过宽或不稳定结论
|
|
646
|
-
if (isCacheableAbs(result)) {
|
|
647
|
-
instMemo.set(key, { result, varOrder });
|
|
648
|
-
}
|
|
649
|
-
return result;
|
|
650
|
-
};
|
|
651
|
-
|
|
652
|
-
const symbolic = run(
|
|
653
|
-
typeParams.map((t) => t.value),
|
|
654
|
-
// 入口契约进 Φ,让 body 内的单调性可传播
|
|
655
|
-
entryReqs && entryReqs.length > 0
|
|
656
|
-
? entryReqs.length === 1
|
|
657
|
-
? entryReqs[0]!.pred
|
|
658
|
-
: { op: "and", args: entryReqs.map((r) => r.pred) }
|
|
659
|
-
: pTrue,
|
|
660
|
-
);
|
|
661
|
-
|
|
662
|
-
return {
|
|
663
|
-
name: fnName,
|
|
664
|
-
params,
|
|
665
|
-
typeParams,
|
|
666
|
-
symbolic,
|
|
667
|
-
instantiate: (args, phi) => run(args, phi ?? pTrue),
|
|
668
|
-
display: formatPoly(fnName, params, typeParams, symbolic, entryReqs),
|
|
669
|
-
...(entryReqs ? { entryReqs } : {}),
|
|
670
|
-
};
|
|
671
|
-
}
|
|
672
|
-
|
|
673
|
-
function formatPoly(
|
|
674
|
-
name: string,
|
|
675
|
-
params: string[],
|
|
676
|
-
typeParams: TypeParam[],
|
|
677
|
-
symbolic: Abs,
|
|
678
|
-
entryReqs?: Array<{ param: string; pred: import("./pred.ts").Pred }>,
|
|
679
|
-
): string {
|
|
680
|
-
const reqByParam = new Map((entryReqs ?? []).map((r) => [r.param, r.pred]));
|
|
681
|
-
const ps = params
|
|
682
|
-
.map((p, i) => {
|
|
683
|
-
const id = typeParams[i]?.id ?? "unknown";
|
|
684
|
-
const pred = reqByParam.get(p);
|
|
685
|
-
if (pred && pred.op !== "true") {
|
|
686
|
-
return `${p}: ${id} where ${predToString(pred)}`;
|
|
687
|
-
}
|
|
688
|
-
return `${p}: ${id}`;
|
|
689
|
-
})
|
|
690
|
-
.join(", ");
|
|
691
|
-
const ret = formatShape(symbolic);
|
|
692
|
-
const termPart =
|
|
693
|
-
symbolic.term && symbolic.term.op !== "lit"
|
|
694
|
-
? ` = ${termToString(symbolic.term)}`
|
|
695
|
-
: "";
|
|
696
|
-
const predPart =
|
|
697
|
-
symbolic.pred && symbolic.pred.op !== "true"
|
|
698
|
-
? ` where ${predToString(symbolic.pred)}`
|
|
699
|
-
: "";
|
|
700
|
-
return `${name}: (${ps}) => ${ret}${termPart}${predPart}`;
|
|
701
|
-
}
|
|
702
|
-
|
|
703
|
-
export function generalizeAll(
|
|
704
|
-
source: string,
|
|
705
|
-
opts: { budget?: LeakBudget } = {},
|
|
706
|
-
): PolyFn[] {
|
|
707
|
-
const file = babelParse(source);
|
|
708
|
-
const names: string[] = [];
|
|
709
|
-
for (const stmt of file.program.body) {
|
|
710
|
-
if (stmt.type === "FunctionDeclaration" && stmt.id) names.push(stmt.id.name);
|
|
711
|
-
if (stmt.type === "VariableDeclaration") {
|
|
712
|
-
for (const d of stmt.declarations) {
|
|
713
|
-
if (
|
|
714
|
-
d.id.type === "Identifier" &&
|
|
715
|
-
d.init &&
|
|
716
|
-
(d.init.type === "ArrowFunctionExpression" ||
|
|
717
|
-
d.init.type === "FunctionExpression")
|
|
718
|
-
) {
|
|
719
|
-
names.push(d.id.name);
|
|
720
|
-
}
|
|
721
|
-
}
|
|
722
|
-
}
|
|
723
|
-
}
|
|
724
|
-
const out: PolyFn[] = [];
|
|
725
|
-
for (const n of names) {
|
|
726
|
-
const g = generalizeFromAst(n, source, opts);
|
|
727
|
-
if (g) out.push(g);
|
|
728
|
-
}
|
|
729
|
-
return out;
|
|
730
|
-
}
|
|
731
|
-
|
|
732
|
-
/** 列出源码中的顶层函数名 */
|
|
733
|
-
export function listFunctionNames(source: string): string[] {
|
|
734
|
-
const file = babelParse(source);
|
|
735
|
-
const names: string[] = [];
|
|
736
|
-
for (const stmt of file.program.body) {
|
|
737
|
-
if (stmt.type === "FunctionDeclaration" && stmt.id) names.push(stmt.id.name);
|
|
738
|
-
if (stmt.type === "VariableDeclaration") {
|
|
739
|
-
for (const d of stmt.declarations) {
|
|
740
|
-
if (
|
|
741
|
-
d.id.type === "Identifier" &&
|
|
742
|
-
d.init &&
|
|
743
|
-
(d.init.type === "ArrowFunctionExpression" ||
|
|
744
|
-
d.init.type === "FunctionExpression")
|
|
745
|
-
) {
|
|
746
|
-
names.push(d.id.name);
|
|
747
|
-
}
|
|
748
|
-
}
|
|
749
|
-
}
|
|
750
|
-
}
|
|
751
|
-
return names;
|
|
752
|
-
}
|
|
753
|
-
|
|
754
|
-
/**
|
|
755
|
-
* 按 assume 集合构造实参:被 assume 的参数给带约束的符号,其余 unknown。
|
|
756
|
-
*/
|
|
757
|
-
export function buildArgsFromAssume(
|
|
758
|
-
source: string,
|
|
759
|
-
fnName: string,
|
|
760
|
-
assumeIds: Set<string>,
|
|
761
|
-
): Abs[] {
|
|
762
|
-
const extracted = extractFn(source, fnName);
|
|
763
|
-
if (!extracted) return [];
|
|
764
|
-
return extracted.params.map((p) => {
|
|
765
|
-
if (assumeIds.has(p)) {
|
|
766
|
-
// 默认假设 >0;更细的 assume 由 CLI 拼 Phi
|
|
767
|
-
return abs(
|
|
768
|
-
{ k: "prim", type: "number" },
|
|
769
|
-
termVar(p),
|
|
770
|
-
{ op: "gt", a: termVar(p), b: { op: "lit", value: 0 } },
|
|
771
|
-
"path",
|
|
772
|
-
);
|
|
773
|
-
}
|
|
774
|
-
return abs({ k: "unknown" }, undefined, undefined, "partial");
|
|
775
|
-
});
|
|
776
|
-
}
|