@nudojs/core 1.0.1 → 1.1.1
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/README.md +9 -5
- package/dist/check-report-AtnAfY9U.d.ts +661 -0
- package/dist/chunk-CDH7QCGQ.js +15066 -0
- package/dist/chunk-US532PZA.js +0 -0
- package/dist/chunk-V2I7VXFZ.js +4923 -0
- package/dist/exec.d.ts +2 -1
- package/dist/exec.js +170 -15
- package/dist/hof-types-tAeoYvzY.d.ts +368 -0
- package/dist/index-B7Lb8FU6.d.ts +726 -0
- package/dist/index.d.ts +648 -1019
- package/dist/index.js +1851 -4875
- package/dist/internal.d.ts +550 -0
- package/dist/internal.js +587 -0
- package/package.json +13 -8
- package/dist/chunk-P3G2TODS.js +0 -7680
- package/dist/index-FITqyJ8v.d.ts +0 -761
|
@@ -0,0 +1,4923 @@
|
|
|
1
|
+
import {
|
|
2
|
+
$invoke,
|
|
3
|
+
$new,
|
|
4
|
+
abs,
|
|
5
|
+
and,
|
|
6
|
+
anyAbs,
|
|
7
|
+
app,
|
|
8
|
+
callTranspiledExportFull,
|
|
9
|
+
defaultLeakBudget,
|
|
10
|
+
emptyEnv,
|
|
11
|
+
eq,
|
|
12
|
+
evalExprAbs,
|
|
13
|
+
foldRequireSpecArg,
|
|
14
|
+
formalParamDisplayNames,
|
|
15
|
+
formalParamsFromNodes,
|
|
16
|
+
formatAbs,
|
|
17
|
+
formatShape,
|
|
18
|
+
formatShapeSlot,
|
|
19
|
+
freeIdentifiers,
|
|
20
|
+
ge,
|
|
21
|
+
getFnImpl,
|
|
22
|
+
getSlot,
|
|
23
|
+
gt,
|
|
24
|
+
joinAbs,
|
|
25
|
+
le,
|
|
26
|
+
lit,
|
|
27
|
+
litValue,
|
|
28
|
+
locateContractParam,
|
|
29
|
+
lt,
|
|
30
|
+
numLit,
|
|
31
|
+
or,
|
|
32
|
+
pTrue,
|
|
33
|
+
parseSource,
|
|
34
|
+
predToString,
|
|
35
|
+
primToTypeof,
|
|
36
|
+
ptypeof,
|
|
37
|
+
runTranspiledOptionsMemoKey,
|
|
38
|
+
snapshotAbs,
|
|
39
|
+
termToString,
|
|
40
|
+
tryRunTranspiled,
|
|
41
|
+
unknown,
|
|
42
|
+
v,
|
|
43
|
+
withExecPhi
|
|
44
|
+
} from "./chunk-CDH7QCGQ.js";
|
|
45
|
+
|
|
46
|
+
// src/algebra/constraint.ts
|
|
47
|
+
var SELF = "__nudo_self__";
|
|
48
|
+
function selfTerm() {
|
|
49
|
+
return v(SELF);
|
|
50
|
+
}
|
|
51
|
+
function getTerm(obj, key) {
|
|
52
|
+
return app("get", [obj, lit(key)]);
|
|
53
|
+
}
|
|
54
|
+
function lenTerm(t) {
|
|
55
|
+
return app("length", [t]);
|
|
56
|
+
}
|
|
57
|
+
function isConstraint(x) {
|
|
58
|
+
return !!x && typeof x === "object" && x.__nudoConstraint === true;
|
|
59
|
+
}
|
|
60
|
+
function isNudoConstraint(x) {
|
|
61
|
+
return isConstraint(x);
|
|
62
|
+
}
|
|
63
|
+
var intFlaggedBuilders = /* @__PURE__ */ new WeakSet();
|
|
64
|
+
function isIntFlag(c) {
|
|
65
|
+
return c.int === true || intFlaggedBuilders.has(c);
|
|
66
|
+
}
|
|
67
|
+
function makeBuilder(prim, preds, extra) {
|
|
68
|
+
const fields = extra?.fields;
|
|
69
|
+
const element = extra?.element;
|
|
70
|
+
const isInt = extra?.int;
|
|
71
|
+
const optional = extra?.optional;
|
|
72
|
+
const members = extra?.members;
|
|
73
|
+
const fnSlot = extra?.fn;
|
|
74
|
+
const base = {
|
|
75
|
+
__nudoConstraint: true,
|
|
76
|
+
...prim ? { prim } : {},
|
|
77
|
+
preds: [...preds],
|
|
78
|
+
...fields ? { fields } : {},
|
|
79
|
+
...element ? { element } : {},
|
|
80
|
+
...optional ? { isOptional: true } : {},
|
|
81
|
+
...members ? { members } : {},
|
|
82
|
+
...fnSlot ? { fn: fnSlot } : {}
|
|
83
|
+
};
|
|
84
|
+
const add = (p) => makeBuilder(prim, [...preds, p], extra);
|
|
85
|
+
const builder = Object.assign(
|
|
86
|
+
/* @__PURE__ */ Object.create(null),
|
|
87
|
+
base,
|
|
88
|
+
{
|
|
89
|
+
gt: (n) => add(gt(selfTerm(), lit(n))),
|
|
90
|
+
ge: (n) => add(ge(selfTerm(), lit(n))),
|
|
91
|
+
lt: (n) => add(lt(selfTerm(), lit(n))),
|
|
92
|
+
le: (n) => add(le(selfTerm(), lit(n))),
|
|
93
|
+
int: () => makeBuilder(prim ?? "number", preds, { ...extra, int: true }),
|
|
94
|
+
min: (n) => add(ge(lenTerm(selfTerm()), lit(n))),
|
|
95
|
+
max: (n) => add(le(lenTerm(selfTerm()), lit(n))),
|
|
96
|
+
length: (n) => add(and(ge(lenTerm(selfTerm()), lit(n)), le(lenTerm(selfTerm()), lit(n)))),
|
|
97
|
+
shift: (n) => {
|
|
98
|
+
if (!Number.isFinite(n))
|
|
99
|
+
throw new Error("nudo shift(): offset must be a finite number");
|
|
100
|
+
if (fields || element || members || fnSlot)
|
|
101
|
+
throw new Error("nudo shift(): only numeric scalar constraint chains are allowed (shape/array/union/fn are not supported)");
|
|
102
|
+
if (prim !== void 0 && prim !== "number")
|
|
103
|
+
throw new Error(`nudo shift(): only numeric chains are allowed (prim=${prim})`);
|
|
104
|
+
return makeBuilder(prim, shiftBoundPreds(preds, n), extra);
|
|
105
|
+
},
|
|
106
|
+
optional: () => makeBuilder(prim, preds, { ...extra, optional: true })
|
|
107
|
+
}
|
|
108
|
+
);
|
|
109
|
+
if (isInt) intFlaggedBuilders.add(builder);
|
|
110
|
+
return builder;
|
|
111
|
+
}
|
|
112
|
+
function shiftBoundPreds(preds, n) {
|
|
113
|
+
return preds.map((p) => {
|
|
114
|
+
if (p.op !== "gt" && p.op !== "ge" && p.op !== "lt" && p.op !== "le")
|
|
115
|
+
throw new Error(`nudo shift(): predicate ${p.op} is not supported (only gt/ge/lt/le constant bounds)`);
|
|
116
|
+
const { a, b } = p;
|
|
117
|
+
if (b.op !== "lit" || typeof b.value !== "number")
|
|
118
|
+
throw new Error("nudo shift(): the right-hand side of a constant bound must be a numeric literal");
|
|
119
|
+
if (termHasApp(a, "length") || termHasApp(b, "length"))
|
|
120
|
+
throw new Error("nudo shift(): length(...) bounds are not supported");
|
|
121
|
+
return { op: p.op, a, b: lit(b.value + n) };
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
function termHasApp(t, fn2) {
|
|
125
|
+
if (t.op !== "app") return false;
|
|
126
|
+
if (t.fn === fn2) return true;
|
|
127
|
+
return t.args.some((x) => termHasApp(x, fn2));
|
|
128
|
+
}
|
|
129
|
+
function number() {
|
|
130
|
+
return makeBuilder("number", []);
|
|
131
|
+
}
|
|
132
|
+
function string() {
|
|
133
|
+
return makeBuilder("string", []);
|
|
134
|
+
}
|
|
135
|
+
function boolean() {
|
|
136
|
+
return makeBuilder("boolean", []);
|
|
137
|
+
}
|
|
138
|
+
function any() {
|
|
139
|
+
return makeBuilder(void 0, []);
|
|
140
|
+
}
|
|
141
|
+
function array(item) {
|
|
142
|
+
return makeBuilder(void 0, [], {
|
|
143
|
+
element: asNestedConstraint(item, "array(item)")
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
function shape(fields) {
|
|
147
|
+
const mapped = {};
|
|
148
|
+
for (const [k, v2] of Object.entries(fields)) {
|
|
149
|
+
const constraint = asNestedConstraint(v2, `shape \u5B57\u6BB5 '${k}'`);
|
|
150
|
+
mapped[k] = {
|
|
151
|
+
constraint,
|
|
152
|
+
...constraint.isOptional ? { optional: true } : {}
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
return makeBuilder(void 0, [], { fields: mapped });
|
|
156
|
+
}
|
|
157
|
+
function toPlainConstraint(c) {
|
|
158
|
+
if (!isConstraint(c))
|
|
159
|
+
throw new Error("nudo: expected a constraint value (number()/string()/\u2026 or a combinator)");
|
|
160
|
+
return {
|
|
161
|
+
__nudoConstraint: true,
|
|
162
|
+
...c.prim ? { prim: c.prim } : {},
|
|
163
|
+
preds: [...c.preds],
|
|
164
|
+
...c.fields ? { fields: c.fields } : {},
|
|
165
|
+
...c.element ? { element: c.element } : {},
|
|
166
|
+
// isIntFlag 统一读取:builder(int 是方法)查 WeakSet,纯数据看 === true
|
|
167
|
+
...isIntFlag(c) ? { int: true } : {},
|
|
168
|
+
...c.isOptional ? { isOptional: true } : {},
|
|
169
|
+
...c.members ? { members: c.members } : {},
|
|
170
|
+
...c.fn ? { fn: c.fn } : {}
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
function litC(v2) {
|
|
174
|
+
const prim = typeof v2 === "number" ? "number" : typeof v2 === "string" ? "string" : typeof v2 === "boolean" ? "boolean" : void 0;
|
|
175
|
+
return makeBuilder(prim, [eq(selfTerm(), lit(v2))]);
|
|
176
|
+
}
|
|
177
|
+
function asNestedConstraint(x, ctx) {
|
|
178
|
+
if (isConstraint(x)) return toPlainConstraint(x);
|
|
179
|
+
if (x === null || x === void 0 || typeof x === "number" || typeof x === "string" || typeof x === "boolean") {
|
|
180
|
+
return toPlainConstraint(litC(x));
|
|
181
|
+
}
|
|
182
|
+
throw new Error(
|
|
183
|
+
`nudo: ${ctx} expects a constraint value (number()/string()/\u2026) or a concrete literal; received a non-constraint`
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
function union(...cs) {
|
|
187
|
+
if (cs.length === 0)
|
|
188
|
+
throw new Error("nudo union(): at least one member constraint is required");
|
|
189
|
+
return makeBuilder(void 0, [], {
|
|
190
|
+
members: cs.map((c) => asNestedConstraint(c, "union member"))
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
function fn(params, returns, opts) {
|
|
194
|
+
const normalized = {};
|
|
195
|
+
for (const [k, v2] of Object.entries(params)) {
|
|
196
|
+
normalized[k] = asNestedConstraint(v2, `fn param '${k}'`);
|
|
197
|
+
}
|
|
198
|
+
return makeBuilder(void 0, [], {
|
|
199
|
+
fn: {
|
|
200
|
+
params: normalized,
|
|
201
|
+
...returns !== void 0 ? { returns: asNestedConstraint(returns, "fn return value") } : {},
|
|
202
|
+
...opts?.throws !== void 0 ? { throws: asNestedConstraint(opts.throws, "fn throws") } : {}
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
function andC(...cs) {
|
|
207
|
+
if (cs.length === 0)
|
|
208
|
+
throw new Error("nudo and(): at least one constraint is required");
|
|
209
|
+
let prim;
|
|
210
|
+
let isInt = false;
|
|
211
|
+
let allOptional = true;
|
|
212
|
+
const preds = [];
|
|
213
|
+
for (const c of cs) {
|
|
214
|
+
if (!isConstraint(c))
|
|
215
|
+
throw new Error("nudo: expected a constraint value (number()/string()/\u2026 or a combinator)");
|
|
216
|
+
if (c.fields || c.element || c.members || c.fn)
|
|
217
|
+
throw new Error("nudo and(): Phase 1 supports scalar constraint conjunction only (shape/array/union/fn are not supported)");
|
|
218
|
+
if (c.prim) {
|
|
219
|
+
if (prim !== void 0 && prim !== c.prim)
|
|
220
|
+
throw new Error(`nudo and(): inconsistent prim (${prim} vs ${c.prim})`);
|
|
221
|
+
prim = c.prim;
|
|
222
|
+
}
|
|
223
|
+
preds.push(...c.preds);
|
|
224
|
+
if (isIntFlag(c)) isInt = true;
|
|
225
|
+
if (!c.isOptional) allOptional = false;
|
|
226
|
+
}
|
|
227
|
+
return makeBuilder(prim, preds, {
|
|
228
|
+
...isInt ? { int: true } : {},
|
|
229
|
+
...allOptional ? { optional: true } : {}
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
function partial(c) {
|
|
233
|
+
if (!isConstraint(c) || !c.fields)
|
|
234
|
+
throw new Error("nudo partial(): only shape(...) constraints are accepted");
|
|
235
|
+
const fields = {};
|
|
236
|
+
for (const [k, f] of Object.entries(c.fields)) {
|
|
237
|
+
fields[k] = {
|
|
238
|
+
constraint: { ...toPlainConstraint(f.constraint), isOptional: true },
|
|
239
|
+
optional: true
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
return makeBuilder(c.prim, c.preds, { fields });
|
|
243
|
+
}
|
|
244
|
+
function pick(c, keys) {
|
|
245
|
+
if (!isConstraint(c) || !c.fields)
|
|
246
|
+
throw new Error("nudo pick(): only shape(...) constraints are accepted");
|
|
247
|
+
const fields = {};
|
|
248
|
+
for (const k of keys) {
|
|
249
|
+
const f = c.fields[k];
|
|
250
|
+
if (f) fields[k] = f;
|
|
251
|
+
}
|
|
252
|
+
return makeBuilder(c.prim, c.preds, { fields });
|
|
253
|
+
}
|
|
254
|
+
function omit(c, keys) {
|
|
255
|
+
if (!isConstraint(c) || !c.fields)
|
|
256
|
+
throw new Error("nudo omit(): only shape(...) constraints are accepted");
|
|
257
|
+
const drop = new Set(keys);
|
|
258
|
+
const fields = {};
|
|
259
|
+
for (const [k, f] of Object.entries(c.fields)) {
|
|
260
|
+
if (!drop.has(k)) fields[k] = f;
|
|
261
|
+
}
|
|
262
|
+
return makeBuilder(c.prim, c.preds, { fields });
|
|
263
|
+
}
|
|
264
|
+
function substTerm(t, paramName) {
|
|
265
|
+
if (t.op === "var" && t.id === SELF) return v(paramName);
|
|
266
|
+
if (t.op === "app" && t.fn === "get" && t.args.length === 2) {
|
|
267
|
+
return getTerm(substTerm(t.args[0], paramName), String(
|
|
268
|
+
t.args[1].op === "lit" ? t.args[1].value : ""
|
|
269
|
+
));
|
|
270
|
+
}
|
|
271
|
+
return t;
|
|
272
|
+
}
|
|
273
|
+
function substPred(p, paramName) {
|
|
274
|
+
switch (p.op) {
|
|
275
|
+
case "gt":
|
|
276
|
+
case "ge":
|
|
277
|
+
case "lt":
|
|
278
|
+
case "le":
|
|
279
|
+
case "eq":
|
|
280
|
+
case "ne": {
|
|
281
|
+
return { op: p.op, a: substTerm(p.a, paramName), b: substTerm(p.b, paramName) };
|
|
282
|
+
}
|
|
283
|
+
case "typeof": {
|
|
284
|
+
return { op: "typeof", t: substTerm(p.t, paramName), type: p.type };
|
|
285
|
+
}
|
|
286
|
+
case "and":
|
|
287
|
+
return and(...p.args.map((x) => substPred(x, paramName)));
|
|
288
|
+
default:
|
|
289
|
+
return p;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
function instantiateConstraint(c, paramName) {
|
|
293
|
+
if (c.fields) {
|
|
294
|
+
const parts = [];
|
|
295
|
+
for (const [key, field] of Object.entries(c.fields)) {
|
|
296
|
+
if (field.optional || field.constraint.isOptional) continue;
|
|
297
|
+
const fieldTerm = getTerm(v(paramName), key);
|
|
298
|
+
parts.push(instantiateOnTerm(field.constraint, fieldTerm));
|
|
299
|
+
}
|
|
300
|
+
if (c.prim) parts.push(ptypeof(v(paramName), primToTypeof(c.prim)));
|
|
301
|
+
if (parts.length === 0) return { op: "true" };
|
|
302
|
+
return parts.length === 1 ? parts[0] : and(...parts);
|
|
303
|
+
}
|
|
304
|
+
if (c.members) {
|
|
305
|
+
const disj = or(
|
|
306
|
+
...c.members.map((m) => instantiateOnTerm(m, v(paramName)))
|
|
307
|
+
);
|
|
308
|
+
const own = c.preds.map((p) => substPred(p, paramName));
|
|
309
|
+
return own.length === 0 ? disj : and(...own, disj);
|
|
310
|
+
}
|
|
311
|
+
if (c.fn) {
|
|
312
|
+
const own = c.preds.map((p) => substPred(p, paramName));
|
|
313
|
+
return own.length === 0 ? pTrue : and(...own);
|
|
314
|
+
}
|
|
315
|
+
const preds = c.preds.map((p) => substPred(p, paramName));
|
|
316
|
+
if (c.prim && c.preds.length === 0) {
|
|
317
|
+
return ptypeof(v(paramName), primToTypeof(c.prim));
|
|
318
|
+
}
|
|
319
|
+
return preds.length === 0 ? { op: "true" } : preds.length === 1 ? preds[0] : and(...preds);
|
|
320
|
+
}
|
|
321
|
+
function instantiateOnTerm(c, t) {
|
|
322
|
+
const subst = (p) => {
|
|
323
|
+
switch (p.op) {
|
|
324
|
+
case "gt":
|
|
325
|
+
case "ge":
|
|
326
|
+
case "lt":
|
|
327
|
+
case "le":
|
|
328
|
+
case "eq":
|
|
329
|
+
case "ne": {
|
|
330
|
+
const a = p.a.op === "var" && p.a.id === SELF ? t : p.a;
|
|
331
|
+
const b = p.b.op === "var" && p.b.id === SELF ? t : p.b;
|
|
332
|
+
return { op: p.op, a, b };
|
|
333
|
+
}
|
|
334
|
+
case "typeof": {
|
|
335
|
+
const tt = p.t.op === "var" && p.t.id === SELF ? t : p.t;
|
|
336
|
+
return { op: "typeof", t: tt, type: p.type };
|
|
337
|
+
}
|
|
338
|
+
case "and":
|
|
339
|
+
return and(...p.args.map(subst));
|
|
340
|
+
default:
|
|
341
|
+
return p;
|
|
342
|
+
}
|
|
343
|
+
};
|
|
344
|
+
if (c.fields) {
|
|
345
|
+
const parts = [];
|
|
346
|
+
for (const [key, field] of Object.entries(c.fields)) {
|
|
347
|
+
if (field.optional || field.constraint.isOptional) continue;
|
|
348
|
+
parts.push(instantiateOnTerm(field.constraint, getTerm(t, key)));
|
|
349
|
+
}
|
|
350
|
+
if (parts.length === 0) return { op: "true" };
|
|
351
|
+
return parts.length === 1 ? parts[0] : and(...parts);
|
|
352
|
+
}
|
|
353
|
+
if (c.members) {
|
|
354
|
+
const disj = or(...c.members.map((m) => instantiateOnTerm(m, t)));
|
|
355
|
+
const own = c.preds.map(subst);
|
|
356
|
+
return own.length === 0 ? disj : and(...own, disj);
|
|
357
|
+
}
|
|
358
|
+
if (c.fn) {
|
|
359
|
+
const own = c.preds.map(subst);
|
|
360
|
+
return own.length === 0 ? pTrue : and(...own);
|
|
361
|
+
}
|
|
362
|
+
const preds = c.preds.map(subst);
|
|
363
|
+
if (c.prim && c.preds.length === 0) return ptypeof(t, primToTypeof(c.prim));
|
|
364
|
+
return preds.length === 0 ? { op: "true" } : preds.length === 1 ? preds[0] : and(...preds);
|
|
365
|
+
}
|
|
366
|
+
function constraintToEntryAbs(c, paramName) {
|
|
367
|
+
const t = v(paramName);
|
|
368
|
+
if (c.fields) {
|
|
369
|
+
const slots = {};
|
|
370
|
+
for (const [key, field] of Object.entries(c.fields)) {
|
|
371
|
+
const fieldTerm = getTerm(t, key);
|
|
372
|
+
slots[key] = {
|
|
373
|
+
value: constraintOnTermAbs(field.constraint, fieldTerm),
|
|
374
|
+
...field.optional || field.constraint.isOptional ? { optional: true } : {}
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
return abs({ k: "obj", slots }, t, void 0, "path");
|
|
378
|
+
}
|
|
379
|
+
return constraintOnTermAbs(c, t);
|
|
380
|
+
}
|
|
381
|
+
function memberLitValue(m) {
|
|
382
|
+
const leaves = [];
|
|
383
|
+
const visit2 = (p2) => {
|
|
384
|
+
if (p2.op === "and") {
|
|
385
|
+
p2.args.forEach(visit2);
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
leaves.push(p2);
|
|
389
|
+
};
|
|
390
|
+
m.preds.forEach(visit2);
|
|
391
|
+
if (leaves.length !== 1) return void 0;
|
|
392
|
+
const p = leaves[0];
|
|
393
|
+
if (p.op !== "eq") return void 0;
|
|
394
|
+
if (p.a.op === "var" && p.b.op === "lit") return p.b.value;
|
|
395
|
+
if (p.b.op === "var" && p.a.op === "lit") return p.a.value;
|
|
396
|
+
return void 0;
|
|
397
|
+
}
|
|
398
|
+
function samePrimLiteralUnionAbs(members, t) {
|
|
399
|
+
if (members.length === 0) return void 0;
|
|
400
|
+
const values = [];
|
|
401
|
+
let prim;
|
|
402
|
+
for (const m of members) {
|
|
403
|
+
const v2 = memberLitValue(m);
|
|
404
|
+
if (v2 === void 0) return void 0;
|
|
405
|
+
if (v2 === null) return void 0;
|
|
406
|
+
const mp = m.prim ?? (typeof v2 === "number" ? "number" : typeof v2 === "string" ? "string" : "boolean");
|
|
407
|
+
if (prim === void 0) prim = mp;
|
|
408
|
+
else if (prim !== mp) return void 0;
|
|
409
|
+
values.push(v2);
|
|
410
|
+
}
|
|
411
|
+
if (prim === void 0) return void 0;
|
|
412
|
+
const uniq = [];
|
|
413
|
+
for (const v2 of values) {
|
|
414
|
+
if (!uniq.some((u) => Object.is(u, v2))) uniq.push(v2);
|
|
415
|
+
}
|
|
416
|
+
const disj = or(
|
|
417
|
+
...uniq.map((v2) => eq(t, lit(v2)))
|
|
418
|
+
);
|
|
419
|
+
return abs({ k: "prim", type: prim }, t, disj, "path");
|
|
420
|
+
}
|
|
421
|
+
function constraintOnTermAbs(c, t) {
|
|
422
|
+
if (c.fields) {
|
|
423
|
+
const slots = {};
|
|
424
|
+
for (const [key, field] of Object.entries(c.fields)) {
|
|
425
|
+
slots[key] = {
|
|
426
|
+
value: constraintOnTermAbs(field.constraint, getTerm(t, key)),
|
|
427
|
+
...field.optional || field.constraint.isOptional ? { optional: true } : {}
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
return abs({ k: "obj", slots }, t, void 0, "path");
|
|
431
|
+
}
|
|
432
|
+
if (c.members) {
|
|
433
|
+
const litUnion = samePrimLiteralUnionAbs(c.members, t);
|
|
434
|
+
if (litUnion) return litUnion;
|
|
435
|
+
const joined = c.members.map((m) => constraintOnTermAbs(m, t)).reduce((a, b) => joinAbs(a, b));
|
|
436
|
+
if (joined.shape.k === "prim" && joined.pred === void 0 && c.members.every((m) => m.prim === joined.shape.type)) {
|
|
437
|
+
const type = joined.shape.type;
|
|
438
|
+
return abs({ k: "prim", type }, t, ptypeof(t, primToTypeof(type)), "path");
|
|
439
|
+
}
|
|
440
|
+
return joined;
|
|
441
|
+
}
|
|
442
|
+
if (c.fn) {
|
|
443
|
+
const paramNames = Object.keys(c.fn.params);
|
|
444
|
+
const paramTypes = paramNames.map(
|
|
445
|
+
(p, i) => constraintOnTermAbs(c.fn.params[p], v(`x${i}`))
|
|
446
|
+
);
|
|
447
|
+
const returnType = c.fn.returns ? constraintOnTermAbs(c.fn.returns, v("ret")) : unknown;
|
|
448
|
+
return abs(
|
|
449
|
+
{ k: "fn", params: paramNames, paramTypes, returnType },
|
|
450
|
+
t,
|
|
451
|
+
void 0,
|
|
452
|
+
"path"
|
|
453
|
+
);
|
|
454
|
+
}
|
|
455
|
+
if (c.element) {
|
|
456
|
+
const elem = constraintOnTermAbs(c.element, v("x[]"));
|
|
457
|
+
return abs({ k: "arr", element: elem }, t, void 0, "path");
|
|
458
|
+
}
|
|
459
|
+
const pred = instantiateOnTerm(c, t);
|
|
460
|
+
const predOut = pred.op === "true" ? void 0 : pred;
|
|
461
|
+
if (c.prim) {
|
|
462
|
+
return abs({ k: "prim", type: c.prim }, t, predOut, "path");
|
|
463
|
+
}
|
|
464
|
+
const allEqNull = c.preds.length > 0 && c.preds.every(
|
|
465
|
+
(p) => p.op === "eq" && (p.b.op === "lit" && p.b.value === null || p.a.op === "lit" && p.a.value === null)
|
|
466
|
+
);
|
|
467
|
+
if (allEqNull) {
|
|
468
|
+
return abs({ k: "unknown" }, t, predOut, "path");
|
|
469
|
+
}
|
|
470
|
+
if (c.preds.length > 0) {
|
|
471
|
+
return abs({ k: "prim", type: "number" }, t, predOut, "path");
|
|
472
|
+
}
|
|
473
|
+
return abs({ k: "any" }, t, void 0, "path");
|
|
474
|
+
}
|
|
475
|
+
function fnConstraintToEntryReqs(c) {
|
|
476
|
+
if (!c.fn) throw new Error("nudo fnConstraintToEntryReqs(): constraint is not in fn() form");
|
|
477
|
+
return Object.entries(c.fn.params).map(([param, constraint]) => ({
|
|
478
|
+
param,
|
|
479
|
+
constraint
|
|
480
|
+
}));
|
|
481
|
+
}
|
|
482
|
+
function throwConstraintToKinds(c) {
|
|
483
|
+
if (!c) return void 0;
|
|
484
|
+
const out = /* @__PURE__ */ new Set();
|
|
485
|
+
let any2 = false;
|
|
486
|
+
const note = (s) => {
|
|
487
|
+
if (s === "*" || s === "any") any2 = true;
|
|
488
|
+
else if (s) out.add(s);
|
|
489
|
+
};
|
|
490
|
+
const walk = (x) => {
|
|
491
|
+
if (x.members) {
|
|
492
|
+
for (const m of x.members) walk(m);
|
|
493
|
+
return;
|
|
494
|
+
}
|
|
495
|
+
for (const p of x.preds) {
|
|
496
|
+
if (p.op !== "eq" && p.op !== "ne") continue;
|
|
497
|
+
for (const side of [p.a, p.b]) {
|
|
498
|
+
if (side.op === "lit" && typeof side.value === "string") note(side.value);
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
};
|
|
502
|
+
walk(c);
|
|
503
|
+
if (any2) return "*";
|
|
504
|
+
if (out.size > 0) return [...out];
|
|
505
|
+
return void 0;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
// src/algebra/hash-source.ts
|
|
509
|
+
var lastHashSource;
|
|
510
|
+
var lastHashOut;
|
|
511
|
+
function hashSource(s) {
|
|
512
|
+
if (s === lastHashSource && lastHashOut !== void 0) return lastHashOut;
|
|
513
|
+
let h1 = 2166136261;
|
|
514
|
+
let h2 = 2654435769;
|
|
515
|
+
for (let i = 0; i < s.length; i++) {
|
|
516
|
+
const c = s.charCodeAt(i);
|
|
517
|
+
h1 ^= c;
|
|
518
|
+
h1 = Math.imul(h1, 16777619);
|
|
519
|
+
h2 = Math.imul(h2 ^ c, 16777619);
|
|
520
|
+
}
|
|
521
|
+
lastHashSource = s;
|
|
522
|
+
lastHashOut = `${(h1 >>> 0).toString(36)}-${(h2 >>> 0).toString(36)}_${s.length.toString(36)}`;
|
|
523
|
+
return lastHashOut;
|
|
524
|
+
}
|
|
525
|
+
function resetHashSourceCache() {
|
|
526
|
+
lastHashSource = void 0;
|
|
527
|
+
lastHashOut = void 0;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
// src/algebra/sidecar-path.ts
|
|
531
|
+
function normPath(p) {
|
|
532
|
+
return p.replace(/\\/g, "/");
|
|
533
|
+
}
|
|
534
|
+
function resolveDepPath(fromFile, spec) {
|
|
535
|
+
const s = normPath(spec);
|
|
536
|
+
if (!s.startsWith(".")) return s;
|
|
537
|
+
const from = normPath(fromFile);
|
|
538
|
+
const i = from.lastIndexOf("/");
|
|
539
|
+
const base = i >= 0 ? from.slice(0, i) : "";
|
|
540
|
+
const parts = base ? base.split("/") : [];
|
|
541
|
+
for (const seg of s.split("/")) {
|
|
542
|
+
if (seg === "" || seg === ".") continue;
|
|
543
|
+
if (seg === "..") parts.pop();
|
|
544
|
+
else parts.push(seg);
|
|
545
|
+
}
|
|
546
|
+
return parts.join("/");
|
|
547
|
+
}
|
|
548
|
+
function isNodeModulesPath(p) {
|
|
549
|
+
const f = normPath(p);
|
|
550
|
+
if (f === "node_modules" || f.startsWith("node_modules/")) return true;
|
|
551
|
+
return f.includes("/node_modules/") || f.endsWith("/node_modules");
|
|
552
|
+
}
|
|
553
|
+
function sidecarPathOf(file) {
|
|
554
|
+
const f = normPath(file);
|
|
555
|
+
if (f.endsWith(".mjs")) return f.slice(0, -4) + ".nudo.js";
|
|
556
|
+
if (f.endsWith(".js")) return f.slice(0, -3) + ".nudo.js";
|
|
557
|
+
if (f.endsWith(".mts")) return f.slice(0, -4) + ".nudo.ts";
|
|
558
|
+
if (f.endsWith(".ts")) return f.slice(0, -3) + ".nudo.ts";
|
|
559
|
+
return `${f}.nudo.js`;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
// src/algebra/refine.ts
|
|
563
|
+
function extractNudoImports(source) {
|
|
564
|
+
const out = [];
|
|
565
|
+
const named = /@nudo:import\s*\{([^}]+)\}\s*from\s*["']([^"']+)["']/g;
|
|
566
|
+
let m;
|
|
567
|
+
while (m = named.exec(source)) {
|
|
568
|
+
const names = m[1].split(",").map((s) => s.trim()).filter(Boolean).map((s) => s.split(/\s+as\s+/).pop().trim());
|
|
569
|
+
out.push({ names, spec: m[2] });
|
|
570
|
+
}
|
|
571
|
+
const ns = /@nudo:import\s+\*\s+as\s+(\w+)\s+from\s*["']([^"']+)["']/g;
|
|
572
|
+
while (m = ns.exec(source)) {
|
|
573
|
+
out.push({ names: [`*${m[1]}`], spec: m[2] });
|
|
574
|
+
}
|
|
575
|
+
return out;
|
|
576
|
+
}
|
|
577
|
+
var refineDiagCollector = null;
|
|
578
|
+
var refineDiagSeq = 0;
|
|
579
|
+
var refineDiags = [];
|
|
580
|
+
var MAX_REFINE_DIAGS = 1024;
|
|
581
|
+
function setRefineDiagCollector(fn2) {
|
|
582
|
+
refineDiagCollector = fn2;
|
|
583
|
+
}
|
|
584
|
+
function refineDiagCount() {
|
|
585
|
+
return refineDiagSeq;
|
|
586
|
+
}
|
|
587
|
+
function takeRefineDiags() {
|
|
588
|
+
const out = refineDiags.map((e) => e.d);
|
|
589
|
+
refineDiags.length = 0;
|
|
590
|
+
return out;
|
|
591
|
+
}
|
|
592
|
+
function takeRefineDiagsSince(since) {
|
|
593
|
+
const out = [];
|
|
594
|
+
let kept = 0;
|
|
595
|
+
for (const e of refineDiags) {
|
|
596
|
+
if (e.seq > since) out.push(e.d);
|
|
597
|
+
else refineDiags[kept++] = e;
|
|
598
|
+
}
|
|
599
|
+
refineDiags.length = kept;
|
|
600
|
+
return out;
|
|
601
|
+
}
|
|
602
|
+
function collectDiag(d) {
|
|
603
|
+
if (refineDiags.length >= MAX_REFINE_DIAGS) refineDiags.shift();
|
|
604
|
+
refineDiags.push({ seq: ++refineDiagSeq, d });
|
|
605
|
+
refineDiagCollector?.(d);
|
|
606
|
+
}
|
|
607
|
+
var NudoSidecarError = class extends Error {
|
|
608
|
+
code;
|
|
609
|
+
constructor(code, message) {
|
|
610
|
+
super(message);
|
|
611
|
+
this.name = "NudoSidecarError";
|
|
612
|
+
this.code = code;
|
|
613
|
+
}
|
|
614
|
+
};
|
|
615
|
+
var sidecarInjects = {
|
|
616
|
+
number,
|
|
617
|
+
string,
|
|
618
|
+
boolean,
|
|
619
|
+
shape,
|
|
620
|
+
array,
|
|
621
|
+
lit: litC,
|
|
622
|
+
union,
|
|
623
|
+
fn,
|
|
624
|
+
and: andC,
|
|
625
|
+
partial,
|
|
626
|
+
pick,
|
|
627
|
+
omit
|
|
628
|
+
};
|
|
629
|
+
function isSidecarSpec(spec) {
|
|
630
|
+
return spec.endsWith(".nudo.js") || spec.endsWith(".nudo.ts");
|
|
631
|
+
}
|
|
632
|
+
function sidecarImportNames(stmt) {
|
|
633
|
+
const out = [];
|
|
634
|
+
for (const s of stmt.specifiers) {
|
|
635
|
+
if (s.type === "ImportSpecifier") {
|
|
636
|
+
out.push({
|
|
637
|
+
local: s.local.name,
|
|
638
|
+
imported: s.imported.type === "Identifier" ? s.imported.name : s.imported.value
|
|
639
|
+
});
|
|
640
|
+
} else if (s.type === "ImportNamespaceSpecifier") {
|
|
641
|
+
out.push({ local: s.local.name, ns: true });
|
|
642
|
+
} else {
|
|
643
|
+
out.push({ local: s.local.name });
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
return out;
|
|
647
|
+
}
|
|
648
|
+
function rewriteSidecarSource(src, fromFile) {
|
|
649
|
+
const keepTs = fromFile?.endsWith(".nudo.ts") === true;
|
|
650
|
+
const ast = parseSource(src, keepTs ? { keepTs: true } : void 0);
|
|
651
|
+
const cuts = [];
|
|
652
|
+
const exportPairs = [];
|
|
653
|
+
const imports = [];
|
|
654
|
+
const DEFAULT_LOCAL = "__nudoDefault";
|
|
655
|
+
const badExport = (what) => {
|
|
656
|
+
collectDiag({
|
|
657
|
+
code: "nudo:interface-load",
|
|
658
|
+
message: `sidecar ${what}; recognized export forms are 'export const <name> = \u2026' and 'export default <constraint|fn>'`,
|
|
659
|
+
file: fromFile
|
|
660
|
+
});
|
|
661
|
+
};
|
|
662
|
+
for (const stmt of ast.program.body) {
|
|
663
|
+
if (stmt.type === "ImportDeclaration") {
|
|
664
|
+
imports.push({ spec: stmt.source.value, names: sidecarImportNames(stmt) });
|
|
665
|
+
if (stmt.start != null && stmt.end != null) {
|
|
666
|
+
cuts.push({ start: stmt.start, end: stmt.end, text: "" });
|
|
667
|
+
}
|
|
668
|
+
continue;
|
|
669
|
+
}
|
|
670
|
+
if (stmt.type === "ExportNamedDeclaration" && stmt.declaration && (stmt.declaration.type === "TSTypeAliasDeclaration" || stmt.declaration.type === "TSInterfaceDeclaration" || stmt.declaration.type === "TSEnumDeclaration")) {
|
|
671
|
+
if (stmt.start != null && stmt.end != null) {
|
|
672
|
+
cuts.push({ start: stmt.start, end: stmt.end, text: "" });
|
|
673
|
+
}
|
|
674
|
+
continue;
|
|
675
|
+
}
|
|
676
|
+
if (stmt.type === "ExportNamedDeclaration") {
|
|
677
|
+
const decl = stmt.declaration;
|
|
678
|
+
if (decl?.type === "VariableDeclaration" && stmt.start != null && decl.start != null) {
|
|
679
|
+
if (decl.kind === "const") {
|
|
680
|
+
let simple = true;
|
|
681
|
+
for (const d of decl.declarations) {
|
|
682
|
+
if (d.id.type === "Identifier") exportPairs.push({ key: d.id.name, expr: d.id.name });
|
|
683
|
+
else simple = false;
|
|
684
|
+
}
|
|
685
|
+
if (!simple) badExport("'export const' with destructuring pattern");
|
|
686
|
+
} else {
|
|
687
|
+
badExport(`'export ${decl.kind}'`);
|
|
688
|
+
}
|
|
689
|
+
cuts.push({ start: stmt.start, end: decl.start, text: "" });
|
|
690
|
+
continue;
|
|
691
|
+
}
|
|
692
|
+
if ((decl?.type === "FunctionDeclaration" || decl?.type === "ClassDeclaration") && stmt.start != null && decl.start != null) {
|
|
693
|
+
const kind = decl.type === "FunctionDeclaration" ? "function" : "class";
|
|
694
|
+
badExport(`'export ${kind} ${decl.id?.name ?? "(anonymous)"}'`);
|
|
695
|
+
cuts.push({ start: stmt.start, end: decl.start, text: "" });
|
|
696
|
+
continue;
|
|
697
|
+
}
|
|
698
|
+
badExport("'export { \u2026 }' (list / re-export)");
|
|
699
|
+
if (stmt.start != null && stmt.end != null) {
|
|
700
|
+
cuts.push({ start: stmt.start, end: stmt.end, text: "" });
|
|
701
|
+
}
|
|
702
|
+
continue;
|
|
703
|
+
}
|
|
704
|
+
if (stmt.type === "ExportDefaultDeclaration") {
|
|
705
|
+
const decl = stmt.declaration;
|
|
706
|
+
if (decl && (decl.type === "FunctionDeclaration" || decl.type === "ClassDeclaration") && stmt.start != null && decl.start != null) {
|
|
707
|
+
if (decl.id?.name) {
|
|
708
|
+
exportPairs.push({ key: decl.id.name, expr: decl.id.name });
|
|
709
|
+
exportPairs.push({ key: "default", expr: decl.id.name });
|
|
710
|
+
} else {
|
|
711
|
+
exportPairs.push({ key: "default", expr: DEFAULT_LOCAL });
|
|
712
|
+
if (decl.start != null && decl.end != null) {
|
|
713
|
+
const kind = decl.type === "FunctionDeclaration" ? "function" : "class";
|
|
714
|
+
cuts.push({
|
|
715
|
+
start: stmt.start,
|
|
716
|
+
end: decl.start,
|
|
717
|
+
text: `var ${DEFAULT_LOCAL} = `
|
|
718
|
+
});
|
|
719
|
+
void kind;
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
if (decl.id?.name) {
|
|
723
|
+
cuts.push({ start: stmt.start, end: decl.start, text: "" });
|
|
724
|
+
}
|
|
725
|
+
continue;
|
|
726
|
+
}
|
|
727
|
+
if (decl && stmt.start != null && decl.start != null && decl.end != null) {
|
|
728
|
+
exportPairs.push({ key: "default", expr: DEFAULT_LOCAL });
|
|
729
|
+
cuts.push({
|
|
730
|
+
start: stmt.start,
|
|
731
|
+
end: decl.start,
|
|
732
|
+
text: `var ${DEFAULT_LOCAL} = `
|
|
733
|
+
});
|
|
734
|
+
cuts.push({ start: decl.end, end: stmt.end ?? decl.end, text: ";" });
|
|
735
|
+
continue;
|
|
736
|
+
}
|
|
737
|
+
badExport("'export default'");
|
|
738
|
+
if (stmt.start != null && stmt.end != null) {
|
|
739
|
+
cuts.push({ start: stmt.start, end: stmt.end, text: "" });
|
|
740
|
+
}
|
|
741
|
+
continue;
|
|
742
|
+
}
|
|
743
|
+
if (stmt.type === "ExportAllDeclaration") {
|
|
744
|
+
badExport("'export * from \u2026'");
|
|
745
|
+
if (stmt.start != null && stmt.end != null) {
|
|
746
|
+
cuts.push({ start: stmt.start, end: stmt.end, text: "" });
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
if (fromFile?.endsWith(".nudo.ts")) {
|
|
751
|
+
for (const cut of collectTsSyntaxCuts(ast.program, src)) {
|
|
752
|
+
cuts.push({ start: cut.start, end: cut.end, text: "" });
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
cuts.sort((a, b) => a.start - b.start);
|
|
756
|
+
const merged = [];
|
|
757
|
+
for (const c of cuts) {
|
|
758
|
+
const last = merged[merged.length - 1];
|
|
759
|
+
if (last && c.start <= last.end && last.text === "" && c.text === "") {
|
|
760
|
+
last.end = Math.max(last.end, c.end);
|
|
761
|
+
} else {
|
|
762
|
+
merged.push({ start: c.start, end: c.end, text: c.text });
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
let code = "";
|
|
766
|
+
let pos = 0;
|
|
767
|
+
for (const c of merged) {
|
|
768
|
+
if (c.start < pos) continue;
|
|
769
|
+
code += src.slice(pos, c.start) + c.text;
|
|
770
|
+
pos = c.end;
|
|
771
|
+
}
|
|
772
|
+
code += src.slice(pos);
|
|
773
|
+
return { code, exportPairs, imports };
|
|
774
|
+
}
|
|
775
|
+
var TS_STMT_CUT_TYPES = /* @__PURE__ */ new Set([
|
|
776
|
+
"TSInterfaceDeclaration",
|
|
777
|
+
"TSTypeAliasDeclaration",
|
|
778
|
+
"TSEnumDeclaration",
|
|
779
|
+
"TSDeclareFunction",
|
|
780
|
+
"TSModuleDeclaration",
|
|
781
|
+
"TSImportEqualsDeclaration"
|
|
782
|
+
]);
|
|
783
|
+
function collectTsSyntaxCuts(node, src, cuts = []) {
|
|
784
|
+
if (!node || typeof node !== "object") return cuts;
|
|
785
|
+
if (Array.isArray(node)) {
|
|
786
|
+
for (const x of node) collectTsSyntaxCuts(x, src, cuts);
|
|
787
|
+
return cuts;
|
|
788
|
+
}
|
|
789
|
+
const o = node;
|
|
790
|
+
if (typeof o.start === "number" && typeof o.end === "number") {
|
|
791
|
+
if (typeof o.type === "string" && TS_STMT_CUT_TYPES.has(o.type)) {
|
|
792
|
+
cuts.push({ start: o.start, end: o.end });
|
|
793
|
+
return cuts;
|
|
794
|
+
}
|
|
795
|
+
if (o.type === "TSAsExpression" || o.type === "TSSatisfiesExpression") {
|
|
796
|
+
collectTsSyntaxCuts(o.expression, src, cuts);
|
|
797
|
+
const exprEnd = o.expression?.end;
|
|
798
|
+
if (typeof exprEnd === "number") cuts.push({ start: exprEnd, end: o.end });
|
|
799
|
+
return cuts;
|
|
800
|
+
}
|
|
801
|
+
if (o.type === "TSNonNullExpression") {
|
|
802
|
+
collectTsSyntaxCuts(o.expression, src, cuts);
|
|
803
|
+
const exprEnd = o.expression?.end;
|
|
804
|
+
if (typeof exprEnd === "number") cuts.push({ start: exprEnd, end: o.end });
|
|
805
|
+
return cuts;
|
|
806
|
+
}
|
|
807
|
+
if (o.type === "TSTypeAssertion") {
|
|
808
|
+
collectTsSyntaxCuts(o.expression, src, cuts);
|
|
809
|
+
const exprStart = o.expression?.start;
|
|
810
|
+
if (typeof exprStart === "number") cuts.push({ start: o.start, end: exprStart });
|
|
811
|
+
return cuts;
|
|
812
|
+
}
|
|
813
|
+
const ta = o.typeAnnotation;
|
|
814
|
+
if (ta && typeof ta.start === "number" && typeof ta.end === "number") {
|
|
815
|
+
let start = ta.start;
|
|
816
|
+
if (src[start - 1] === "?") start -= 1;
|
|
817
|
+
cuts.push({ start, end: ta.end });
|
|
818
|
+
}
|
|
819
|
+
const rt = o.returnType;
|
|
820
|
+
if (rt && typeof rt.start === "number" && typeof rt.end === "number") {
|
|
821
|
+
cuts.push({ start: rt.start, end: rt.end });
|
|
822
|
+
}
|
|
823
|
+
for (const key of ["typeParameters", "typeArguments"]) {
|
|
824
|
+
const tp = o[key];
|
|
825
|
+
if (tp && typeof tp.start === "number" && typeof tp.end === "number") {
|
|
826
|
+
cuts.push({ start: tp.start, end: tp.end });
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
if (o.optional === true && !ta && typeof o.end === "number" && src[o.end] === "?") {
|
|
830
|
+
cuts.push({ start: o.end, end: o.end + 1 });
|
|
831
|
+
}
|
|
832
|
+
if (o.declare === true) {
|
|
833
|
+
cuts.push({ start: o.start, end: o.end });
|
|
834
|
+
return cuts;
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
for (const key of Object.keys(o)) {
|
|
838
|
+
if (key === "loc" || key === "leadingComments" || key === "trailingComments" || key === "innerComments") {
|
|
839
|
+
continue;
|
|
840
|
+
}
|
|
841
|
+
collectTsSyntaxCuts(o[key], src, cuts);
|
|
842
|
+
}
|
|
843
|
+
return cuts;
|
|
844
|
+
}
|
|
845
|
+
function execSidecar(src, opts, chain, preloaded) {
|
|
846
|
+
const fromFile = opts?.fromFile;
|
|
847
|
+
const { code, exportPairs, imports } = rewriteSidecarSource(src, fromFile);
|
|
848
|
+
const prologue = [];
|
|
849
|
+
const deps = {};
|
|
850
|
+
for (const imp of imports) {
|
|
851
|
+
const relative = imp.spec.startsWith(".") || imp.spec.startsWith("/");
|
|
852
|
+
if (!relative) {
|
|
853
|
+
for (const n of imp.names) {
|
|
854
|
+
if (n.ns) {
|
|
855
|
+
collectDiag({
|
|
856
|
+
code: "nudo:interface-load",
|
|
857
|
+
message: `sidecar namespace import '${n.local}' from '${imp.spec}' is not supported (import named builders only)`,
|
|
858
|
+
file: fromFile
|
|
859
|
+
});
|
|
860
|
+
prologue.push(`var ${n.local} = __nudoInjects;`);
|
|
861
|
+
} else if (n.imported !== void 0) {
|
|
862
|
+
if (!(n.imported in sidecarInjects)) {
|
|
863
|
+
collectDiag({
|
|
864
|
+
code: "nudo:interface-load",
|
|
865
|
+
message: `sidecar import '{ ${n.imported} }' from '${imp.spec}' is not an injected builder`,
|
|
866
|
+
file: fromFile
|
|
867
|
+
});
|
|
868
|
+
}
|
|
869
|
+
if (n.local !== n.imported || !(n.imported in sidecarInjects)) {
|
|
870
|
+
prologue.push(`var ${n.local} = __nudoInjects[${JSON.stringify(n.imported)}];`);
|
|
871
|
+
}
|
|
872
|
+
} else {
|
|
873
|
+
collectDiag({
|
|
874
|
+
code: "nudo:interface-load",
|
|
875
|
+
message: `sidecar default import '${n.local}' from '${imp.spec}' has no default export to bind (sidecars export named constraints only)`,
|
|
876
|
+
file: fromFile
|
|
877
|
+
});
|
|
878
|
+
prologue.push(`var ${n.local} = undefined;`);
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
continue;
|
|
882
|
+
}
|
|
883
|
+
const load = opts?.loadModule;
|
|
884
|
+
if (!load || !fromFile) {
|
|
885
|
+
collectDiag({
|
|
886
|
+
code: "nudo:interface-load",
|
|
887
|
+
message: `sidecar import '${imp.spec}' is unresolvable (no loadModule for relative specifier)`,
|
|
888
|
+
file: fromFile
|
|
889
|
+
});
|
|
890
|
+
for (const n of imp.names) deps[n.local] = void 0;
|
|
891
|
+
continue;
|
|
892
|
+
}
|
|
893
|
+
if (!isSidecarSpec(imp.spec)) {
|
|
894
|
+
collectDiag({
|
|
895
|
+
code: "nudo:interface-load",
|
|
896
|
+
message: `sidecar import '${imp.spec}' unsupported: relative imports must target .nudo.js/.nudo.ts`,
|
|
897
|
+
file: fromFile
|
|
898
|
+
});
|
|
899
|
+
for (const n of imp.names) deps[n.local] = void 0;
|
|
900
|
+
continue;
|
|
901
|
+
}
|
|
902
|
+
const canonical = resolveDepPath(fromFile, imp.spec);
|
|
903
|
+
if (isNodeModulesPath(canonical)) {
|
|
904
|
+
collectDiag({
|
|
905
|
+
code: "nudo:interface-load",
|
|
906
|
+
message: `sidecar import '${imp.spec}' resolves into node_modules (${canonical}); ambient sidecar loading never crosses node_modules`,
|
|
907
|
+
file: fromFile
|
|
908
|
+
});
|
|
909
|
+
for (const n of imp.names) deps[n.local] = void 0;
|
|
910
|
+
continue;
|
|
911
|
+
}
|
|
912
|
+
if (chain.includes(canonical)) {
|
|
913
|
+
throw new NudoSidecarError(
|
|
914
|
+
"nudo:interface-cycle",
|
|
915
|
+
`sidecar import cycle: ${[...chain, canonical].join(" \u2192 ")}`
|
|
916
|
+
);
|
|
917
|
+
}
|
|
918
|
+
const depSrc = preloaded.has(canonical) ? preloaded.get(canonical) : load(imp.spec, fromFile);
|
|
919
|
+
if (depSrc === void 0) {
|
|
920
|
+
collectDiag({
|
|
921
|
+
code: "nudo:interface-load",
|
|
922
|
+
message: `sidecar import '${imp.spec}' is unresolvable (loadModule returned no source)`,
|
|
923
|
+
file: fromFile
|
|
924
|
+
});
|
|
925
|
+
for (const n of imp.names) deps[n.local] = void 0;
|
|
926
|
+
continue;
|
|
927
|
+
}
|
|
928
|
+
const depExports = execSidecar(
|
|
929
|
+
depSrc,
|
|
930
|
+
{ loadModule: load, fromFile: canonical },
|
|
931
|
+
[...chain, canonical],
|
|
932
|
+
preloaded
|
|
933
|
+
);
|
|
934
|
+
for (const n of imp.names) {
|
|
935
|
+
if (n.ns) {
|
|
936
|
+
deps[n.local] = depExports;
|
|
937
|
+
} else if (n.imported !== void 0) {
|
|
938
|
+
if (!(n.imported in depExports)) {
|
|
939
|
+
collectDiag({
|
|
940
|
+
code: "nudo:interface-load",
|
|
941
|
+
message: `sidecar '${imp.spec}' has no export '${n.imported}'`,
|
|
942
|
+
file: fromFile
|
|
943
|
+
});
|
|
944
|
+
}
|
|
945
|
+
deps[n.local] = depExports[n.imported];
|
|
946
|
+
} else {
|
|
947
|
+
collectDiag({
|
|
948
|
+
code: "nudo:interface-load",
|
|
949
|
+
message: `sidecar default import '${n.local}' from '${imp.spec}' has no default export (sidecars export named constraints only)`,
|
|
950
|
+
file: fromFile
|
|
951
|
+
});
|
|
952
|
+
deps[n.local] = depExports.default;
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
for (const local of Object.keys(deps)) {
|
|
957
|
+
prologue.push(`var ${local} = __nudoDeps[${JSON.stringify(local)}];`);
|
|
958
|
+
}
|
|
959
|
+
const returnObj = exportPairs.map(
|
|
960
|
+
(p) => p.key === p.expr ? p.key : `${JSON.stringify(p.key)}: ${p.expr}`
|
|
961
|
+
).join(", ");
|
|
962
|
+
const body = `${prologue.length > 0 ? `${prologue.join("\n")}
|
|
963
|
+
` : ""}${code}
|
|
964
|
+
return { ${returnObj} };`;
|
|
965
|
+
const fn2 = new Function(
|
|
966
|
+
...Object.keys(sidecarInjects),
|
|
967
|
+
"__nudoInjects",
|
|
968
|
+
"__nudoDeps",
|
|
969
|
+
body
|
|
970
|
+
);
|
|
971
|
+
return fn2(...Object.values(sidecarInjects), sidecarInjects, deps);
|
|
972
|
+
}
|
|
973
|
+
function execNudoModule(src, opts) {
|
|
974
|
+
return execNudoModuleCached(src, opts ?? {});
|
|
975
|
+
}
|
|
976
|
+
var nudoModuleExecCache = /* @__PURE__ */ new Map();
|
|
977
|
+
var MAX_NUDO_MODULE_EXEC = 64;
|
|
978
|
+
var MAX_SIDECAR_CLOSURE = 64;
|
|
979
|
+
function sidecarDepClosure(src, fromFile, loadModule, out) {
|
|
980
|
+
let ast;
|
|
981
|
+
try {
|
|
982
|
+
ast = parseSource(src);
|
|
983
|
+
} catch {
|
|
984
|
+
return { truncated: false };
|
|
985
|
+
}
|
|
986
|
+
for (const stmt of ast.program.body) {
|
|
987
|
+
if (out.size >= MAX_SIDECAR_CLOSURE) return { truncated: true };
|
|
988
|
+
if (stmt.type !== "ImportDeclaration") continue;
|
|
989
|
+
const spec = stmt.source.value;
|
|
990
|
+
if (!(spec.startsWith(".") || spec.startsWith("/"))) continue;
|
|
991
|
+
if (!isSidecarSpec(spec)) continue;
|
|
992
|
+
const canonical = resolveDepPath(fromFile, spec);
|
|
993
|
+
if (out.has(canonical)) continue;
|
|
994
|
+
const depSrc = loadModule(spec, fromFile);
|
|
995
|
+
out.set(canonical, depSrc);
|
|
996
|
+
if (depSrc === void 0) continue;
|
|
997
|
+
const r = sidecarDepClosure(depSrc, canonical, loadModule, out);
|
|
998
|
+
if (r.truncated) return r;
|
|
999
|
+
}
|
|
1000
|
+
return { truncated: false };
|
|
1001
|
+
}
|
|
1002
|
+
function execNudoModuleCached(src, opts) {
|
|
1003
|
+
let key;
|
|
1004
|
+
let preloaded = /* @__PURE__ */ new Map();
|
|
1005
|
+
if (opts.loadModule && opts.fromFile) {
|
|
1006
|
+
const deps = /* @__PURE__ */ new Map();
|
|
1007
|
+
const { truncated } = sidecarDepClosure(src, opts.fromFile, opts.loadModule, deps);
|
|
1008
|
+
if (!truncated) {
|
|
1009
|
+
const depPart = [...deps.entries()].map(([p, s]) => `${p}=${s === void 0 ? "miss" : hashSource(s)}`).sort().join(",");
|
|
1010
|
+
key = `${hashSource(src)}|${depPart}`;
|
|
1011
|
+
preloaded = deps;
|
|
1012
|
+
}
|
|
1013
|
+
} else {
|
|
1014
|
+
key = src;
|
|
1015
|
+
}
|
|
1016
|
+
if (key !== void 0) {
|
|
1017
|
+
const hit = nudoModuleExecCache.get(key);
|
|
1018
|
+
if (hit !== void 0) {
|
|
1019
|
+
nudoModuleExecCache.delete(key);
|
|
1020
|
+
nudoModuleExecCache.set(key, hit);
|
|
1021
|
+
return hit;
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
const out = execSidecar(src, opts, opts.fromFile ? [opts.fromFile] : [], preloaded);
|
|
1025
|
+
if (key !== void 0) {
|
|
1026
|
+
if (nudoModuleExecCache.size >= MAX_NUDO_MODULE_EXEC) {
|
|
1027
|
+
const oldest = nudoModuleExecCache.keys().next().value;
|
|
1028
|
+
if (oldest !== void 0) nudoModuleExecCache.delete(oldest);
|
|
1029
|
+
}
|
|
1030
|
+
nudoModuleExecCache.set(key, out);
|
|
1031
|
+
}
|
|
1032
|
+
return out;
|
|
1033
|
+
}
|
|
1034
|
+
function resetNudoModuleExecCache() {
|
|
1035
|
+
nudoModuleExecCache.clear();
|
|
1036
|
+
}
|
|
1037
|
+
function collectConstraints(source, opts) {
|
|
1038
|
+
const map = /* @__PURE__ */ new Map();
|
|
1039
|
+
const imports = extractNudoImports(source);
|
|
1040
|
+
for (const imp of imports) {
|
|
1041
|
+
if (!opts.loadModule || !opts.fromFile) {
|
|
1042
|
+
collectDiag({
|
|
1043
|
+
code: "nudo:interface-load",
|
|
1044
|
+
message: `sidecar '${imp.spec}' not loaded: loadModule/fromFile missing from host options`,
|
|
1045
|
+
file: opts.fromFile
|
|
1046
|
+
});
|
|
1047
|
+
continue;
|
|
1048
|
+
}
|
|
1049
|
+
const src = opts.loadModule(imp.spec, opts.fromFile);
|
|
1050
|
+
if (src === void 0) {
|
|
1051
|
+
collectDiag({
|
|
1052
|
+
code: "nudo:interface-load",
|
|
1053
|
+
message: `sidecar '${imp.spec}' failed to load from '${opts.fromFile}'`,
|
|
1054
|
+
file: opts.fromFile
|
|
1055
|
+
});
|
|
1056
|
+
continue;
|
|
1057
|
+
}
|
|
1058
|
+
const sidecarPath = resolveDepPath(opts.fromFile, imp.spec);
|
|
1059
|
+
let exports;
|
|
1060
|
+
try {
|
|
1061
|
+
exports = execNudoModuleCached(src, { loadModule: opts.loadModule, fromFile: sidecarPath });
|
|
1062
|
+
} catch (e) {
|
|
1063
|
+
if (e instanceof NudoSidecarError) {
|
|
1064
|
+
collectDiag({ code: e.code, message: e.message, file: opts.fromFile });
|
|
1065
|
+
} else {
|
|
1066
|
+
collectDiag({
|
|
1067
|
+
code: "nudo:interface-load",
|
|
1068
|
+
message: `sidecar '${imp.spec}' failed to execute: ${e instanceof Error ? e.message : String(e)}`,
|
|
1069
|
+
file: opts.fromFile
|
|
1070
|
+
});
|
|
1071
|
+
}
|
|
1072
|
+
continue;
|
|
1073
|
+
}
|
|
1074
|
+
for (const name of imp.names) {
|
|
1075
|
+
if (name.startsWith("*")) {
|
|
1076
|
+
const ns = name.slice(1);
|
|
1077
|
+
for (const [expName, v3] of Object.entries(exports)) {
|
|
1078
|
+
if (isNudoConstraint(v3)) {
|
|
1079
|
+
map.set(`${ns}.${expName}`, v3);
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
continue;
|
|
1083
|
+
}
|
|
1084
|
+
const v2 = exports[name];
|
|
1085
|
+
if (isNudoConstraint(v2)) {
|
|
1086
|
+
map.set(name, v2);
|
|
1087
|
+
} else if (v2 === void 0) {
|
|
1088
|
+
collectDiag({
|
|
1089
|
+
code: "nudo:interface-load",
|
|
1090
|
+
message: `sidecar '${imp.spec}' has no export '${name}'`,
|
|
1091
|
+
file: opts.fromFile
|
|
1092
|
+
});
|
|
1093
|
+
} else {
|
|
1094
|
+
collectDiag({
|
|
1095
|
+
code: "nudo:interface-load",
|
|
1096
|
+
message: `sidecar '${imp.spec}' export '${name}' is not a Nudo constraint`,
|
|
1097
|
+
file: opts.fromFile
|
|
1098
|
+
});
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
return map;
|
|
1103
|
+
}
|
|
1104
|
+
function extractRefineLines(source, fnName) {
|
|
1105
|
+
const escaped = fnName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1106
|
+
const fnRe = new RegExp(
|
|
1107
|
+
`(?:export\\s+(?:default\\s+)?)?(?:async\\s+)?(?:function\\s+${escaped}\\b|const\\s+${escaped}\\s*=)`
|
|
1108
|
+
);
|
|
1109
|
+
const m = source.match(fnRe);
|
|
1110
|
+
if (!m || m.index === void 0) return [];
|
|
1111
|
+
const before = source.slice(0, m.index);
|
|
1112
|
+
const lines = before.split("\n");
|
|
1113
|
+
const reqs = [];
|
|
1114
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
1115
|
+
const line = lines[i].trim();
|
|
1116
|
+
if (line === "" || line === "*/") continue;
|
|
1117
|
+
if (line.startsWith("*") || line.startsWith("/*") || line.startsWith("//")) {
|
|
1118
|
+
const rm = line.match(/@nudo:contract\s+(.+)$/);
|
|
1119
|
+
if (rm) reqs.unshift(rm[1].trim().replace(/\*\/$/, "").trim());
|
|
1120
|
+
continue;
|
|
1121
|
+
}
|
|
1122
|
+
break;
|
|
1123
|
+
}
|
|
1124
|
+
return reqs;
|
|
1125
|
+
}
|
|
1126
|
+
function extractRefinesFromSource(source, fnName, opts = {}) {
|
|
1127
|
+
if (!source.includes("@nudo:contract")) return [];
|
|
1128
|
+
const constraints = collectConstraints(source, opts);
|
|
1129
|
+
const out = [];
|
|
1130
|
+
for (const line of extractRefineLines(source, fnName)) {
|
|
1131
|
+
const parts = line.split(/&&|,/).map((s) => s.trim()).filter(Boolean);
|
|
1132
|
+
for (const part of parts) {
|
|
1133
|
+
const m = part.match(/^(\w+)\s+([\w.]+)$/);
|
|
1134
|
+
if (!m) continue;
|
|
1135
|
+
const [, param, cName] = m;
|
|
1136
|
+
if (param === "return") continue;
|
|
1137
|
+
const c = constraints.get(cName);
|
|
1138
|
+
if (!c) continue;
|
|
1139
|
+
out.push({
|
|
1140
|
+
param,
|
|
1141
|
+
pred: instantiateConstraint(c, param),
|
|
1142
|
+
constraint: c
|
|
1143
|
+
});
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
return out;
|
|
1147
|
+
}
|
|
1148
|
+
function refineToIndexedFull(source, fnName, paramNames, opts = {}) {
|
|
1149
|
+
const raw = extractRefinesFromSource(source, fnName, opts);
|
|
1150
|
+
const out = [];
|
|
1151
|
+
for (const item of raw) {
|
|
1152
|
+
const idx = paramNames.indexOf(item.param);
|
|
1153
|
+
if (idx >= 0) out.push([idx, item]);
|
|
1154
|
+
}
|
|
1155
|
+
return out;
|
|
1156
|
+
}
|
|
1157
|
+
function extractRefineReturnFromSource(source, fnName, opts = {}) {
|
|
1158
|
+
if (!source.includes("@nudo:contract")) return void 0;
|
|
1159
|
+
const constraints = collectConstraints(source, opts);
|
|
1160
|
+
for (const line of extractRefineLines(source, fnName)) {
|
|
1161
|
+
const parts = line.split(/&&|,/).map((s) => s.trim()).filter(Boolean);
|
|
1162
|
+
for (const part of parts) {
|
|
1163
|
+
const m = part.match(/^return\s+([\w.]+)$/);
|
|
1164
|
+
if (!m) continue;
|
|
1165
|
+
const cName = m[1];
|
|
1166
|
+
const c = constraints.get(cName);
|
|
1167
|
+
if (!c) continue;
|
|
1168
|
+
return { name: cName, constraint: c };
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
return void 0;
|
|
1172
|
+
}
|
|
1173
|
+
function extractDeclaredThrows(source, fnName) {
|
|
1174
|
+
if (!source.includes("@nudo:throws") && !source.includes("!! throws")) {
|
|
1175
|
+
return void 0;
|
|
1176
|
+
}
|
|
1177
|
+
const kinds = /* @__PURE__ */ new Set();
|
|
1178
|
+
let any2 = false;
|
|
1179
|
+
const escaped = fnName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1180
|
+
const fnRe = new RegExp(
|
|
1181
|
+
`(?:export\\s+(?:default\\s+)?)?(?:async\\s+)?(?:function\\s+${escaped}\\b|const\\s+${escaped}\\s*=)`
|
|
1182
|
+
);
|
|
1183
|
+
const m = source.match(fnRe);
|
|
1184
|
+
if (!m || m.index === void 0) return void 0;
|
|
1185
|
+
const before = source.slice(0, m.index);
|
|
1186
|
+
const lines = before.split("\n");
|
|
1187
|
+
let seenComment = false;
|
|
1188
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
1189
|
+
const line = lines[i].trim();
|
|
1190
|
+
if (line === "") {
|
|
1191
|
+
if (seenComment) break;
|
|
1192
|
+
continue;
|
|
1193
|
+
}
|
|
1194
|
+
if (line === "*/") continue;
|
|
1195
|
+
if (line.startsWith("*") || line.startsWith("/*") || line.startsWith("//")) {
|
|
1196
|
+
seenComment = true;
|
|
1197
|
+
const body = line.replace(/^[*/\s]+/, "").replace(/\*\/$/, "").trim();
|
|
1198
|
+
const th = body.match(/@nudo:throws\s+(.+)$/i);
|
|
1199
|
+
if (th) {
|
|
1200
|
+
const spec = th[1].trim();
|
|
1201
|
+
if (spec === "*") any2 = true;
|
|
1202
|
+
else {
|
|
1203
|
+
for (const k of spec.split(/[,\s|]+/).map((s) => s.trim()).filter(Boolean)) {
|
|
1204
|
+
if (k === "*") any2 = true;
|
|
1205
|
+
else kinds.add(k);
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
const cs = body.match(/!!\s*throws(?:\s+([A-Za-z*][\w*|,\s]*))?/i);
|
|
1210
|
+
if (cs) {
|
|
1211
|
+
const spec = (cs[1] ?? "*").trim();
|
|
1212
|
+
if (spec === "" || spec === "*") any2 = true;
|
|
1213
|
+
else {
|
|
1214
|
+
for (const k of spec.split(/[,\s|]+/).map((s) => s.trim()).filter(Boolean)) {
|
|
1215
|
+
if (k === "*") any2 = true;
|
|
1216
|
+
else kinds.add(k);
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
continue;
|
|
1221
|
+
}
|
|
1222
|
+
break;
|
|
1223
|
+
}
|
|
1224
|
+
if (any2) return "*";
|
|
1225
|
+
if (kinds.size > 0) return [...kinds];
|
|
1226
|
+
return void 0;
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
// src/algebra/interface.ts
|
|
1230
|
+
var interfaceDiagCollector = null;
|
|
1231
|
+
var interfaceDiagSeq = 0;
|
|
1232
|
+
var interfaceDiags = [];
|
|
1233
|
+
var MAX_INTERFACE_DIAGS = 1024;
|
|
1234
|
+
function setInterfaceDiagCollector(fn2) {
|
|
1235
|
+
interfaceDiagCollector = fn2;
|
|
1236
|
+
}
|
|
1237
|
+
function interfaceDiagCount() {
|
|
1238
|
+
return interfaceDiagSeq;
|
|
1239
|
+
}
|
|
1240
|
+
function takeInterfaceDiags() {
|
|
1241
|
+
const out = interfaceDiags.map((e) => e.d);
|
|
1242
|
+
interfaceDiags.length = 0;
|
|
1243
|
+
return out;
|
|
1244
|
+
}
|
|
1245
|
+
function takeInterfaceDiagsSince(since) {
|
|
1246
|
+
const out = [];
|
|
1247
|
+
let kept = 0;
|
|
1248
|
+
for (const e of interfaceDiags) {
|
|
1249
|
+
if (e.seq > since) out.push(e.d);
|
|
1250
|
+
else interfaceDiags[kept++] = e;
|
|
1251
|
+
}
|
|
1252
|
+
interfaceDiags.length = kept;
|
|
1253
|
+
return out;
|
|
1254
|
+
}
|
|
1255
|
+
function collectDiag2(d) {
|
|
1256
|
+
if (interfaceDiags.length >= MAX_INTERFACE_DIAGS) interfaceDiags.shift();
|
|
1257
|
+
interfaceDiags.push({ seq: ++interfaceDiagSeq, d });
|
|
1258
|
+
interfaceDiagCollector?.(d);
|
|
1259
|
+
}
|
|
1260
|
+
function localNamedExports(source) {
|
|
1261
|
+
const out = /* @__PURE__ */ new Set();
|
|
1262
|
+
let ast;
|
|
1263
|
+
try {
|
|
1264
|
+
ast = parseSource(source, { errorRecovery: true });
|
|
1265
|
+
} catch {
|
|
1266
|
+
return out;
|
|
1267
|
+
}
|
|
1268
|
+
const importedLocalNames = /* @__PURE__ */ new Set();
|
|
1269
|
+
for (const stmt of ast.program.body) {
|
|
1270
|
+
if (stmt.type !== "ImportDeclaration") continue;
|
|
1271
|
+
for (const spec of stmt.specifiers) {
|
|
1272
|
+
if (spec.type === "ImportNamespaceSpecifier" || spec.type === "ImportDefaultSpecifier") {
|
|
1273
|
+
importedLocalNames.add(spec.local.name);
|
|
1274
|
+
} else if (spec.type === "ImportSpecifier") {
|
|
1275
|
+
importedLocalNames.add(spec.local.name);
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1279
|
+
const identName = (n) => {
|
|
1280
|
+
if (!n) return void 0;
|
|
1281
|
+
if (n.type === "Identifier" && typeof n.name === "string") return n.name;
|
|
1282
|
+
if (n.type === "Literal" && typeof n.value === "string") return n.value;
|
|
1283
|
+
return void 0;
|
|
1284
|
+
};
|
|
1285
|
+
const localClasses = /* @__PURE__ */ new Map();
|
|
1286
|
+
for (const stmt of ast.program.body) {
|
|
1287
|
+
const s = stmt;
|
|
1288
|
+
if (s.type !== "ClassDeclaration") continue;
|
|
1289
|
+
const idName = identName(s.id);
|
|
1290
|
+
if (idName) localClasses.set(idName, s);
|
|
1291
|
+
}
|
|
1292
|
+
const addClassMethodKeys = (out2, decl) => {
|
|
1293
|
+
if (!decl || decl.type !== "ClassDeclaration") return;
|
|
1294
|
+
const idName = identName(decl.id);
|
|
1295
|
+
if (!idName) return;
|
|
1296
|
+
const body = decl.body?.body ?? [];
|
|
1297
|
+
for (const m of body) {
|
|
1298
|
+
const mem = m;
|
|
1299
|
+
const isMethod = mem.type === "MethodDefinition" || mem.type === "ClassMethod" || mem.type === "TSDeclareMethod";
|
|
1300
|
+
if (!isMethod) continue;
|
|
1301
|
+
if (mem.kind && mem.kind !== "method") continue;
|
|
1302
|
+
const keyName = identName(mem.key);
|
|
1303
|
+
if (keyName) out2.add(`${idName}.${keyName}`);
|
|
1304
|
+
}
|
|
1305
|
+
};
|
|
1306
|
+
const isExportsTarget = (n) => {
|
|
1307
|
+
if (!n) return false;
|
|
1308
|
+
if (n.type === "Identifier") return n.name === "exports";
|
|
1309
|
+
if (n.type === "MemberExpression") {
|
|
1310
|
+
return identName(n.object) === "module" && n.property !== void 0 ? identName(n.property) === "exports" : false;
|
|
1311
|
+
}
|
|
1312
|
+
return false;
|
|
1313
|
+
};
|
|
1314
|
+
const cjsPropName = (left) => {
|
|
1315
|
+
if (!left || left.type !== "MemberExpression") return void 0;
|
|
1316
|
+
if (left.computed === true) return void 0;
|
|
1317
|
+
const obj = left.object;
|
|
1318
|
+
if (!isExportsTarget(obj)) return void 0;
|
|
1319
|
+
return identName(left.property);
|
|
1320
|
+
};
|
|
1321
|
+
for (const stmt of ast.program.body) {
|
|
1322
|
+
const s = stmt;
|
|
1323
|
+
if (s.type === "ExportNamedDeclaration") {
|
|
1324
|
+
if (s.source) continue;
|
|
1325
|
+
const d = s.declaration;
|
|
1326
|
+
if (!d) {
|
|
1327
|
+
const specs = s.specifiers ?? [];
|
|
1328
|
+
for (const spec of specs) {
|
|
1329
|
+
if (spec.type !== "ExportSpecifier") continue;
|
|
1330
|
+
const name = identName(spec.exported);
|
|
1331
|
+
if (!name) continue;
|
|
1332
|
+
const localName = identName(spec.local);
|
|
1333
|
+
if (name === "default") {
|
|
1334
|
+
out.add("default");
|
|
1335
|
+
if (localName && !importedLocalNames.has(localName)) out.add(localName);
|
|
1336
|
+
if (localName && !importedLocalNames.has(localName)) {
|
|
1337
|
+
addClassMethodKeys(out, localClasses.get(localName));
|
|
1338
|
+
}
|
|
1339
|
+
continue;
|
|
1340
|
+
}
|
|
1341
|
+
if (localName && importedLocalNames.has(localName)) continue;
|
|
1342
|
+
out.add(name);
|
|
1343
|
+
if (localName) {
|
|
1344
|
+
out.add(localName);
|
|
1345
|
+
addClassMethodKeys(out, localClasses.get(localName));
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1348
|
+
continue;
|
|
1349
|
+
}
|
|
1350
|
+
if (d.type === "FunctionDeclaration" || d.type === "ClassDeclaration") {
|
|
1351
|
+
const idName = identName(d.id);
|
|
1352
|
+
if (idName) out.add(idName);
|
|
1353
|
+
if (d.type === "ClassDeclaration" && idName) {
|
|
1354
|
+
addClassMethodKeys(out, d);
|
|
1355
|
+
}
|
|
1356
|
+
} else if (d.type === "VariableDeclaration") {
|
|
1357
|
+
const decls = d.declarations ?? [];
|
|
1358
|
+
for (const decl of decls) {
|
|
1359
|
+
const idName = identName(decl.id);
|
|
1360
|
+
if (idName) out.add(idName);
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1363
|
+
continue;
|
|
1364
|
+
}
|
|
1365
|
+
if (s.type === "ExportDefaultDeclaration") {
|
|
1366
|
+
out.add("default");
|
|
1367
|
+
const d = s.declaration;
|
|
1368
|
+
if (d) {
|
|
1369
|
+
if ((d.type === "FunctionDeclaration" || d.type === "ClassDeclaration") && d.id) {
|
|
1370
|
+
const idName = identName(d.id);
|
|
1371
|
+
if (idName) out.add(idName);
|
|
1372
|
+
if (d.type === "ClassDeclaration" && idName) {
|
|
1373
|
+
addClassMethodKeys(out, d);
|
|
1374
|
+
}
|
|
1375
|
+
} else if (d.type === "ArrowFunctionExpression" || d.type === "FunctionExpression") {
|
|
1376
|
+
} else if (d.type === "Identifier") {
|
|
1377
|
+
const n = identName(d);
|
|
1378
|
+
if (n && !importedLocalNames.has(n)) {
|
|
1379
|
+
out.add(n);
|
|
1380
|
+
addClassMethodKeys(out, localClasses.get(n));
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
continue;
|
|
1385
|
+
}
|
|
1386
|
+
if (s.type === "ExpressionStatement") {
|
|
1387
|
+
const expr = s.expression;
|
|
1388
|
+
if (!expr || expr.type !== "AssignmentExpression" || expr.operator !== "=") continue;
|
|
1389
|
+
const left = expr.left;
|
|
1390
|
+
const right = expr.right;
|
|
1391
|
+
if (!left || !right) continue;
|
|
1392
|
+
const prop = cjsPropName(left);
|
|
1393
|
+
if (prop && prop !== "default") out.add(prop);
|
|
1394
|
+
if (isExportsTarget(left)) {
|
|
1395
|
+
if (right.type === "ObjectExpression") {
|
|
1396
|
+
const props = right.properties ?? [];
|
|
1397
|
+
for (const p of props) {
|
|
1398
|
+
if (p.type !== "ObjectProperty" && p.type !== "Property" && p.type !== "ObjectMethod") {
|
|
1399
|
+
continue;
|
|
1400
|
+
}
|
|
1401
|
+
const keyName = identName(p.key);
|
|
1402
|
+
if (keyName && keyName !== "default") out.add(keyName);
|
|
1403
|
+
else if (p.type !== "ObjectMethod") {
|
|
1404
|
+
const valName = identName(p.value);
|
|
1405
|
+
if (valName) out.add(valName);
|
|
1406
|
+
}
|
|
1407
|
+
}
|
|
1408
|
+
} else if (right.type === "Identifier") {
|
|
1409
|
+
const n = identName(right);
|
|
1410
|
+
if (n && !importedLocalNames.has(n)) out.add(n);
|
|
1411
|
+
}
|
|
1412
|
+
}
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1415
|
+
return out;
|
|
1416
|
+
}
|
|
1417
|
+
function generatedExportNames(sidecarSrc) {
|
|
1418
|
+
const out = /* @__PURE__ */ new Set();
|
|
1419
|
+
let ast;
|
|
1420
|
+
try {
|
|
1421
|
+
ast = parseSource(sidecarSrc, { errorRecovery: true });
|
|
1422
|
+
} catch {
|
|
1423
|
+
return out;
|
|
1424
|
+
}
|
|
1425
|
+
const stmts = ast.program.body;
|
|
1426
|
+
for (let i = 0; i < stmts.length; i++) {
|
|
1427
|
+
const stmt = stmts[i];
|
|
1428
|
+
if (stmt.type !== "ExportNamedDeclaration") continue;
|
|
1429
|
+
if (stmt.source) continue;
|
|
1430
|
+
const d = stmt.declaration;
|
|
1431
|
+
if (!d || stmt.start == null) continue;
|
|
1432
|
+
const names = [];
|
|
1433
|
+
if (d.type === "VariableDeclaration") {
|
|
1434
|
+
for (const decl of d.declarations) {
|
|
1435
|
+
if (decl.id.type === "Identifier") names.push(decl.id.name);
|
|
1436
|
+
}
|
|
1437
|
+
} else if (d.type === "FunctionDeclaration" || d.type === "ClassDeclaration") {
|
|
1438
|
+
if (d.id) names.push(d.id.name);
|
|
1439
|
+
}
|
|
1440
|
+
if (names.length === 0) continue;
|
|
1441
|
+
if (regionHasGeneratedMarker(stmts, i, sidecarSrc)) {
|
|
1442
|
+
for (const n of names) out.add(n);
|
|
1443
|
+
}
|
|
1444
|
+
}
|
|
1445
|
+
return out;
|
|
1446
|
+
}
|
|
1447
|
+
function regionHasGeneratedMarker(stmts, exportIdx, src) {
|
|
1448
|
+
const exportStart = stmts[exportIdx].start;
|
|
1449
|
+
if (exportStart == null) return false;
|
|
1450
|
+
let regionStart = 0;
|
|
1451
|
+
for (let j = exportIdx - 1; j >= 0; j--) {
|
|
1452
|
+
const prev = stmts[j];
|
|
1453
|
+
if (prev.type === "ImportDeclaration" || prev.type === "VariableDeclaration") continue;
|
|
1454
|
+
regionStart = prev.end ?? 0;
|
|
1455
|
+
break;
|
|
1456
|
+
}
|
|
1457
|
+
return src.slice(regionStart, exportStart).split("\n").some((line) => {
|
|
1458
|
+
const t = line.trim();
|
|
1459
|
+
return (t.startsWith("//") || t.startsWith("/*") || t.startsWith("*")) && /@generated/.test(t);
|
|
1460
|
+
});
|
|
1461
|
+
}
|
|
1462
|
+
function isUnderProjectRoot(sidecarPath, projectDir) {
|
|
1463
|
+
const norm = (p) => p.split("\\").join("/").replace(/\/+$/, "");
|
|
1464
|
+
const root = norm(projectDir);
|
|
1465
|
+
const sc = norm(sidecarPath);
|
|
1466
|
+
return sc === root || sc.startsWith(`${root}/`);
|
|
1467
|
+
}
|
|
1468
|
+
function sidecarAutoBindAllowed(sidecarPath, autoBind, projectDir) {
|
|
1469
|
+
if (isNodeModulesPath(sidecarPath)) return false;
|
|
1470
|
+
if (projectDir && !isUnderProjectRoot(sidecarPath, projectDir)) return false;
|
|
1471
|
+
if (autoBind === void 0 || autoBind === true) return true;
|
|
1472
|
+
if (typeof autoBind === "function") return autoBind(sidecarPath) === true;
|
|
1473
|
+
return false;
|
|
1474
|
+
}
|
|
1475
|
+
function isDefaultExportLocal(source, localName) {
|
|
1476
|
+
const reList = new RegExp(
|
|
1477
|
+
`export\\s*\\{[^}]*\\b${localName}\\s+as\\s+default\\b[^}]*\\}`,
|
|
1478
|
+
"m"
|
|
1479
|
+
);
|
|
1480
|
+
const reDefaultFn = new RegExp(
|
|
1481
|
+
`export\\s+default\\s+(?:async\\s+)?function\\s+${localName}\\b`,
|
|
1482
|
+
"m"
|
|
1483
|
+
);
|
|
1484
|
+
const reDefaultId = new RegExp(`export\\s+default\\s+${localName}\\b`, "m");
|
|
1485
|
+
return reList.test(source) || reDefaultFn.test(source) || reDefaultId.test(source);
|
|
1486
|
+
}
|
|
1487
|
+
function loadSidecarBinding(source, fnName, opts) {
|
|
1488
|
+
const { loadModule, fromFile, autoBind, projectDir } = opts;
|
|
1489
|
+
if (!loadModule || !fromFile) return { ok: false };
|
|
1490
|
+
const sidecarPath = sidecarPathOf(fromFile);
|
|
1491
|
+
if (!sidecarAutoBindAllowed(sidecarPath, autoBind, projectDir)) return { ok: false };
|
|
1492
|
+
if (!localNamedExports(source).has(fnName)) return { ok: false };
|
|
1493
|
+
const spec = `./${sidecarPath.slice(sidecarPath.lastIndexOf("/") + 1)}`;
|
|
1494
|
+
const sidecarSrc = loadModule(spec, fromFile);
|
|
1495
|
+
if (sidecarSrc === void 0) return { ok: false };
|
|
1496
|
+
if (sidecarSrc === source) return { ok: false };
|
|
1497
|
+
let exports;
|
|
1498
|
+
try {
|
|
1499
|
+
exports = execNudoModule(sidecarSrc, { loadModule, fromFile: sidecarPath });
|
|
1500
|
+
} catch (e) {
|
|
1501
|
+
collectDiag2({
|
|
1502
|
+
code: e instanceof NudoSidecarError ? e.code : "nudo:interface-load",
|
|
1503
|
+
message: `sidecar '${spec}' for '${fnName}' failed: ${e instanceof Error ? e.message : String(e)}`,
|
|
1504
|
+
file: sidecarPath
|
|
1505
|
+
});
|
|
1506
|
+
return { ok: false };
|
|
1507
|
+
}
|
|
1508
|
+
let binding = exports[fnName];
|
|
1509
|
+
if (binding === void 0 && exports.default !== void 0 && isDefaultExportLocal(source, fnName)) {
|
|
1510
|
+
binding = exports.default;
|
|
1511
|
+
}
|
|
1512
|
+
if (binding === void 0 && fnName.includes(".")) {
|
|
1513
|
+
const [cls, method] = fnName.split(".", 2);
|
|
1514
|
+
binding = exports[`${cls}_${method}`];
|
|
1515
|
+
if (binding === void 0) {
|
|
1516
|
+
const bag = exports[cls];
|
|
1517
|
+
if (bag && typeof bag === "object" && !isNudoConstraint(bag)) {
|
|
1518
|
+
binding = bag[method];
|
|
1519
|
+
}
|
|
1520
|
+
}
|
|
1521
|
+
if (binding === void 0 && method !== void 0 && exports[method] !== void 0) {
|
|
1522
|
+
collectDiag2({
|
|
1523
|
+
code: "nudo:interface-load",
|
|
1524
|
+
message: `sidecar key '${method}' does not bind '${fnName}' \u2014 use '${cls}.${method}', '${cls}_${method}', or nested { ${cls}: { ${method}: \u2026 } }`,
|
|
1525
|
+
file: sidecarPath
|
|
1526
|
+
});
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1529
|
+
if (binding === void 0) return { ok: false };
|
|
1530
|
+
if (!isNudoConstraint(binding)) {
|
|
1531
|
+
collectDiag2({
|
|
1532
|
+
code: "nudo:interface-load",
|
|
1533
|
+
message: `sidecar '${spec}' export '${fnName}' is not a Nudo constraint`,
|
|
1534
|
+
file: sidecarPath
|
|
1535
|
+
});
|
|
1536
|
+
return { ok: false };
|
|
1537
|
+
}
|
|
1538
|
+
if (!binding.fn) {
|
|
1539
|
+
collectDiag2({
|
|
1540
|
+
code: "nudo:interface-load",
|
|
1541
|
+
message: `sidecar '${spec}' binding '${fnName}' is not fn() form (Phase 1 consumes only fn({ \u2026 }, \u2026) bindings)`,
|
|
1542
|
+
file: sidecarPath
|
|
1543
|
+
});
|
|
1544
|
+
return { ok: false };
|
|
1545
|
+
}
|
|
1546
|
+
const generatedNames = generatedExportNames(sidecarSrc);
|
|
1547
|
+
const isGenerated = generatedNames.has(fnName) || (fnName.includes(".") ? (() => {
|
|
1548
|
+
const [cls, method] = fnName.split(".", 2);
|
|
1549
|
+
return generatedNames.has(`${cls}_${method}`) || generatedNames.has(cls);
|
|
1550
|
+
})() : false);
|
|
1551
|
+
return {
|
|
1552
|
+
ok: true,
|
|
1553
|
+
sidecarPath,
|
|
1554
|
+
constraint: binding,
|
|
1555
|
+
generated: isGenerated
|
|
1556
|
+
};
|
|
1557
|
+
}
|
|
1558
|
+
function boundsOf(c) {
|
|
1559
|
+
const out = [];
|
|
1560
|
+
const visit2 = (p) => {
|
|
1561
|
+
if (p.op === "and") {
|
|
1562
|
+
p.args.forEach(visit2);
|
|
1563
|
+
return;
|
|
1564
|
+
}
|
|
1565
|
+
if (p.op !== "gt" && p.op !== "ge" && p.op !== "lt" && p.op !== "le") return;
|
|
1566
|
+
if (p.b.op !== "lit" || typeof p.b.value !== "number") return;
|
|
1567
|
+
if (p.a.op !== "var" && p.a.op !== "app") return;
|
|
1568
|
+
out.push({
|
|
1569
|
+
lower: p.op === "gt" || p.op === "ge",
|
|
1570
|
+
strict: p.op === "gt" || p.op === "lt",
|
|
1571
|
+
n: p.b.value,
|
|
1572
|
+
term: termToString(p.a)
|
|
1573
|
+
});
|
|
1574
|
+
};
|
|
1575
|
+
c.preds.forEach(visit2);
|
|
1576
|
+
return out;
|
|
1577
|
+
}
|
|
1578
|
+
function eqLitsOf(c) {
|
|
1579
|
+
const out = [];
|
|
1580
|
+
const visit2 = (p) => {
|
|
1581
|
+
if (p.op === "and") {
|
|
1582
|
+
p.args.forEach(visit2);
|
|
1583
|
+
return;
|
|
1584
|
+
}
|
|
1585
|
+
if (p.op !== "eq") return;
|
|
1586
|
+
let term;
|
|
1587
|
+
let value;
|
|
1588
|
+
if (p.a.op === "var" && p.b.op === "lit") {
|
|
1589
|
+
term = p.a;
|
|
1590
|
+
value = p.b.value;
|
|
1591
|
+
} else if (p.b.op === "var" && p.a.op === "lit") {
|
|
1592
|
+
term = p.b;
|
|
1593
|
+
value = p.a.value;
|
|
1594
|
+
}
|
|
1595
|
+
if (term !== void 0 && value !== void 0) {
|
|
1596
|
+
out.push({ term: termToString(term), value });
|
|
1597
|
+
}
|
|
1598
|
+
};
|
|
1599
|
+
c.preds.forEach(visit2);
|
|
1600
|
+
return out;
|
|
1601
|
+
}
|
|
1602
|
+
function boundExcludes(bounds, term, v2) {
|
|
1603
|
+
for (const b of bounds) {
|
|
1604
|
+
if (b.term !== term) continue;
|
|
1605
|
+
if (b.lower && (b.strict ? v2 <= b.n : v2 < b.n)) return true;
|
|
1606
|
+
if (!b.lower && (b.strict ? v2 >= b.n : v2 > b.n)) return true;
|
|
1607
|
+
}
|
|
1608
|
+
return false;
|
|
1609
|
+
}
|
|
1610
|
+
function crossBoundConflict(a, b) {
|
|
1611
|
+
const A = boundsOf(a);
|
|
1612
|
+
const B = boundsOf(b);
|
|
1613
|
+
for (const x of A) {
|
|
1614
|
+
for (const y of B) {
|
|
1615
|
+
if (x.term !== y.term || x.lower === y.lower) continue;
|
|
1616
|
+
const lo = x.lower ? x : y;
|
|
1617
|
+
const hi = x.lower ? y : x;
|
|
1618
|
+
if (lo.strict || hi.strict ? lo.n >= hi.n : lo.n > hi.n) return true;
|
|
1619
|
+
}
|
|
1620
|
+
}
|
|
1621
|
+
const eqA = eqLitsOf(a);
|
|
1622
|
+
const eqB = eqLitsOf(b);
|
|
1623
|
+
for (const x of eqA) {
|
|
1624
|
+
for (const y of eqB) {
|
|
1625
|
+
if (x.term !== y.term) continue;
|
|
1626
|
+
if (x.value !== y.value) return true;
|
|
1627
|
+
}
|
|
1628
|
+
}
|
|
1629
|
+
for (const eq2 of eqA) {
|
|
1630
|
+
if (typeof eq2.value === "number" && boundExcludes(B, eq2.term, eq2.value)) return true;
|
|
1631
|
+
}
|
|
1632
|
+
for (const eq2 of eqB) {
|
|
1633
|
+
if (typeof eq2.value === "number" && boundExcludes(A, eq2.term, eq2.value)) return true;
|
|
1634
|
+
}
|
|
1635
|
+
return false;
|
|
1636
|
+
}
|
|
1637
|
+
function conjoinOrConflict(prev, next, onConflict) {
|
|
1638
|
+
try {
|
|
1639
|
+
return andC(prev, next);
|
|
1640
|
+
} catch {
|
|
1641
|
+
onConflict();
|
|
1642
|
+
return prev;
|
|
1643
|
+
}
|
|
1644
|
+
}
|
|
1645
|
+
function effectiveInterface(source, fnName, opts = {}) {
|
|
1646
|
+
const refineSince = refineDiagCount();
|
|
1647
|
+
const sourceEntries = extractRefinesFromSource(source, fnName, opts);
|
|
1648
|
+
const sourceReturn = extractRefineReturnFromSource(source, fnName, opts);
|
|
1649
|
+
const sourceThrows = extractDeclaredThrows(source, fnName);
|
|
1650
|
+
const sidecar = loadSidecarBinding(source, fnName, opts);
|
|
1651
|
+
for (const d of takeRefineDiagsSince(refineSince)) {
|
|
1652
|
+
collectDiag2({ code: d.code, message: d.message, ...d.file ? { file: d.file } : {} });
|
|
1653
|
+
}
|
|
1654
|
+
const sidecarFn = sidecar.ok ? sidecar.constraint : void 0;
|
|
1655
|
+
const sidecarGenerated = sidecar.ok && sidecar.generated;
|
|
1656
|
+
const sidecarParams = sidecarFn ? fnConstraintToEntryReqs(sidecarFn) : [];
|
|
1657
|
+
const sidecarReturns = sidecarFn?.fn.returns;
|
|
1658
|
+
const sidecarThrowsKinds = throwConstraintToKinds(sidecarFn?.fn.throws);
|
|
1659
|
+
const mergedThrows = (() => {
|
|
1660
|
+
if (sourceThrows === "*" || sidecarThrowsKinds === "*") return "*";
|
|
1661
|
+
const set = /* @__PURE__ */ new Set([...sourceThrows ?? [], ...sidecarThrowsKinds ?? []]);
|
|
1662
|
+
return set.size > 0 ? [...set] : void 0;
|
|
1663
|
+
})();
|
|
1664
|
+
const hasHandwritten = sourceEntries.length > 0 || sourceReturn !== void 0 || sidecarFn !== void 0 && !sidecarGenerated;
|
|
1665
|
+
if (!hasHandwritten) {
|
|
1666
|
+
if (sidecarFn !== void 0 && sidecarGenerated) {
|
|
1667
|
+
return {
|
|
1668
|
+
fnName,
|
|
1669
|
+
params: sidecarParams,
|
|
1670
|
+
...sidecarReturns !== void 0 ? { returns: { constraint: sidecarReturns } } : {},
|
|
1671
|
+
...mergedThrows !== void 0 ? { throws: { kinds: mergedThrows } } : {},
|
|
1672
|
+
source: "generated"
|
|
1673
|
+
};
|
|
1674
|
+
}
|
|
1675
|
+
return void 0;
|
|
1676
|
+
}
|
|
1677
|
+
const params = /* @__PURE__ */ new Map();
|
|
1678
|
+
const conflictParams = [];
|
|
1679
|
+
for (const e of sourceEntries) {
|
|
1680
|
+
const prev = params.get(e.param);
|
|
1681
|
+
if (prev === void 0) {
|
|
1682
|
+
params.set(e.param, e.constraint);
|
|
1683
|
+
continue;
|
|
1684
|
+
}
|
|
1685
|
+
if (crossBoundConflict(prev, e.constraint) && !conflictParams.includes(e.param)) {
|
|
1686
|
+
conflictParams.push(e.param);
|
|
1687
|
+
}
|
|
1688
|
+
params.set(
|
|
1689
|
+
e.param,
|
|
1690
|
+
conjoinOrConflict(prev, e.constraint, () => {
|
|
1691
|
+
if (!conflictParams.includes(e.param)) conflictParams.push(e.param);
|
|
1692
|
+
})
|
|
1693
|
+
);
|
|
1694
|
+
}
|
|
1695
|
+
if (sidecarFn !== void 0 && !sidecarGenerated) {
|
|
1696
|
+
for (const { param, constraint } of sidecarParams) {
|
|
1697
|
+
const prev = params.get(param);
|
|
1698
|
+
if (prev !== void 0) {
|
|
1699
|
+
if (crossBoundConflict(prev, constraint) && !conflictParams.includes(param)) {
|
|
1700
|
+
conflictParams.push(param);
|
|
1701
|
+
}
|
|
1702
|
+
params.set(param, conjoinOrConflict(prev, constraint, () => {
|
|
1703
|
+
if (!conflictParams.includes(param)) conflictParams.push(param);
|
|
1704
|
+
}));
|
|
1705
|
+
} else {
|
|
1706
|
+
params.set(param, constraint);
|
|
1707
|
+
}
|
|
1708
|
+
}
|
|
1709
|
+
}
|
|
1710
|
+
const r1 = sourceReturn?.constraint;
|
|
1711
|
+
const r2 = sidecarFn !== void 0 && !sidecarGenerated ? sidecarReturns : void 0;
|
|
1712
|
+
let returnsC;
|
|
1713
|
+
let conflictReturns = false;
|
|
1714
|
+
if (r1 !== void 0 && r2 !== void 0) {
|
|
1715
|
+
if (crossBoundConflict(r1, r2)) conflictReturns = true;
|
|
1716
|
+
returnsC = conjoinOrConflict(r1, r2, () => {
|
|
1717
|
+
conflictReturns = true;
|
|
1718
|
+
});
|
|
1719
|
+
} else {
|
|
1720
|
+
returnsC = r1 ?? r2;
|
|
1721
|
+
}
|
|
1722
|
+
return {
|
|
1723
|
+
fnName,
|
|
1724
|
+
params: [...params.entries()].map(([param, constraint]) => ({ param, constraint })),
|
|
1725
|
+
...returnsC !== void 0 ? { returns: { constraint: returnsC } } : {},
|
|
1726
|
+
...mergedThrows !== void 0 ? { throws: { kinds: mergedThrows } } : {},
|
|
1727
|
+
source: "handwritten",
|
|
1728
|
+
...conflictParams.length > 0 || conflictReturns ? {
|
|
1729
|
+
conflict: {
|
|
1730
|
+
params: conflictParams,
|
|
1731
|
+
...conflictReturns ? { returns: true } : {}
|
|
1732
|
+
}
|
|
1733
|
+
} : {}
|
|
1734
|
+
};
|
|
1735
|
+
}
|
|
1736
|
+
var MAX_SIDECAR_FP_NODES = 64;
|
|
1737
|
+
function enqueueSidecarDeps(src, path, seen, queue) {
|
|
1738
|
+
let ast;
|
|
1739
|
+
try {
|
|
1740
|
+
ast = parseSource(src);
|
|
1741
|
+
} catch {
|
|
1742
|
+
return;
|
|
1743
|
+
}
|
|
1744
|
+
for (const stmt of ast.program.body) {
|
|
1745
|
+
if (stmt.type !== "ImportDeclaration") continue;
|
|
1746
|
+
const spec = stmt.source.value;
|
|
1747
|
+
if (!(spec.startsWith(".") || spec.startsWith("/"))) continue;
|
|
1748
|
+
if (!(spec.endsWith(".nudo.js") || spec.endsWith(".nudo.ts"))) continue;
|
|
1749
|
+
const depPath = resolveDepPath(path, spec);
|
|
1750
|
+
if (seen.has(depPath)) continue;
|
|
1751
|
+
seen.add(depPath);
|
|
1752
|
+
queue.push({ spec, from: path });
|
|
1753
|
+
}
|
|
1754
|
+
}
|
|
1755
|
+
function sidecarClosureFingerprint(fromFile, opts) {
|
|
1756
|
+
const { loadModule, autoBind, projectDir } = opts;
|
|
1757
|
+
if (!loadModule || !fromFile) return void 0;
|
|
1758
|
+
const sidecarPath = sidecarPathOf(fromFile);
|
|
1759
|
+
if (!sidecarAutoBindAllowed(sidecarPath, autoBind, projectDir)) return void 0;
|
|
1760
|
+
const spec = `./${sidecarPath.slice(sidecarPath.lastIndexOf("/") + 1)}`;
|
|
1761
|
+
const sidecarSrc = loadModule(spec, fromFile);
|
|
1762
|
+
if (sidecarSrc === void 0) return void 0;
|
|
1763
|
+
const parts = [];
|
|
1764
|
+
const seen = /* @__PURE__ */ new Set([sidecarPath]);
|
|
1765
|
+
const queue = [];
|
|
1766
|
+
enqueueSidecarDeps(sidecarSrc, sidecarPath, seen, queue);
|
|
1767
|
+
let truncated = false;
|
|
1768
|
+
while (queue.length > 0) {
|
|
1769
|
+
if (seen.size > MAX_SIDECAR_FP_NODES) {
|
|
1770
|
+
truncated = true;
|
|
1771
|
+
break;
|
|
1772
|
+
}
|
|
1773
|
+
const { spec: s, from } = queue.shift();
|
|
1774
|
+
const depPath = resolveDepPath(from, s);
|
|
1775
|
+
const depSrc = loadModule(s, from);
|
|
1776
|
+
parts.push(`${depPath}=${depSrc === void 0 ? "miss" : hashSource(depSrc)}`);
|
|
1777
|
+
if (depSrc !== void 0) enqueueSidecarDeps(depSrc, depPath, seen, queue);
|
|
1778
|
+
}
|
|
1779
|
+
parts.sort();
|
|
1780
|
+
const fp = `${hashSource(sidecarSrc)}|${parts.join(",")}`;
|
|
1781
|
+
return truncated ? `trunc:${fp}` : fp;
|
|
1782
|
+
}
|
|
1783
|
+
function litToString(v2) {
|
|
1784
|
+
if (typeof v2 === "string") return JSON.stringify(v2);
|
|
1785
|
+
return String(v2);
|
|
1786
|
+
}
|
|
1787
|
+
function lengthBound(p, op) {
|
|
1788
|
+
if (p.op !== op) return void 0;
|
|
1789
|
+
if (p.a.op !== "app" || p.a.fn !== "length") return void 0;
|
|
1790
|
+
if (p.b.op !== "lit" || typeof p.b.value !== "number") return void 0;
|
|
1791
|
+
return p.b.value;
|
|
1792
|
+
}
|
|
1793
|
+
function predToChain(p) {
|
|
1794
|
+
switch (p.op) {
|
|
1795
|
+
case "gt":
|
|
1796
|
+
case "ge":
|
|
1797
|
+
case "lt":
|
|
1798
|
+
case "le": {
|
|
1799
|
+
if (p.b.op !== "lit" || typeof p.b.value !== "number") break;
|
|
1800
|
+
if (p.a.op === "app" && p.a.fn === "length") {
|
|
1801
|
+
if (p.op === "ge") return `.min(${p.b.value})`;
|
|
1802
|
+
if (p.op === "le") return `.max(${p.b.value})`;
|
|
1803
|
+
break;
|
|
1804
|
+
}
|
|
1805
|
+
if (p.a.op === "var") return `.${p.op}(${p.b.value})`;
|
|
1806
|
+
break;
|
|
1807
|
+
}
|
|
1808
|
+
case "eq": {
|
|
1809
|
+
if (p.b.op === "lit" && p.a.op === "var") return `.eq(${litToString(p.b.value)})`;
|
|
1810
|
+
break;
|
|
1811
|
+
}
|
|
1812
|
+
case "and": {
|
|
1813
|
+
if (p.args.length === 2) {
|
|
1814
|
+
const [a, b] = [p.args[0], p.args[1]];
|
|
1815
|
+
for (const [x, y] of [
|
|
1816
|
+
[a, b],
|
|
1817
|
+
[b, a]
|
|
1818
|
+
]) {
|
|
1819
|
+
const ge2 = lengthBound(x, "ge");
|
|
1820
|
+
const le2 = lengthBound(y, "le");
|
|
1821
|
+
if (ge2 !== void 0 && ge2 === le2) return `.length(${ge2})`;
|
|
1822
|
+
}
|
|
1823
|
+
}
|
|
1824
|
+
return p.args.map(predToChain).join("");
|
|
1825
|
+
}
|
|
1826
|
+
default:
|
|
1827
|
+
break;
|
|
1828
|
+
}
|
|
1829
|
+
return `{${predToString(p)}}`;
|
|
1830
|
+
}
|
|
1831
|
+
function fmtConstraint(c) {
|
|
1832
|
+
if (c.fn) {
|
|
1833
|
+
const args = [
|
|
1834
|
+
`{ ${Object.entries(c.fn.params).map(([k, v2]) => `${k}: ${fmtConstraint(v2)}`).join(", ")} }`
|
|
1835
|
+
];
|
|
1836
|
+
if (c.fn.returns !== void 0) args.push(fmtConstraint(c.fn.returns));
|
|
1837
|
+
if (c.fn.throws !== void 0) args.push(`{ throws: ${fmtConstraint(c.fn.throws)} }`);
|
|
1838
|
+
return `fn(${args.join(", ")})`;
|
|
1839
|
+
}
|
|
1840
|
+
if (c.members) return `union(${c.members.map(fmtConstraint).join(", ")})`;
|
|
1841
|
+
if (c.fields) {
|
|
1842
|
+
const fs = Object.entries(c.fields).map(([k, f]) => {
|
|
1843
|
+
const opt = f.optional || f.constraint.isOptional ? "?" : "";
|
|
1844
|
+
return `${k}${opt}: ${fmtConstraint(f.constraint)}`;
|
|
1845
|
+
});
|
|
1846
|
+
return `shape({ ${fs.join(", ")} })`;
|
|
1847
|
+
}
|
|
1848
|
+
if (c.element) return `array(${fmtConstraint(c.element)})`;
|
|
1849
|
+
if (c.preds.length === 1 && c.preds[0].op === "eq" && c.preds[0].a.op === "var" && c.preds[0].b.op === "lit" && !isIntFlag(c)) {
|
|
1850
|
+
return `lit(${litToString(c.preds[0].b.value)})`;
|
|
1851
|
+
}
|
|
1852
|
+
let out = c.prim !== void 0 ? `${c.prim}()` : c.preds.length > 0 ? "number()" : "any()";
|
|
1853
|
+
if (isIntFlag(c)) out += ".int()";
|
|
1854
|
+
for (const p of c.preds) out += predToChain(p);
|
|
1855
|
+
if (c.isOptional) out += ".optional()";
|
|
1856
|
+
return out;
|
|
1857
|
+
}
|
|
1858
|
+
function formatConstraint(c) {
|
|
1859
|
+
return fmtConstraint(c);
|
|
1860
|
+
}
|
|
1861
|
+
function formatEffectiveInterfaceDisplay(eff) {
|
|
1862
|
+
const params = eff.params.map((p) => `${p.param}: ${formatConstraint(p.constraint)}`).join(", ");
|
|
1863
|
+
let s = `(${params})`;
|
|
1864
|
+
if (eff.returns) s += ` \u2192 ${formatConstraint(eff.returns.constraint)}`;
|
|
1865
|
+
return s;
|
|
1866
|
+
}
|
|
1867
|
+
function formatInterfaceTierLine(source) {
|
|
1868
|
+
return `\u25CF interface / ${source}`;
|
|
1869
|
+
}
|
|
1870
|
+
function interfaceTierOf(source, fnName, fromFile, opts = {}) {
|
|
1871
|
+
if (!localNamedExports(source).has(fnName)) return void 0;
|
|
1872
|
+
const eff = effectiveInterface(source, fnName, { fromFile, ...opts });
|
|
1873
|
+
const src = eff?.source ?? "implicit";
|
|
1874
|
+
if (eff && src !== "implicit") {
|
|
1875
|
+
return { source: src, display: formatEffectiveInterfaceDisplay(eff) };
|
|
1876
|
+
}
|
|
1877
|
+
return { source: src };
|
|
1878
|
+
}
|
|
1879
|
+
function interfaceSourceOf(source, fnName, fromFile, opts = {}) {
|
|
1880
|
+
return interfaceTierOf(source, fnName, fromFile, opts)?.source;
|
|
1881
|
+
}
|
|
1882
|
+
|
|
1883
|
+
// src/algebra/fn-fp.ts
|
|
1884
|
+
var fnFpCache = /* @__PURE__ */ new Map();
|
|
1885
|
+
var MAX_FN_FP_CACHE = 24;
|
|
1886
|
+
var MAX_FN_FP_SOURCE_CHARS = 1e6;
|
|
1887
|
+
function resetFnFpCache() {
|
|
1888
|
+
fnFpCache.clear();
|
|
1889
|
+
}
|
|
1890
|
+
function getFnNameAndBodies(decl) {
|
|
1891
|
+
const out = [];
|
|
1892
|
+
if (decl.type === "FunctionDeclaration" && decl.id) {
|
|
1893
|
+
out.push({ name: decl.id.name, body: decl.body, fnNode: decl });
|
|
1894
|
+
return out;
|
|
1895
|
+
}
|
|
1896
|
+
if (decl.type === "VariableDeclaration") {
|
|
1897
|
+
for (const d of decl.declarations) {
|
|
1898
|
+
if (d.id.type === "Identifier" && d.init && (d.init.type === "ArrowFunctionExpression" || d.init.type === "FunctionExpression")) {
|
|
1899
|
+
const comments = d.leadingComments;
|
|
1900
|
+
const first = comments?.[0];
|
|
1901
|
+
out.push({
|
|
1902
|
+
name: d.id.name,
|
|
1903
|
+
body: d.init.body,
|
|
1904
|
+
fnNode: d.init,
|
|
1905
|
+
declaratorStart: d.start ?? void 0,
|
|
1906
|
+
declaratorLeadStart: typeof first?.start === "number" ? first.start : void 0
|
|
1907
|
+
});
|
|
1908
|
+
}
|
|
1909
|
+
}
|
|
1910
|
+
}
|
|
1911
|
+
return out;
|
|
1912
|
+
}
|
|
1913
|
+
function leadingSliceStart(stmt, decl) {
|
|
1914
|
+
const comments = stmt.leadingComments ?? decl.leadingComments;
|
|
1915
|
+
const first = comments?.[0];
|
|
1916
|
+
if (first && typeof first.start === "number") return first.start;
|
|
1917
|
+
return stmt.start ?? decl.start ?? 0;
|
|
1918
|
+
}
|
|
1919
|
+
function collectSiblingRefs(node, known, out) {
|
|
1920
|
+
if (!node || typeof node !== "object") return;
|
|
1921
|
+
const obj = node;
|
|
1922
|
+
if (obj.type === "Identifier" && typeof obj.name === "string" && known.has(obj.name)) {
|
|
1923
|
+
out.add(obj.name);
|
|
1924
|
+
}
|
|
1925
|
+
for (const key of Object.keys(obj)) {
|
|
1926
|
+
if (key === "loc" || key === "start" || key === "end" || key === "leadingComments" || key === "trailingComments" || key === "innerComments") {
|
|
1927
|
+
continue;
|
|
1928
|
+
}
|
|
1929
|
+
if ((key === "key" || key === "property") && (obj.type === "MemberExpression" || obj.type === "ObjectProperty" || obj.type === "ObjectMethod" || obj.type === "ClassMethod" || obj.type === "ClassProperty") && obj.computed !== true) {
|
|
1930
|
+
continue;
|
|
1931
|
+
}
|
|
1932
|
+
if (key === "id" && (obj.type === "FunctionDeclaration" || obj.type === "FunctionExpression" || obj.type === "ClassDeclaration" || obj.type === "ClassExpression")) {
|
|
1933
|
+
continue;
|
|
1934
|
+
}
|
|
1935
|
+
const v2 = obj[key];
|
|
1936
|
+
if (Array.isArray(v2)) {
|
|
1937
|
+
for (const x of v2) collectSiblingRefs(x, known, out);
|
|
1938
|
+
} else if (v2 && typeof v2 === "object") {
|
|
1939
|
+
collectSiblingRefs(v2, known, out);
|
|
1940
|
+
}
|
|
1941
|
+
}
|
|
1942
|
+
}
|
|
1943
|
+
function computeFnFingerprints(source, file) {
|
|
1944
|
+
const top = [];
|
|
1945
|
+
const contextParts = [];
|
|
1946
|
+
for (const stmt of file.program.body) {
|
|
1947
|
+
let decl = stmt;
|
|
1948
|
+
if (stmt.type === "ExportNamedDeclaration" && stmt.declaration) {
|
|
1949
|
+
decl = stmt.declaration;
|
|
1950
|
+
} else if (stmt.type === "ExportDefaultDeclaration" && stmt.declaration) {
|
|
1951
|
+
decl = stmt.declaration;
|
|
1952
|
+
}
|
|
1953
|
+
const fnInfos = getFnNameAndBodies(decl);
|
|
1954
|
+
if (fnInfos.length > 0) {
|
|
1955
|
+
const lead = leadingSliceStart(stmt, decl);
|
|
1956
|
+
for (let i = 0; i < fnInfos.length; i++) {
|
|
1957
|
+
const info = fnInfos[i];
|
|
1958
|
+
const sliceStart = i === 0 ? lead : info.declaratorLeadStart ?? info.declaratorStart ?? info.fnNode.start ?? lead;
|
|
1959
|
+
const end = info.fnNode.end ?? info.declaratorStart ?? decl.end ?? stmt.end ?? sliceStart;
|
|
1960
|
+
top.push({
|
|
1961
|
+
name: info.name,
|
|
1962
|
+
slice: source.slice(sliceStart, Math.max(end, sliceStart)),
|
|
1963
|
+
fnNode: info.fnNode
|
|
1964
|
+
});
|
|
1965
|
+
}
|
|
1966
|
+
} else if (typeof stmt.start === "number" && typeof stmt.end === "number") {
|
|
1967
|
+
contextParts.push(source.slice(stmt.start, stmt.end));
|
|
1968
|
+
} else {
|
|
1969
|
+
contextParts.push(stmt.type);
|
|
1970
|
+
}
|
|
1971
|
+
}
|
|
1972
|
+
const context = hashSource(contextParts.join("\n "));
|
|
1973
|
+
const names = new Set(top.map((t) => t.name));
|
|
1974
|
+
const own = /* @__PURE__ */ new Map();
|
|
1975
|
+
for (const t of top) own.set(t.name, hashSource(t.slice));
|
|
1976
|
+
const edges = /* @__PURE__ */ new Map();
|
|
1977
|
+
for (const t of top) {
|
|
1978
|
+
const refs = /* @__PURE__ */ new Set();
|
|
1979
|
+
collectSiblingRefs(t.fnNode, names, refs);
|
|
1980
|
+
edges.set(t.name, refs);
|
|
1981
|
+
}
|
|
1982
|
+
const result = /* @__PURE__ */ new Map();
|
|
1983
|
+
for (const t of top) {
|
|
1984
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1985
|
+
const stack = [...edges.get(t.name) ?? []];
|
|
1986
|
+
const depParts = [];
|
|
1987
|
+
while (stack.length > 0) {
|
|
1988
|
+
const cur = stack.pop();
|
|
1989
|
+
if (seen.has(cur)) continue;
|
|
1990
|
+
seen.add(cur);
|
|
1991
|
+
depParts.push(`${cur}=${own.get(cur) ?? "?"}`);
|
|
1992
|
+
for (const next of edges.get(cur) ?? []) {
|
|
1993
|
+
if (!seen.has(next)) stack.push(next);
|
|
1994
|
+
}
|
|
1995
|
+
}
|
|
1996
|
+
depParts.sort();
|
|
1997
|
+
result.set(t.name, {
|
|
1998
|
+
own: own.get(t.name),
|
|
1999
|
+
deps: hashSource(`${depParts.join(",")} ${context}`),
|
|
2000
|
+
nRefs: seen.size
|
|
2001
|
+
});
|
|
2002
|
+
}
|
|
2003
|
+
return result;
|
|
2004
|
+
}
|
|
2005
|
+
function fnFingerprints(source, file) {
|
|
2006
|
+
if (source.length > MAX_FN_FP_SOURCE_CHARS) {
|
|
2007
|
+
return computeFnFingerprints(source, file);
|
|
2008
|
+
}
|
|
2009
|
+
const hit = fnFpCache.get(source);
|
|
2010
|
+
if (hit !== void 0) {
|
|
2011
|
+
fnFpCache.delete(source);
|
|
2012
|
+
fnFpCache.set(source, hit);
|
|
2013
|
+
return hit;
|
|
2014
|
+
}
|
|
2015
|
+
const fp = computeFnFingerprints(source, file);
|
|
2016
|
+
if (fnFpCache.size >= MAX_FN_FP_CACHE) {
|
|
2017
|
+
const oldest = fnFpCache.keys().next().value;
|
|
2018
|
+
if (oldest !== void 0) fnFpCache.delete(oldest);
|
|
2019
|
+
}
|
|
2020
|
+
fnFpCache.set(source, fp);
|
|
2021
|
+
return fp;
|
|
2022
|
+
}
|
|
2023
|
+
function generalizeSourceKeyPart(source, fnName, file) {
|
|
2024
|
+
if (!file) return `w:${hashSource(source)}`;
|
|
2025
|
+
const fp = fnFingerprints(source, file).get(fnName);
|
|
2026
|
+
if (fp) return `f:${fp.own}:${fp.deps}`;
|
|
2027
|
+
return `u:${hashSource(source)}`;
|
|
2028
|
+
}
|
|
2029
|
+
function hasAnyCallLike(node, depth = 0) {
|
|
2030
|
+
if (!node || typeof node !== "object") return false;
|
|
2031
|
+
if (depth > 80) return true;
|
|
2032
|
+
const obj = node;
|
|
2033
|
+
if (obj.type === "CallExpression" || obj.type === "NewExpression" || obj.type === "OptionalCallExpression" || obj.type === "ImportExpression") {
|
|
2034
|
+
return true;
|
|
2035
|
+
}
|
|
2036
|
+
for (const key of Object.keys(obj)) {
|
|
2037
|
+
if (key === "loc" || key === "start" || key === "end" || key === "leadingComments" || key === "trailingComments" || key === "innerComments") {
|
|
2038
|
+
continue;
|
|
2039
|
+
}
|
|
2040
|
+
const v2 = obj[key];
|
|
2041
|
+
if (Array.isArray(v2)) {
|
|
2042
|
+
for (const x of v2) if (hasAnyCallLike(x, depth + 1)) return true;
|
|
2043
|
+
} else if (v2 && typeof v2 === "object") {
|
|
2044
|
+
if (hasAnyCallLike(v2, depth + 1)) return true;
|
|
2045
|
+
}
|
|
2046
|
+
}
|
|
2047
|
+
return false;
|
|
2048
|
+
}
|
|
2049
|
+
function hasModuleSyntax(file) {
|
|
2050
|
+
for (const stmt of file.program.body) {
|
|
2051
|
+
if (stmt.type === "ImportDeclaration") return true;
|
|
2052
|
+
if (stmt.type === "ExportAllDeclaration") return true;
|
|
2053
|
+
if (stmt.type === "ExportNamedDeclaration" && stmt.source) return true;
|
|
2054
|
+
}
|
|
2055
|
+
return false;
|
|
2056
|
+
}
|
|
2057
|
+
function canSkipLiteralCallScan(source, file) {
|
|
2058
|
+
if (source.includes("@nudo:contract")) return false;
|
|
2059
|
+
if (hasModuleSyntax(file)) return false;
|
|
2060
|
+
if (hasAnyCallLike(file)) return false;
|
|
2061
|
+
const fps = fnFingerprints(source, file);
|
|
2062
|
+
for (const fp of fps.values()) {
|
|
2063
|
+
if (fp.nRefs > 0) return false;
|
|
2064
|
+
}
|
|
2065
|
+
return true;
|
|
2066
|
+
}
|
|
2067
|
+
|
|
2068
|
+
// src/algebra/load-deps-fp.ts
|
|
2069
|
+
var MAX_LOAD_DEP_NODES = 64;
|
|
2070
|
+
function extractAllLoadSpecs(source) {
|
|
2071
|
+
const specs = /* @__PURE__ */ new Set();
|
|
2072
|
+
for (const imp of extractNudoImports(source)) specs.add(imp.spec);
|
|
2073
|
+
const isPathLikeSpec = (spec) => spec.startsWith("./") || spec.startsWith("../") || spec.startsWith("/") || /\.(ts|js|mjs|cjs|tsx|jsx)$/.test(spec);
|
|
2074
|
+
const envRe = /@nudo:env\s+([^\n*]+)/g;
|
|
2075
|
+
let envM;
|
|
2076
|
+
while (envM = envRe.exec(source)) {
|
|
2077
|
+
for (const part of envM[1].split(",")) {
|
|
2078
|
+
const spec = part.trim().replace(/^['"]|['"]$/g, "");
|
|
2079
|
+
if (spec && isPathLikeSpec(spec)) specs.add(spec);
|
|
2080
|
+
}
|
|
2081
|
+
}
|
|
2082
|
+
const mockFromRe = /@nudo:mock-module\s+"[^"]+"\s+(?:\{[^}]*\}\s+)?from\s+["']([^"']+)["']/g;
|
|
2083
|
+
let mockM;
|
|
2084
|
+
while (mockM = mockFromRe.exec(source)) specs.add(mockM[1]);
|
|
2085
|
+
const patterns = [
|
|
2086
|
+
/\bfrom\s*['"]([^'"]+)['"]/g,
|
|
2087
|
+
/\brequire\s*\(\s*['"]([^'"]+)['"]\s*\)/g,
|
|
2088
|
+
/\bimport\s*\(\s*['"]([^'"]+)['"]\s*\)/g
|
|
2089
|
+
];
|
|
2090
|
+
for (const re of patterns) {
|
|
2091
|
+
let m;
|
|
2092
|
+
while (m = re.exec(source)) specs.add(m[1]);
|
|
2093
|
+
}
|
|
2094
|
+
return [...specs];
|
|
2095
|
+
}
|
|
2096
|
+
function sidecarSpecsOf(source) {
|
|
2097
|
+
return extractAllLoadSpecs(source).filter(
|
|
2098
|
+
(spec) => (spec.startsWith(".") || spec.startsWith("/")) && (spec.endsWith(".nudo.js") || spec.endsWith(".nudo.ts"))
|
|
2099
|
+
);
|
|
2100
|
+
}
|
|
2101
|
+
function loadModuleDepsFingerprint(source, loadModule, fromFile) {
|
|
2102
|
+
const parts = [];
|
|
2103
|
+
const paths = [];
|
|
2104
|
+
const contents = [];
|
|
2105
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2106
|
+
const queue = extractAllLoadSpecs(source).map(
|
|
2107
|
+
(spec) => ({ spec, from: fromFile })
|
|
2108
|
+
);
|
|
2109
|
+
let n = 0;
|
|
2110
|
+
let truncated = false;
|
|
2111
|
+
const finish = () => {
|
|
2112
|
+
parts.sort();
|
|
2113
|
+
return truncated ? { fp: `trunc:${parts.join(",")}`, paths, contents, truncated: true } : { fp: parts.join(","), paths, contents, truncated: false };
|
|
2114
|
+
};
|
|
2115
|
+
const absorbSidecarClosure = (entryFile) => {
|
|
2116
|
+
const sidecarPath = sidecarPathOf(entryFile);
|
|
2117
|
+
if (seen.has(sidecarPath)) return;
|
|
2118
|
+
const sidecarSpec = `./${sidecarPath.slice(sidecarPath.lastIndexOf("/") + 1)}`;
|
|
2119
|
+
const sidecarSrc = loadModule(sidecarSpec, entryFile);
|
|
2120
|
+
if (sidecarSrc === void 0) return;
|
|
2121
|
+
if (n >= MAX_LOAD_DEP_NODES) {
|
|
2122
|
+
truncated = true;
|
|
2123
|
+
return;
|
|
2124
|
+
}
|
|
2125
|
+
seen.add(sidecarPath);
|
|
2126
|
+
n++;
|
|
2127
|
+
parts.push(`sidecar:${sidecarPath}=${hashSource(sidecarSrc)}`);
|
|
2128
|
+
paths.push(sidecarPath);
|
|
2129
|
+
contents.push({ path: sidecarPath, content: sidecarSrc });
|
|
2130
|
+
const sidecarQueue = sidecarSpecsOf(
|
|
2131
|
+
sidecarSrc
|
|
2132
|
+
).map((spec) => ({ spec, from: sidecarPath }));
|
|
2133
|
+
while (sidecarQueue.length > 0) {
|
|
2134
|
+
if (n >= MAX_LOAD_DEP_NODES) {
|
|
2135
|
+
truncated = true;
|
|
2136
|
+
return;
|
|
2137
|
+
}
|
|
2138
|
+
const cur = sidecarQueue.shift();
|
|
2139
|
+
const path = resolveDepPath(cur.from, cur.spec);
|
|
2140
|
+
if (seen.has(path)) continue;
|
|
2141
|
+
seen.add(path);
|
|
2142
|
+
n++;
|
|
2143
|
+
const src = loadModule(cur.spec, cur.from);
|
|
2144
|
+
parts.push(`sidecar:${path}=${src === void 0 ? "miss" : hashSource(src)}`);
|
|
2145
|
+
paths.push(path);
|
|
2146
|
+
contents.push({ path, content: src ?? null });
|
|
2147
|
+
if (src === void 0) continue;
|
|
2148
|
+
for (const next of sidecarSpecsOf(src)) {
|
|
2149
|
+
const nextPath = resolveDepPath(path, next);
|
|
2150
|
+
if (!seen.has(nextPath)) sidecarQueue.push({ spec: next, from: path });
|
|
2151
|
+
}
|
|
2152
|
+
}
|
|
2153
|
+
};
|
|
2154
|
+
while (queue.length > 0) {
|
|
2155
|
+
if (n >= MAX_LOAD_DEP_NODES) {
|
|
2156
|
+
truncated = true;
|
|
2157
|
+
break;
|
|
2158
|
+
}
|
|
2159
|
+
const cur = queue.shift();
|
|
2160
|
+
const path = resolveDepPath(cur.from, cur.spec);
|
|
2161
|
+
if (seen.has(path)) continue;
|
|
2162
|
+
seen.add(path);
|
|
2163
|
+
n++;
|
|
2164
|
+
const src = loadModule(cur.spec, cur.from);
|
|
2165
|
+
parts.push(`${path}=${src === void 0 ? "miss" : hashSource(src)}`);
|
|
2166
|
+
paths.push(path);
|
|
2167
|
+
contents.push({ path, content: src ?? null });
|
|
2168
|
+
if (src === void 0) continue;
|
|
2169
|
+
for (const next of extractAllLoadSpecs(src)) {
|
|
2170
|
+
const nextPath = resolveDepPath(path, next);
|
|
2171
|
+
if (!seen.has(nextPath)) queue.push({ spec: next, from: path });
|
|
2172
|
+
}
|
|
2173
|
+
absorbSidecarClosure(path);
|
|
2174
|
+
}
|
|
2175
|
+
absorbSidecarClosure(fromFile);
|
|
2176
|
+
return finish();
|
|
2177
|
+
}
|
|
2178
|
+
|
|
2179
|
+
// src/algebra/generalize-key.ts
|
|
2180
|
+
function termKey(t, rename) {
|
|
2181
|
+
switch (t.op) {
|
|
2182
|
+
case "lit":
|
|
2183
|
+
return `L:${typeof t.value}:${String(t.value)}`;
|
|
2184
|
+
case "var":
|
|
2185
|
+
return `V:${rename?.get(t.id) ?? t.id}`;
|
|
2186
|
+
case "app":
|
|
2187
|
+
return `A:${t.fn}(${t.args.map((a) => termKey(a, rename)).join(",")})`;
|
|
2188
|
+
}
|
|
2189
|
+
}
|
|
2190
|
+
function predKey(p, rename) {
|
|
2191
|
+
switch (p.op) {
|
|
2192
|
+
case "true":
|
|
2193
|
+
return "T";
|
|
2194
|
+
case "false":
|
|
2195
|
+
return "F";
|
|
2196
|
+
case "eq":
|
|
2197
|
+
case "ne":
|
|
2198
|
+
case "lt":
|
|
2199
|
+
case "le":
|
|
2200
|
+
case "gt":
|
|
2201
|
+
case "ge":
|
|
2202
|
+
return `${p.op}(${termKey(p.a, rename)},${termKey(p.b, rename)})`;
|
|
2203
|
+
case "and":
|
|
2204
|
+
case "or": {
|
|
2205
|
+
const keys = p.args.map((a) => predKey(a, rename)).sort();
|
|
2206
|
+
return `${p.op}(${keys.join(",")})`;
|
|
2207
|
+
}
|
|
2208
|
+
case "not":
|
|
2209
|
+
return `not(${predKey(p.arg, rename)})`;
|
|
2210
|
+
case "typeof":
|
|
2211
|
+
return `typeof(${termKey(p.t, rename)},${p.type})`;
|
|
2212
|
+
}
|
|
2213
|
+
}
|
|
2214
|
+
function shapeKey(s, seen, rename) {
|
|
2215
|
+
switch (s.k) {
|
|
2216
|
+
case "never":
|
|
2217
|
+
case "any":
|
|
2218
|
+
case "unknown":
|
|
2219
|
+
return s.k;
|
|
2220
|
+
case "prim":
|
|
2221
|
+
return `p:${s.type}`;
|
|
2222
|
+
case "brand":
|
|
2223
|
+
return `b:${s.name}(${absKeyInner(s.shape, seen, rename)})`;
|
|
2224
|
+
case "eff":
|
|
2225
|
+
return `e:${s.eff}<${absKeyInner(s.inner, seen, rename)}>`;
|
|
2226
|
+
case "arr":
|
|
2227
|
+
return `arr(${absKeyInner(s.element, seen, rename)})`;
|
|
2228
|
+
case "tuple": {
|
|
2229
|
+
const els = s.elements.map((e) => absKeyInner(e, seen, rename)).join(",");
|
|
2230
|
+
const rest = s.rest ? `...${absKeyInner(s.rest, seen, rename)}` : "";
|
|
2231
|
+
return `tup[${els}${rest}]`;
|
|
2232
|
+
}
|
|
2233
|
+
case "fn": {
|
|
2234
|
+
const pts = (s.paramTypes ?? []).map((t) => absKeyInner(t, seen, rename)).join(",");
|
|
2235
|
+
const ret = s.returnType ? absKeyInner(s.returnType, seen, rename) : "?";
|
|
2236
|
+
const name = s.name ? `#${s.name}` : "";
|
|
2237
|
+
return `fn${name}(${s.params.join(",")}|${pts})=>${ret}`;
|
|
2238
|
+
}
|
|
2239
|
+
case "sum":
|
|
2240
|
+
return `sum(${s.members.map((m) => absKeyInner(m, seen, rename)).join("|")})`;
|
|
2241
|
+
case "obj": {
|
|
2242
|
+
const slots = Object.keys(s.slots).sort().map((k) => {
|
|
2243
|
+
const slot = s.slots[k];
|
|
2244
|
+
const flags = (slot.optional ? "?" : "") + (slot.readonly ? "r" : "");
|
|
2245
|
+
return `${k}${flags}:${absKeyInner(slot.value, seen, rename)}`;
|
|
2246
|
+
}).join(",");
|
|
2247
|
+
const idx = s.index ? `idx(${absKeyInner(s.index.key, seen, rename)}\u2192${absKeyInner(s.index.value, seen, rename)})` : "";
|
|
2248
|
+
const open = s.open ? "open" : "";
|
|
2249
|
+
return `obj{${slots}}${idx}${open}`;
|
|
2250
|
+
}
|
|
2251
|
+
}
|
|
2252
|
+
}
|
|
2253
|
+
function absKeyInner(a, seen, rename) {
|
|
2254
|
+
if (seen.has(a)) return "cycle";
|
|
2255
|
+
seen.add(a);
|
|
2256
|
+
const t = a.term ? `=${termKey(a.term, rename)}` : "";
|
|
2257
|
+
const p = a.pred ? `@${predKey(a.pred, rename)}` : "";
|
|
2258
|
+
return `${shapeKey(a.shape, seen, rename)}${t}${p}`;
|
|
2259
|
+
}
|
|
2260
|
+
function collectTermVars(t, acc) {
|
|
2261
|
+
if (t.op === "var") acc.add(t.id);
|
|
2262
|
+
else if (t.op === "app") for (const a of t.args) collectTermVars(a, acc);
|
|
2263
|
+
}
|
|
2264
|
+
function collectPredVars(p, acc) {
|
|
2265
|
+
switch (p.op) {
|
|
2266
|
+
case "true":
|
|
2267
|
+
case "false":
|
|
2268
|
+
return;
|
|
2269
|
+
case "eq":
|
|
2270
|
+
case "ne":
|
|
2271
|
+
case "lt":
|
|
2272
|
+
case "le":
|
|
2273
|
+
case "gt":
|
|
2274
|
+
case "ge":
|
|
2275
|
+
collectTermVars(p.a, acc);
|
|
2276
|
+
collectTermVars(p.b, acc);
|
|
2277
|
+
return;
|
|
2278
|
+
case "and":
|
|
2279
|
+
case "or":
|
|
2280
|
+
for (const a of p.args) collectPredVars(a, acc);
|
|
2281
|
+
return;
|
|
2282
|
+
case "not":
|
|
2283
|
+
collectPredVars(p.arg, acc);
|
|
2284
|
+
return;
|
|
2285
|
+
case "typeof":
|
|
2286
|
+
collectTermVars(p.t, acc);
|
|
2287
|
+
return;
|
|
2288
|
+
}
|
|
2289
|
+
}
|
|
2290
|
+
function collectAbsVars(a, acc, seen) {
|
|
2291
|
+
if (seen.has(a)) return;
|
|
2292
|
+
seen.add(a);
|
|
2293
|
+
if (a.term) collectTermVars(a.term, acc);
|
|
2294
|
+
if (a.pred) collectPredVars(a.pred, acc);
|
|
2295
|
+
const s = a.shape;
|
|
2296
|
+
switch (s.k) {
|
|
2297
|
+
case "brand":
|
|
2298
|
+
collectAbsVars(s.shape, acc, seen);
|
|
2299
|
+
return;
|
|
2300
|
+
case "eff":
|
|
2301
|
+
collectAbsVars(s.inner, acc, seen);
|
|
2302
|
+
return;
|
|
2303
|
+
case "arr":
|
|
2304
|
+
collectAbsVars(s.element, acc, seen);
|
|
2305
|
+
return;
|
|
2306
|
+
case "tuple":
|
|
2307
|
+
for (const e of s.elements) collectAbsVars(e, acc, seen);
|
|
2308
|
+
if (s.rest) collectAbsVars(s.rest, acc, seen);
|
|
2309
|
+
return;
|
|
2310
|
+
case "fn":
|
|
2311
|
+
for (const t of s.paramTypes ?? []) collectAbsVars(t, acc, seen);
|
|
2312
|
+
if (s.returnType) collectAbsVars(s.returnType, acc, seen);
|
|
2313
|
+
return;
|
|
2314
|
+
case "sum":
|
|
2315
|
+
for (const m of s.members) collectAbsVars(m, acc, seen);
|
|
2316
|
+
return;
|
|
2317
|
+
case "obj":
|
|
2318
|
+
for (const slot of Object.values(s.slots)) collectAbsVars(slot.value, acc, seen);
|
|
2319
|
+
if (s.index) {
|
|
2320
|
+
collectAbsVars(s.index.key, acc, seen);
|
|
2321
|
+
collectAbsVars(s.index.value, acc, seen);
|
|
2322
|
+
}
|
|
2323
|
+
return;
|
|
2324
|
+
default:
|
|
2325
|
+
return;
|
|
2326
|
+
}
|
|
2327
|
+
}
|
|
2328
|
+
function collectAbsFreeVars(a) {
|
|
2329
|
+
const acc = /* @__PURE__ */ new Set();
|
|
2330
|
+
collectAbsVars(a, acc, /* @__PURE__ */ new Set());
|
|
2331
|
+
return acc;
|
|
2332
|
+
}
|
|
2333
|
+
function renameTerm(t, map) {
|
|
2334
|
+
if (t.op === "var") {
|
|
2335
|
+
const to = map.get(t.id);
|
|
2336
|
+
return to === void 0 ? t : v(to);
|
|
2337
|
+
}
|
|
2338
|
+
if (t.op === "app") {
|
|
2339
|
+
return { op: "app", fn: t.fn, args: t.args.map((a) => renameTerm(a, map)) };
|
|
2340
|
+
}
|
|
2341
|
+
return t;
|
|
2342
|
+
}
|
|
2343
|
+
function renamePred(p, map) {
|
|
2344
|
+
switch (p.op) {
|
|
2345
|
+
case "true":
|
|
2346
|
+
case "false":
|
|
2347
|
+
return p;
|
|
2348
|
+
case "eq":
|
|
2349
|
+
case "ne":
|
|
2350
|
+
case "lt":
|
|
2351
|
+
case "le":
|
|
2352
|
+
case "gt":
|
|
2353
|
+
case "ge":
|
|
2354
|
+
return { op: p.op, a: renameTerm(p.a, map), b: renameTerm(p.b, map) };
|
|
2355
|
+
case "and":
|
|
2356
|
+
case "or":
|
|
2357
|
+
return { op: p.op, args: p.args.map((a) => renamePred(a, map)) };
|
|
2358
|
+
case "not":
|
|
2359
|
+
return { op: "not", arg: renamePred(p.arg, map) };
|
|
2360
|
+
case "typeof":
|
|
2361
|
+
return { op: "typeof", t: renameTerm(p.t, map), type: p.type };
|
|
2362
|
+
}
|
|
2363
|
+
}
|
|
2364
|
+
function renameAbs(a, map) {
|
|
2365
|
+
const out = {
|
|
2366
|
+
shape: renameShape(a.shape, map),
|
|
2367
|
+
conf: a.conf
|
|
2368
|
+
};
|
|
2369
|
+
if (a.term) out.term = renameTerm(a.term, map);
|
|
2370
|
+
if (a.pred) out.pred = renamePred(a.pred, map);
|
|
2371
|
+
return out;
|
|
2372
|
+
}
|
|
2373
|
+
function renameShape(s, map) {
|
|
2374
|
+
switch (s.k) {
|
|
2375
|
+
case "never":
|
|
2376
|
+
case "any":
|
|
2377
|
+
case "unknown":
|
|
2378
|
+
case "prim":
|
|
2379
|
+
return s;
|
|
2380
|
+
case "brand":
|
|
2381
|
+
return { k: "brand", name: s.name, shape: renameAbs(s.shape, map) };
|
|
2382
|
+
case "eff":
|
|
2383
|
+
return { k: "eff", eff: s.eff, inner: renameAbs(s.inner, map) };
|
|
2384
|
+
case "arr":
|
|
2385
|
+
return { k: "arr", element: renameAbs(s.element, map) };
|
|
2386
|
+
case "tuple": {
|
|
2387
|
+
const out = {
|
|
2388
|
+
k: "tuple",
|
|
2389
|
+
elements: s.elements.map((e) => renameAbs(e, map))
|
|
2390
|
+
};
|
|
2391
|
+
if (s.rest) out.rest = renameAbs(s.rest, map);
|
|
2392
|
+
return out;
|
|
2393
|
+
}
|
|
2394
|
+
case "fn": {
|
|
2395
|
+
const out = { k: "fn", params: s.params };
|
|
2396
|
+
if (s.name !== void 0) out.name = s.name;
|
|
2397
|
+
if (s.paramTypes) out.paramTypes = s.paramTypes.map((t) => renameAbs(t, map));
|
|
2398
|
+
if (s.returnType) out.returnType = renameAbs(s.returnType, map);
|
|
2399
|
+
return out;
|
|
2400
|
+
}
|
|
2401
|
+
case "sum":
|
|
2402
|
+
return { k: "sum", members: s.members.map((m) => renameAbs(m, map)) };
|
|
2403
|
+
case "obj": {
|
|
2404
|
+
const slots = {};
|
|
2405
|
+
for (const [k, slot] of Object.entries(s.slots)) {
|
|
2406
|
+
slots[k] = {
|
|
2407
|
+
value: renameAbs(slot.value, map),
|
|
2408
|
+
...slot.optional ? { optional: true } : {},
|
|
2409
|
+
...slot.readonly ? { readonly: true } : {}
|
|
2410
|
+
};
|
|
2411
|
+
}
|
|
2412
|
+
const out = { k: "obj", slots };
|
|
2413
|
+
if (s.index) {
|
|
2414
|
+
out.index = { key: renameAbs(s.index.key, map), value: renameAbs(s.index.value, map) };
|
|
2415
|
+
}
|
|
2416
|
+
if (s.open) out.open = true;
|
|
2417
|
+
return out;
|
|
2418
|
+
}
|
|
2419
|
+
}
|
|
2420
|
+
}
|
|
2421
|
+
function instantiateMemoKey(args, phi) {
|
|
2422
|
+
const acc = /* @__PURE__ */ new Set();
|
|
2423
|
+
for (const a of args) collectAbsVars(a, acc, /* @__PURE__ */ new Set());
|
|
2424
|
+
collectPredVars(phi, acc);
|
|
2425
|
+
const varOrder = [...acc].sort();
|
|
2426
|
+
const rename = new Map(varOrder.map((id, i) => [id, `\u03B1${i}`]));
|
|
2427
|
+
const key = `${args.map((a) => absKeyInner(a, /* @__PURE__ */ new Set(), rename)).join(";")}#${predKey(phi, rename)}`;
|
|
2428
|
+
return { key, varOrder };
|
|
2429
|
+
}
|
|
2430
|
+
function alphaRenameResult(result, fromOrder, toOrder) {
|
|
2431
|
+
if (fromOrder.length !== toOrder.length) return result;
|
|
2432
|
+
let same = true;
|
|
2433
|
+
for (let i = 0; i < fromOrder.length; i++) {
|
|
2434
|
+
if (fromOrder[i] !== toOrder[i]) {
|
|
2435
|
+
same = false;
|
|
2436
|
+
break;
|
|
2437
|
+
}
|
|
2438
|
+
}
|
|
2439
|
+
if (same) return result;
|
|
2440
|
+
const map = /* @__PURE__ */ new Map();
|
|
2441
|
+
for (let i = 0; i < fromOrder.length; i++) {
|
|
2442
|
+
map.set(fromOrder[i], toOrder[i]);
|
|
2443
|
+
}
|
|
2444
|
+
return renameAbs(result, map);
|
|
2445
|
+
}
|
|
2446
|
+
|
|
2447
|
+
// src/algebra/promote-scan.ts
|
|
2448
|
+
var HOF_ARR_METHODS = /* @__PURE__ */ new Set(["map", "filter", "reduce", "flatMap"]);
|
|
2449
|
+
function alphaOfTerm(t, ctx) {
|
|
2450
|
+
if (t?.op === "var" && ctx.alphaIds.has(t.id)) return t;
|
|
2451
|
+
ctx.freshSeq.n += 1;
|
|
2452
|
+
const id = `T${ctx.freshSeq.n}`;
|
|
2453
|
+
ctx.alphaIds.add(id);
|
|
2454
|
+
return v(id);
|
|
2455
|
+
}
|
|
2456
|
+
function rawTermOf(arg, ctx) {
|
|
2457
|
+
if (arg?.type === "Identifier") {
|
|
2458
|
+
const lt2 = ctx.localTerms.get(arg.name);
|
|
2459
|
+
if (lt2) return lt2;
|
|
2460
|
+
if (ctx.paramTermId.has(arg.name)) {
|
|
2461
|
+
return v(ctx.paramTermId.get(arg.name));
|
|
2462
|
+
}
|
|
2463
|
+
}
|
|
2464
|
+
if (arg?.type === "NumericLiteral") return { op: "lit", value: arg.value };
|
|
2465
|
+
if (arg?.type === "StringLiteral") return { op: "lit", value: arg.value };
|
|
2466
|
+
if (arg?.type === "BooleanLiteral") return { op: "lit", value: arg.value };
|
|
2467
|
+
return { op: "var", id: "_" };
|
|
2468
|
+
}
|
|
2469
|
+
function alphaTermOf(arg, ctx) {
|
|
2470
|
+
if (arg?.type === "Identifier") {
|
|
2471
|
+
const lt2 = ctx.localTerms.get(arg.name);
|
|
2472
|
+
if (lt2) return lt2;
|
|
2473
|
+
if (ctx.paramTermId.has(arg.name)) {
|
|
2474
|
+
return v(ctx.paramTermId.get(arg.name));
|
|
2475
|
+
}
|
|
2476
|
+
}
|
|
2477
|
+
return alphaOfTerm(void 0, ctx);
|
|
2478
|
+
}
|
|
2479
|
+
function promoteShape(ctx, param, shape2, loc, recordSite = true) {
|
|
2480
|
+
const prev = ctx.shapes.get(param);
|
|
2481
|
+
if (!prev) return;
|
|
2482
|
+
if (prev.shape.k !== "any" && prev.shape.k !== "unknown") return;
|
|
2483
|
+
const next = { shape: shape2, term: prev.term, pred: prev.pred, conf: "path" };
|
|
2484
|
+
ctx.shapes.set(param, next);
|
|
2485
|
+
ctx.promotedShapes.set(param, shape2);
|
|
2486
|
+
if (shape2.k === "fn") {
|
|
2487
|
+
if (!ctx.fnRels.has(param) && !ctx.entryShapes.has(param)) {
|
|
2488
|
+
ctx.fnRels.set(param, { abs: { ...next }, source: "promote" });
|
|
2489
|
+
}
|
|
2490
|
+
if (recordSite) {
|
|
2491
|
+
ctx.sites.push({
|
|
2492
|
+
param,
|
|
2493
|
+
argTerms: [],
|
|
2494
|
+
result: shape2.returnType ?? next,
|
|
2495
|
+
loc
|
|
2496
|
+
});
|
|
2497
|
+
}
|
|
2498
|
+
} else {
|
|
2499
|
+
if (!ctx.fnRels.has(param) && !ctx.entryShapes.has(param)) {
|
|
2500
|
+
ctx.entryShapes.set(param, { abs: { ...next }, source: "promote" });
|
|
2501
|
+
}
|
|
2502
|
+
}
|
|
2503
|
+
}
|
|
2504
|
+
function promoteAsArr(ctx, name, loc) {
|
|
2505
|
+
if (!ctx.params.has(name)) return;
|
|
2506
|
+
const prev = ctx.shapes.get(name);
|
|
2507
|
+
if (!prev) return;
|
|
2508
|
+
if (prev.shape.k !== "any" && prev.shape.k !== "unknown") return;
|
|
2509
|
+
const element = prev.term ? abs(prev.shape, prev.term, prev.pred, "path") : abs({ k: "any" }, void 0, void 0, "path");
|
|
2510
|
+
promoteShape(ctx, name, { k: "arr", element }, loc);
|
|
2511
|
+
}
|
|
2512
|
+
function promoteAsFn(ctx, name, args, loc) {
|
|
2513
|
+
if (!ctx.params.has(name)) return;
|
|
2514
|
+
const prev = ctx.shapes.get(name);
|
|
2515
|
+
if (!prev) return;
|
|
2516
|
+
if (prev.shape.k !== "any" && prev.shape.k !== "unknown") {
|
|
2517
|
+
if (prev.shape.k === "fn") {
|
|
2518
|
+
ctx.sites.push({
|
|
2519
|
+
param: name,
|
|
2520
|
+
argTerms: args.map((a) => rawTermOf(a, ctx)),
|
|
2521
|
+
result: prev.shape.returnType ?? prev,
|
|
2522
|
+
loc
|
|
2523
|
+
});
|
|
2524
|
+
}
|
|
2525
|
+
return;
|
|
2526
|
+
}
|
|
2527
|
+
const beta = v(`B:${name}`);
|
|
2528
|
+
const paramTypes = args.map((a) => abs({ k: "any" }, alphaTermOf(a, ctx), void 0, "path"));
|
|
2529
|
+
const ret = abs({ k: "any" }, beta, void 0, "path");
|
|
2530
|
+
const fnShape = {
|
|
2531
|
+
k: "fn",
|
|
2532
|
+
params: args.map((_, i) => `x${i}`),
|
|
2533
|
+
paramTypes,
|
|
2534
|
+
returnType: ret
|
|
2535
|
+
};
|
|
2536
|
+
promoteShape(ctx, name, fnShape, loc, false);
|
|
2537
|
+
ctx.sites.push({
|
|
2538
|
+
param: name,
|
|
2539
|
+
argTerms: args.map((a) => rawTermOf(a, ctx)),
|
|
2540
|
+
result: ret,
|
|
2541
|
+
loc
|
|
2542
|
+
});
|
|
2543
|
+
}
|
|
2544
|
+
function promoteCb(ctx, cbName, method, argTerms, loc) {
|
|
2545
|
+
if (!ctx.params.has(cbName)) return;
|
|
2546
|
+
const prev = ctx.shapes.get(cbName);
|
|
2547
|
+
if (!prev) return;
|
|
2548
|
+
if (prev.shape.k !== "any" && prev.shape.k !== "unknown") return;
|
|
2549
|
+
let paramTypes;
|
|
2550
|
+
let returnType;
|
|
2551
|
+
if (method === "filter") {
|
|
2552
|
+
paramTypes = argTerms.map((t) => abs({ k: "any" }, t, void 0, "path"));
|
|
2553
|
+
returnType = abs({ k: "prim", type: "boolean" }, void 0, void 0, "path");
|
|
2554
|
+
} else {
|
|
2555
|
+
paramTypes = argTerms.map((t) => abs({ k: "any" }, t, void 0, "path"));
|
|
2556
|
+
returnType = abs({ k: "any" }, v(`B:${cbName}`), void 0, "path");
|
|
2557
|
+
}
|
|
2558
|
+
const fnShape = {
|
|
2559
|
+
k: "fn",
|
|
2560
|
+
params: argTerms.map((_, i) => `x${i}`),
|
|
2561
|
+
paramTypes,
|
|
2562
|
+
returnType
|
|
2563
|
+
};
|
|
2564
|
+
promoteShape(ctx, cbName, fnShape, loc, false);
|
|
2565
|
+
ctx.sites.push({
|
|
2566
|
+
param: cbName,
|
|
2567
|
+
argTerms,
|
|
2568
|
+
result: returnType,
|
|
2569
|
+
loc
|
|
2570
|
+
});
|
|
2571
|
+
}
|
|
2572
|
+
function locOf(n) {
|
|
2573
|
+
const l = n.loc;
|
|
2574
|
+
return l ? { line: l.start.line, column: l.start.column } : void 0;
|
|
2575
|
+
}
|
|
2576
|
+
function visit(node, ctx) {
|
|
2577
|
+
if (!node || typeof node !== "object") return;
|
|
2578
|
+
const n = node;
|
|
2579
|
+
if (n.type === "FunctionDeclaration" || n.type === "ClassDeclaration") return;
|
|
2580
|
+
switch (n.type) {
|
|
2581
|
+
case "CallExpression": {
|
|
2582
|
+
const callee = n.callee;
|
|
2583
|
+
const args = n.arguments ?? [];
|
|
2584
|
+
if (callee.type === "MemberExpression" && callee.computed !== true) {
|
|
2585
|
+
const prop = callee.property;
|
|
2586
|
+
const method = prop?.name;
|
|
2587
|
+
if (method) {
|
|
2588
|
+
if (callee.object?.type === "Identifier" && HOF_ARR_METHODS.has(method)) {
|
|
2589
|
+
promoteAsArr(ctx, callee.object.name, locOf(node));
|
|
2590
|
+
}
|
|
2591
|
+
if (HOF_ARR_METHODS.has(method)) {
|
|
2592
|
+
const cb = args[0];
|
|
2593
|
+
if (cb?.type === "Identifier" && ctx.params.has(cb.name)) {
|
|
2594
|
+
const elemTerm = callee.object?.type === "Identifier" && ctx.paramTermId.has(callee.object.name) ? v(ctx.paramTermId.get(callee.object.name)) : alphaOfTerm(void 0, ctx);
|
|
2595
|
+
if (method === "reduce") {
|
|
2596
|
+
promoteCb(ctx, cb.name, "reduce", [alphaTermOf(args[1], ctx), elemTerm], locOf(node));
|
|
2597
|
+
} else {
|
|
2598
|
+
promoteCb(ctx, cb.name, method, [elemTerm], locOf(node));
|
|
2599
|
+
}
|
|
2600
|
+
}
|
|
2601
|
+
}
|
|
2602
|
+
}
|
|
2603
|
+
} else if (callee.type === "Identifier") {
|
|
2604
|
+
promoteAsFn(ctx, callee.name, args, locOf(node));
|
|
2605
|
+
}
|
|
2606
|
+
break;
|
|
2607
|
+
}
|
|
2608
|
+
case "ForOfStatement": {
|
|
2609
|
+
const right = n.right;
|
|
2610
|
+
let saved;
|
|
2611
|
+
let bindName;
|
|
2612
|
+
if (right?.type === "Identifier" && ctx.paramTermId.has(right.name)) {
|
|
2613
|
+
promoteAsArr(ctx, right.name, locOf(node));
|
|
2614
|
+
const left = n.left;
|
|
2615
|
+
const bind = left?.type === "VariableDeclaration" ? left.declarations?.[0]?.id?.name : left?.type === "Identifier" ? left.name : void 0;
|
|
2616
|
+
if (bind) {
|
|
2617
|
+
bindName = bind;
|
|
2618
|
+
saved = ctx.localTerms.get(bind);
|
|
2619
|
+
ctx.localTerms.set(bind, v(ctx.paramTermId.get(right.name)));
|
|
2620
|
+
}
|
|
2621
|
+
}
|
|
2622
|
+
for (const key of Object.keys(n)) {
|
|
2623
|
+
if (key === "loc" || key === "start" || key === "end") continue;
|
|
2624
|
+
const v2 = n[key];
|
|
2625
|
+
if (Array.isArray(v2)) {
|
|
2626
|
+
for (const item of v2) {
|
|
2627
|
+
if (item && typeof item === "object" && "type" in item) visit(item, ctx);
|
|
2628
|
+
}
|
|
2629
|
+
} else if (v2 && typeof v2 === "object" && "type" in v2) {
|
|
2630
|
+
visit(v2, ctx);
|
|
2631
|
+
}
|
|
2632
|
+
}
|
|
2633
|
+
if (bindName !== void 0) {
|
|
2634
|
+
if (saved !== void 0) ctx.localTerms.set(bindName, saved);
|
|
2635
|
+
else ctx.localTerms.delete(bindName);
|
|
2636
|
+
}
|
|
2637
|
+
return;
|
|
2638
|
+
}
|
|
2639
|
+
default:
|
|
2640
|
+
break;
|
|
2641
|
+
}
|
|
2642
|
+
for (const key of Object.keys(n)) {
|
|
2643
|
+
if (key === "loc" || key === "start" || key === "end") continue;
|
|
2644
|
+
const v2 = n[key];
|
|
2645
|
+
if (Array.isArray(v2)) {
|
|
2646
|
+
for (const item of v2) {
|
|
2647
|
+
if (item && typeof item === "object" && "type" in item) visit(item, ctx);
|
|
2648
|
+
}
|
|
2649
|
+
} else if (v2 && typeof v2 === "object" && "type" in v2) {
|
|
2650
|
+
visit(v2, ctx);
|
|
2651
|
+
}
|
|
2652
|
+
}
|
|
2653
|
+
}
|
|
2654
|
+
function scanPromotions(body, params, paramTermIds, initialShapes) {
|
|
2655
|
+
const res = {
|
|
2656
|
+
promotedShapes: /* @__PURE__ */ new Map(),
|
|
2657
|
+
fnRels: /* @__PURE__ */ new Map(),
|
|
2658
|
+
entryShapes: /* @__PURE__ */ new Map(),
|
|
2659
|
+
sites: []
|
|
2660
|
+
};
|
|
2661
|
+
const ctx = {
|
|
2662
|
+
params: new Set(params),
|
|
2663
|
+
paramTermId: new Map(params.map((p, i) => [p, paramTermIds[i] ?? `A${i + 1}`])),
|
|
2664
|
+
shapes: new Map(initialShapes),
|
|
2665
|
+
promotedShapes: res.promotedShapes,
|
|
2666
|
+
localTerms: /* @__PURE__ */ new Map(),
|
|
2667
|
+
freshSeq: { n: 0 },
|
|
2668
|
+
alphaIds: new Set(paramTermIds),
|
|
2669
|
+
fnRels: res.fnRels,
|
|
2670
|
+
entryShapes: res.entryShapes,
|
|
2671
|
+
sites: res.sites
|
|
2672
|
+
};
|
|
2673
|
+
visit(body, ctx);
|
|
2674
|
+
return res;
|
|
2675
|
+
}
|
|
2676
|
+
|
|
2677
|
+
// src/algebra/generalize.ts
|
|
2678
|
+
var bRunMemo = /* @__PURE__ */ new Map();
|
|
2679
|
+
function bPathRunOf(source, modules, inject) {
|
|
2680
|
+
const mKey = `${source}|${runTranspiledOptionsMemoKey({ ...inject ?? {}, ...modules ? { modules } : {} })}`;
|
|
2681
|
+
if (bRunMemo.size >= 256) {
|
|
2682
|
+
const oldest = bRunMemo.keys().next().value;
|
|
2683
|
+
if (oldest !== void 0) bRunMemo.delete(oldest);
|
|
2684
|
+
}
|
|
2685
|
+
let run = bRunMemo.get(mKey);
|
|
2686
|
+
if (run === void 0) {
|
|
2687
|
+
const merged = modules ?? inject?.modules;
|
|
2688
|
+
const r = tryRunTranspiled(source, {
|
|
2689
|
+
...inject ?? {},
|
|
2690
|
+
mode: "analyze",
|
|
2691
|
+
...merged ? { modules: merged } : {}
|
|
2692
|
+
});
|
|
2693
|
+
if (r === void 0) return void 0;
|
|
2694
|
+
run = r;
|
|
2695
|
+
bRunMemo.set(mKey, run);
|
|
2696
|
+
}
|
|
2697
|
+
return run;
|
|
2698
|
+
}
|
|
2699
|
+
function ctorParamCountOf(fileAst, clsName) {
|
|
2700
|
+
for (const stmt of fileAst.program.body) {
|
|
2701
|
+
if ((stmt.type === "ClassDeclaration" || stmt.type === "ExportNamedDeclaration") && "declaration" in stmt) {
|
|
2702
|
+
const decl = stmt.type === "ExportNamedDeclaration" ? stmt.declaration : stmt;
|
|
2703
|
+
const c = decl;
|
|
2704
|
+
if (c.type === "ClassDeclaration" && c.id?.name === clsName) {
|
|
2705
|
+
for (const m of c.body?.body ?? []) {
|
|
2706
|
+
const mem = m;
|
|
2707
|
+
if (mem.kind === "constructor") return mem.params?.length ?? 0;
|
|
2708
|
+
}
|
|
2709
|
+
return 0;
|
|
2710
|
+
}
|
|
2711
|
+
}
|
|
2712
|
+
}
|
|
2713
|
+
return 0;
|
|
2714
|
+
}
|
|
2715
|
+
function bodyReferencesName(body, name) {
|
|
2716
|
+
let found = false;
|
|
2717
|
+
const visit2 = (n) => {
|
|
2718
|
+
if (found || !n || typeof n !== "object") return;
|
|
2719
|
+
const o = n;
|
|
2720
|
+
if (o.type === "Identifier" && o.name === name) {
|
|
2721
|
+
found = true;
|
|
2722
|
+
return;
|
|
2723
|
+
}
|
|
2724
|
+
for (const key of Object.keys(o)) {
|
|
2725
|
+
if (key === "loc" || key === "start" || key === "end" || key === "tokens") continue;
|
|
2726
|
+
if (o.type === "MemberExpression" && key === "property" && o.computed !== true) {
|
|
2727
|
+
continue;
|
|
2728
|
+
}
|
|
2729
|
+
const v2 = o[key];
|
|
2730
|
+
if (Array.isArray(v2)) {
|
|
2731
|
+
for (const item of v2) if (item && typeof item === "object") visit2(item);
|
|
2732
|
+
} else if (v2 && typeof v2 === "object") {
|
|
2733
|
+
visit2(v2);
|
|
2734
|
+
}
|
|
2735
|
+
}
|
|
2736
|
+
};
|
|
2737
|
+
visit2(body);
|
|
2738
|
+
return found;
|
|
2739
|
+
}
|
|
2740
|
+
var generalizeMemo = /* @__PURE__ */ new Map();
|
|
2741
|
+
var memoKeyDeps = /* @__PURE__ */ new Map();
|
|
2742
|
+
var memoDepIndex = /* @__PURE__ */ new Map();
|
|
2743
|
+
var loadModuleIds = /* @__PURE__ */ new WeakMap();
|
|
2744
|
+
var nextLoadModuleId = 1;
|
|
2745
|
+
var MAX_GENERALIZE_MEMO = 1024;
|
|
2746
|
+
function resetGeneralizeMemo() {
|
|
2747
|
+
generalizeMemo.clear();
|
|
2748
|
+
memoKeyDeps.clear();
|
|
2749
|
+
memoDepIndex.clear();
|
|
2750
|
+
resetFnFpCache();
|
|
2751
|
+
resetHashSourceCache();
|
|
2752
|
+
}
|
|
2753
|
+
function getGeneralizeMemoSize() {
|
|
2754
|
+
return generalizeMemo.size;
|
|
2755
|
+
}
|
|
2756
|
+
function unindexMemoKey(key) {
|
|
2757
|
+
const deps = memoKeyDeps.get(key);
|
|
2758
|
+
if (!deps) return;
|
|
2759
|
+
for (const p of deps) {
|
|
2760
|
+
const set = memoDepIndex.get(p);
|
|
2761
|
+
if (!set) continue;
|
|
2762
|
+
set.delete(key);
|
|
2763
|
+
if (set.size === 0) memoDepIndex.delete(p);
|
|
2764
|
+
}
|
|
2765
|
+
memoKeyDeps.delete(key);
|
|
2766
|
+
}
|
|
2767
|
+
function evictGeneralizeMemoForPaths(paths) {
|
|
2768
|
+
let n = 0;
|
|
2769
|
+
for (const raw of paths) {
|
|
2770
|
+
const p = normPath(raw);
|
|
2771
|
+
const keys = memoDepIndex.get(p);
|
|
2772
|
+
if (!keys) continue;
|
|
2773
|
+
for (const key of [...keys]) {
|
|
2774
|
+
generalizeMemo.delete(key);
|
|
2775
|
+
unindexMemoKey(key);
|
|
2776
|
+
n++;
|
|
2777
|
+
}
|
|
2778
|
+
}
|
|
2779
|
+
return n;
|
|
2780
|
+
}
|
|
2781
|
+
function loadModuleId(fn2) {
|
|
2782
|
+
if (!fn2) return 0;
|
|
2783
|
+
let id = loadModuleIds.get(fn2);
|
|
2784
|
+
if (id === void 0) {
|
|
2785
|
+
id = nextLoadModuleId++;
|
|
2786
|
+
loadModuleIds.set(fn2, id);
|
|
2787
|
+
}
|
|
2788
|
+
return id;
|
|
2789
|
+
}
|
|
2790
|
+
function refineDepsFingerprint(source, refine) {
|
|
2791
|
+
if (!refine?.loadModule || !refine.fromFile) {
|
|
2792
|
+
return { fp: "-", paths: [], contents: [], truncated: false };
|
|
2793
|
+
}
|
|
2794
|
+
return loadModuleDepsFingerprint(source, refine.loadModule, refine.fromFile);
|
|
2795
|
+
}
|
|
2796
|
+
var moduleMapIds = /* @__PURE__ */ new WeakMap();
|
|
2797
|
+
var moduleMapIdSeq = 0;
|
|
2798
|
+
function moduleMapId(m) {
|
|
2799
|
+
if (!m) return "-";
|
|
2800
|
+
let id = moduleMapIds.get(m);
|
|
2801
|
+
if (id === void 0) {
|
|
2802
|
+
id = ++moduleMapIdSeq;
|
|
2803
|
+
moduleMapIds.set(m, id);
|
|
2804
|
+
}
|
|
2805
|
+
return `m${id}`;
|
|
2806
|
+
}
|
|
2807
|
+
function generalizeMemoKey(fnName, source, opts) {
|
|
2808
|
+
const r = opts.refine;
|
|
2809
|
+
const budget = opts.budget ?? defaultLeakBudget;
|
|
2810
|
+
const deps = opts.depsFp ?? (r ? refineDepsFingerprint(source, r) : { fp: "-", paths: [], contents: [], truncated: false });
|
|
2811
|
+
const sc = opts.sidecarFp ?? (r?.loadModule && r.fromFile ? sidecarClosureFingerprint(r.fromFile, r) : void 0);
|
|
2812
|
+
const scPath = sc !== void 0 && !sc.startsWith("trunc:") && r?.fromFile ? normPath(sidecarPathOf(r.fromFile)) : void 0;
|
|
2813
|
+
const srcPart = generalizeSourceKeyPart(source, fnName, opts.file);
|
|
2814
|
+
const key = [
|
|
2815
|
+
srcPart,
|
|
2816
|
+
fnName,
|
|
2817
|
+
opts.label ?? "A",
|
|
2818
|
+
r ? `${loadModuleId(r.loadModule)}:${r.fromFile ?? ""}` : "-",
|
|
2819
|
+
deps.fp,
|
|
2820
|
+
sc ?? "-",
|
|
2821
|
+
`${budget.maxDepth}/${budget.maxNodes}`,
|
|
2822
|
+
moduleMapId(opts.modules),
|
|
2823
|
+
runTranspiledOptionsMemoKey(opts.inject)
|
|
2824
|
+
].join("|");
|
|
2825
|
+
return {
|
|
2826
|
+
key,
|
|
2827
|
+
depPaths: scPath !== void 0 ? [...deps.paths, scPath] : deps.paths,
|
|
2828
|
+
truncated: deps.truncated || (sc?.startsWith("trunc:") ?? false)
|
|
2829
|
+
};
|
|
2830
|
+
}
|
|
2831
|
+
function generalizeMemoGet(key) {
|
|
2832
|
+
if (!generalizeMemo.has(key)) return null;
|
|
2833
|
+
const v2 = generalizeMemo.get(key);
|
|
2834
|
+
generalizeMemo.delete(key);
|
|
2835
|
+
generalizeMemo.set(key, v2);
|
|
2836
|
+
return v2;
|
|
2837
|
+
}
|
|
2838
|
+
function generalizeMemoSet(key, value, depPaths) {
|
|
2839
|
+
if (generalizeMemo.size >= MAX_GENERALIZE_MEMO) {
|
|
2840
|
+
const oldest = generalizeMemo.keys().next().value;
|
|
2841
|
+
if (oldest !== void 0) {
|
|
2842
|
+
generalizeMemo.delete(oldest);
|
|
2843
|
+
unindexMemoKey(oldest);
|
|
2844
|
+
}
|
|
2845
|
+
}
|
|
2846
|
+
generalizeMemo.set(key, value);
|
|
2847
|
+
if (depPaths.length > 0) {
|
|
2848
|
+
memoKeyDeps.set(key, depPaths);
|
|
2849
|
+
for (const p of depPaths) {
|
|
2850
|
+
let set = memoDepIndex.get(p);
|
|
2851
|
+
if (!set) {
|
|
2852
|
+
set = /* @__PURE__ */ new Set();
|
|
2853
|
+
memoDepIndex.set(p, set);
|
|
2854
|
+
}
|
|
2855
|
+
set.add(key);
|
|
2856
|
+
}
|
|
2857
|
+
}
|
|
2858
|
+
}
|
|
2859
|
+
function isCacheableAbs(a) {
|
|
2860
|
+
return a.conf === "exact" || a.conf === "path" || a.conf === "widened" || a.conf === "mock";
|
|
2861
|
+
}
|
|
2862
|
+
function extractFn(source, fnName, fileAst) {
|
|
2863
|
+
const file = fileAst ?? parseSource(source);
|
|
2864
|
+
const env = emptyEnv();
|
|
2865
|
+
const formalsByName = /* @__PURE__ */ new Map();
|
|
2866
|
+
for (const stmt of file.program.body) {
|
|
2867
|
+
let decl = stmt;
|
|
2868
|
+
if (stmt.type === "ExportNamedDeclaration" && stmt.declaration) {
|
|
2869
|
+
decl = stmt.declaration;
|
|
2870
|
+
}
|
|
2871
|
+
if (stmt.type === "ExportDefaultDeclaration" && stmt.declaration) {
|
|
2872
|
+
decl = stmt.declaration;
|
|
2873
|
+
}
|
|
2874
|
+
if (decl.type === "FunctionDeclaration" && decl.id) {
|
|
2875
|
+
const formals = formalParamsFromNodes(decl.params);
|
|
2876
|
+
env.fns.set(decl.id.name, {
|
|
2877
|
+
params: formalParamDisplayNames(formals),
|
|
2878
|
+
body: decl.body,
|
|
2879
|
+
async: decl.async === true
|
|
2880
|
+
});
|
|
2881
|
+
formalsByName.set(decl.id.name, formals);
|
|
2882
|
+
if (stmt.type === "ExportDefaultDeclaration") {
|
|
2883
|
+
env.fns.set("default", env.fns.get(decl.id.name));
|
|
2884
|
+
formalsByName.set("default", formals);
|
|
2885
|
+
}
|
|
2886
|
+
}
|
|
2887
|
+
if (decl.type === "ClassDeclaration" && decl.id?.name) {
|
|
2888
|
+
const cname = decl.id.name;
|
|
2889
|
+
const body = decl.body?.body ?? [];
|
|
2890
|
+
for (const m of body) {
|
|
2891
|
+
const mem = m;
|
|
2892
|
+
const isMethod = mem.type === "MethodDefinition" || mem.type === "ClassMethod" || mem.type === "TSDeclareMethod";
|
|
2893
|
+
if (!isMethod || !mem.body) continue;
|
|
2894
|
+
if (mem.kind && mem.kind !== "method") continue;
|
|
2895
|
+
const keyName = mem.key?.type === "Identifier" ? mem.key.name : void 0;
|
|
2896
|
+
if (!keyName) continue;
|
|
2897
|
+
const fullName = `${cname}.${keyName}`;
|
|
2898
|
+
const formals = formalParamsFromNodes(mem.params ?? []);
|
|
2899
|
+
env.fns.set(fullName, {
|
|
2900
|
+
params: formalParamDisplayNames(formals),
|
|
2901
|
+
body: mem.body,
|
|
2902
|
+
async: mem.async === true
|
|
2903
|
+
});
|
|
2904
|
+
formalsByName.set(fullName, formals);
|
|
2905
|
+
}
|
|
2906
|
+
}
|
|
2907
|
+
if (decl.type === "VariableDeclaration") {
|
|
2908
|
+
for (const d of decl.declarations) {
|
|
2909
|
+
if (d.id.type === "Identifier" && d.init && (d.init.type === "ArrowFunctionExpression" || d.init.type === "FunctionExpression")) {
|
|
2910
|
+
const init = d.init;
|
|
2911
|
+
const formals = formalParamsFromNodes(init.params);
|
|
2912
|
+
env.fns.set(d.id.name, {
|
|
2913
|
+
params: formalParamDisplayNames(formals),
|
|
2914
|
+
body: init.body,
|
|
2915
|
+
async: init.async === true
|
|
2916
|
+
});
|
|
2917
|
+
formalsByName.set(d.id.name, formals);
|
|
2918
|
+
}
|
|
2919
|
+
}
|
|
2920
|
+
}
|
|
2921
|
+
if (stmt.type === "ExportDefaultDeclaration" && (decl.type === "ArrowFunctionExpression" || decl.type === "FunctionExpression" || decl.type === "FunctionDeclaration" && !decl.id)) {
|
|
2922
|
+
const init = decl;
|
|
2923
|
+
const formals = formalParamsFromNodes(init.params);
|
|
2924
|
+
env.fns.set("default", {
|
|
2925
|
+
params: formalParamDisplayNames(formals),
|
|
2926
|
+
body: init.body,
|
|
2927
|
+
async: init.async === true
|
|
2928
|
+
});
|
|
2929
|
+
formalsByName.set("default", formals);
|
|
2930
|
+
}
|
|
2931
|
+
}
|
|
2932
|
+
const registerFnNode = (key, node) => {
|
|
2933
|
+
if (!node?.body) return;
|
|
2934
|
+
if (node.type !== "FunctionExpression" && node.type !== "ArrowFunctionExpression" && node.type !== "ObjectMethod" && node.type !== "ClassMethod" && node.type !== "FunctionDeclaration") {
|
|
2935
|
+
return;
|
|
2936
|
+
}
|
|
2937
|
+
const formals = formalParamsFromNodes(node.params ?? []);
|
|
2938
|
+
env.fns.set(key, {
|
|
2939
|
+
params: formalParamDisplayNames(formals),
|
|
2940
|
+
body: node.body,
|
|
2941
|
+
async: node.async === true
|
|
2942
|
+
});
|
|
2943
|
+
formalsByName.set(key, formals);
|
|
2944
|
+
};
|
|
2945
|
+
const ident = (n) => {
|
|
2946
|
+
const id = n;
|
|
2947
|
+
if (!id) return void 0;
|
|
2948
|
+
if (id.type === "Identifier" && typeof id.name === "string") return id.name;
|
|
2949
|
+
if (id.type === "StringLiteral" || id.type === "NumericLiteral") return String(id.value);
|
|
2950
|
+
return void 0;
|
|
2951
|
+
};
|
|
2952
|
+
const isExportsTarget = (n) => {
|
|
2953
|
+
const id = n;
|
|
2954
|
+
if (!id) return false;
|
|
2955
|
+
if (id.type === "Identifier") return id.name === "exports" || id.name === "module";
|
|
2956
|
+
if (id.type === "MemberExpression") {
|
|
2957
|
+
return id.object?.name === "module" && id.property?.name === "exports";
|
|
2958
|
+
}
|
|
2959
|
+
return false;
|
|
2960
|
+
};
|
|
2961
|
+
for (const stmt of file.program.body) {
|
|
2962
|
+
if (stmt.type !== "ExpressionStatement") continue;
|
|
2963
|
+
const expr = stmt.expression;
|
|
2964
|
+
if (!expr || expr.type !== "AssignmentExpression" || expr.operator !== "=") continue;
|
|
2965
|
+
const left = expr.left;
|
|
2966
|
+
const right = expr.right;
|
|
2967
|
+
if (!left || !right) continue;
|
|
2968
|
+
const propName = left.property ? ident(left.property) : void 0;
|
|
2969
|
+
const isModuleExportsIdent = left.type === "MemberExpression" && left.object?.name === "module" && left.property?.name === "exports" || left.type === "Identifier" && left.name === "exports";
|
|
2970
|
+
const isExportsMember = left.type === "MemberExpression" && left.object && (left.object.name === "exports" || left.object.object?.name === "module" && left.object.property?.name === "exports");
|
|
2971
|
+
if (isModuleExportsIdent) {
|
|
2972
|
+
if (right.type === "ObjectExpression") {
|
|
2973
|
+
for (const p of right.properties ?? []) {
|
|
2974
|
+
const prop = p;
|
|
2975
|
+
const keyName = ident(prop.key);
|
|
2976
|
+
if (!keyName) continue;
|
|
2977
|
+
if (prop.type === "ObjectMethod" || prop.type === "ClassMethod") {
|
|
2978
|
+
registerFnNode(keyName, prop);
|
|
2979
|
+
} else {
|
|
2980
|
+
registerFnNode(keyName, prop.value);
|
|
2981
|
+
}
|
|
2982
|
+
}
|
|
2983
|
+
} else if (right.type === "Identifier") {
|
|
2984
|
+
const n = ident(right);
|
|
2985
|
+
if (n) {
|
|
2986
|
+
}
|
|
2987
|
+
} else {
|
|
2988
|
+
registerFnNode("default", right);
|
|
2989
|
+
}
|
|
2990
|
+
continue;
|
|
2991
|
+
}
|
|
2992
|
+
if (isExportsMember && propName) {
|
|
2993
|
+
registerFnNode(propName, right);
|
|
2994
|
+
}
|
|
2995
|
+
}
|
|
2996
|
+
const fn2 = env.fns.get(fnName);
|
|
2997
|
+
if (!fn2) return void 0;
|
|
2998
|
+
return { params: fn2.params, body: fn2.body, env, formals: formalsByName.get(fnName) ?? [] };
|
|
2999
|
+
}
|
|
3000
|
+
function generalizeFromAst(fnName, source, opts = {}) {
|
|
3001
|
+
const { key, depPaths, truncated } = generalizeMemoKey(fnName, source, opts);
|
|
3002
|
+
if (!truncated) {
|
|
3003
|
+
const cached = generalizeMemoGet(key);
|
|
3004
|
+
if (cached !== null) {
|
|
3005
|
+
return cached;
|
|
3006
|
+
}
|
|
3007
|
+
}
|
|
3008
|
+
const result = generalizeFromAstUncached(fnName, source, opts);
|
|
3009
|
+
if (!truncated) {
|
|
3010
|
+
generalizeMemoSet(key, result, depPaths);
|
|
3011
|
+
}
|
|
3012
|
+
return result;
|
|
3013
|
+
}
|
|
3014
|
+
function generalizeFromAstUncached(fnName, source, opts = {}) {
|
|
3015
|
+
const extracted = extractFn(source, fnName, opts.file);
|
|
3016
|
+
if (!extracted) return void 0;
|
|
3017
|
+
const { params, body, env, formals } = extracted;
|
|
3018
|
+
const budget = opts.budget ?? defaultLeakBudget;
|
|
3019
|
+
const label = opts.label ?? "A";
|
|
3020
|
+
const typeParams = params.map((p, i) => ({
|
|
3021
|
+
id: `${label}${i + 1}`,
|
|
3022
|
+
// 无约束参数 = any(任意 JS 值),不是 unknown(分析无信息)
|
|
3023
|
+
value: abs({ k: "any" }, v(`${label}${i + 1}`), pTrue, "path")
|
|
3024
|
+
}));
|
|
3025
|
+
let entryReqs;
|
|
3026
|
+
const refineEntryShapes = /* @__PURE__ */ new Map();
|
|
3027
|
+
if (opts.refine) {
|
|
3028
|
+
try {
|
|
3029
|
+
const r = opts.refine;
|
|
3030
|
+
const hasDirective = source.includes("@nudo:contract") || source.includes("@nudo:contract");
|
|
3031
|
+
const sc = opts.sidecarFp ?? (r.loadModule && r.fromFile ? sidecarClosureFingerprint(r.fromFile, r) : void 0);
|
|
3032
|
+
const eff = hasDirective || sc !== void 0 ? effectiveInterface(source, fnName, r) : void 0;
|
|
3033
|
+
const reqs = eff?.params ?? [];
|
|
3034
|
+
if (reqs.length > 0) {
|
|
3035
|
+
entryReqs = reqs.map((e) => ({
|
|
3036
|
+
param: e.param,
|
|
3037
|
+
pred: instantiateConstraint(e.constraint, e.param)
|
|
3038
|
+
}));
|
|
3039
|
+
for (const e of reqs) {
|
|
3040
|
+
const idx = params.indexOf(e.param);
|
|
3041
|
+
if (idx >= 0) {
|
|
3042
|
+
const entryAbs = constraintToEntryAbs(e.constraint, e.param);
|
|
3043
|
+
typeParams[idx] = {
|
|
3044
|
+
id: typeParams[idx].id,
|
|
3045
|
+
value: entryAbs
|
|
3046
|
+
};
|
|
3047
|
+
refineEntryShapes.set(e.param, {
|
|
3048
|
+
abs: snapshotAbs(entryAbs),
|
|
3049
|
+
source: "refine"
|
|
3050
|
+
});
|
|
3051
|
+
}
|
|
3052
|
+
}
|
|
3053
|
+
}
|
|
3054
|
+
} catch {
|
|
3055
|
+
}
|
|
3056
|
+
}
|
|
3057
|
+
const instMemo = /* @__PURE__ */ new Map();
|
|
3058
|
+
const alphaIds = typeParams.map((t) => t.id);
|
|
3059
|
+
const promoteScan = scanPromotions(
|
|
3060
|
+
body,
|
|
3061
|
+
params,
|
|
3062
|
+
typeParams.map((t) => t.id),
|
|
3063
|
+
new Map(params.map((p, i) => [p, typeParams[i].value]))
|
|
3064
|
+
);
|
|
3065
|
+
const fileAst = opts.file ?? parseSource(source);
|
|
3066
|
+
const importSpecByLocal = /* @__PURE__ */ new Map();
|
|
3067
|
+
for (const stmt of fileAst.program.body) {
|
|
3068
|
+
if (stmt.type === "ImportDeclaration") {
|
|
3069
|
+
for (const s of stmt.specifiers) {
|
|
3070
|
+
importSpecByLocal.set(s.local.name, stmt.source.value ?? "");
|
|
3071
|
+
}
|
|
3072
|
+
}
|
|
3073
|
+
}
|
|
3074
|
+
const unresolvableImports = [...importSpecByLocal.keys()].some((n) => {
|
|
3075
|
+
if (!bodyReferencesName(body, n)) return false;
|
|
3076
|
+
const spec = importSpecByLocal.get(n);
|
|
3077
|
+
return spec === void 0 || !(opts.modules && Object.prototype.hasOwnProperty.call(opts.modules, spec));
|
|
3078
|
+
});
|
|
3079
|
+
const inject = opts.inject;
|
|
3080
|
+
const hasMocks = inject && Object.keys(inject.mocks ?? {}).length > 0;
|
|
3081
|
+
const hasEnv = inject && Object.keys(inject.envGlobals ?? {}).length > 0;
|
|
3082
|
+
const hasReps = inject && Object.keys(inject.replacements ?? {}).length > 0;
|
|
3083
|
+
const hasFnMockDirective = /@nudo:mock\s+\w+\s*(?:=|from\b)/.test(source);
|
|
3084
|
+
const hasModMockDirective = /@nudo:mock-module\b/.test(source);
|
|
3085
|
+
const hasModInject = inject && inject.modules && Object.keys(inject.modules).length > 0;
|
|
3086
|
+
const mockGated = hasFnMockDirective && !hasMocks || hasModMockDirective && !hasModInject;
|
|
3087
|
+
const envGatedSource = /@nudo:env\b/.test(source) && !hasEnv;
|
|
3088
|
+
const boundNames = formals.flatMap(
|
|
3089
|
+
(f) => f.kind === "pattern" ? f.bound : [f.name]
|
|
3090
|
+
);
|
|
3091
|
+
const envGated = envGatedSource && freeIdentifiers(body, boundNames).size > 0;
|
|
3092
|
+
const replaceGated = /@nudo:replace\b/.test(source) && !hasReps;
|
|
3093
|
+
const bEligible = !unresolvableImports && !mockGated && !envGated && !replaceGated;
|
|
3094
|
+
const run = (args, phi = pTrue) => {
|
|
3095
|
+
const { key, varOrder } = instantiateMemoKey(args, phi);
|
|
3096
|
+
const hit = instMemo.get(key);
|
|
3097
|
+
if (hit !== void 0) {
|
|
3098
|
+
return alphaRenameResult(hit.result, hit.varOrder, varOrder);
|
|
3099
|
+
}
|
|
3100
|
+
let result;
|
|
3101
|
+
if (bEligible) {
|
|
3102
|
+
try {
|
|
3103
|
+
const bRun = bPathRunOf(source, opts.modules, opts.inject);
|
|
3104
|
+
if (!bRun) {
|
|
3105
|
+
} else if (fnName.includes(".")) {
|
|
3106
|
+
const [clsName, methodName] = fnName.split(".", 2);
|
|
3107
|
+
const clsAbs = bRun[clsName ?? ""];
|
|
3108
|
+
if (clsAbs && typeof clsAbs === "object" && "shape" in clsAbs) {
|
|
3109
|
+
const nCtor = ctorParamCountOf(fileAst, clsName ?? "");
|
|
3110
|
+
const inst = $new(clsAbs, Array.from({ length: nCtor }, () => abs({ k: "any" }, void 0, pTrue, "path")));
|
|
3111
|
+
result = withExecPhi(phi, () => $invoke(inst, methodName ?? "", args));
|
|
3112
|
+
}
|
|
3113
|
+
} else if (fnName in bRun) {
|
|
3114
|
+
const bArgs = args.map((a, i) => {
|
|
3115
|
+
const shape2 = promoteScan.promotedShapes.get(params[i]);
|
|
3116
|
+
if (shape2 && (a.shape.k === "any" || a.shape.k === "unknown")) {
|
|
3117
|
+
return { shape: shape2, term: a.term, pred: a.pred, conf: "path" };
|
|
3118
|
+
}
|
|
3119
|
+
return a;
|
|
3120
|
+
});
|
|
3121
|
+
result = callTranspiledExportFull(bRun, fnName, bArgs, { phi }).result;
|
|
3122
|
+
}
|
|
3123
|
+
} catch {
|
|
3124
|
+
}
|
|
3125
|
+
}
|
|
3126
|
+
if (result === void 0) {
|
|
3127
|
+
result = abs({ k: "unknown" }, void 0, void 0, "opaque");
|
|
3128
|
+
}
|
|
3129
|
+
if (isCacheableAbs(result)) {
|
|
3130
|
+
instMemo.set(key, { result, varOrder });
|
|
3131
|
+
}
|
|
3132
|
+
return result;
|
|
3133
|
+
};
|
|
3134
|
+
const symbolic = run(
|
|
3135
|
+
typeParams.map((t) => t.value),
|
|
3136
|
+
// 入口契约进 Φ,让 body 内的单调性可传播
|
|
3137
|
+
entryReqs && entryReqs.length > 0 ? entryReqs.length === 1 ? entryReqs[0].pred : { op: "and", args: entryReqs.map((r) => r.pred) } : pTrue
|
|
3138
|
+
);
|
|
3139
|
+
let fnRels;
|
|
3140
|
+
let entryShapes;
|
|
3141
|
+
let hofSites;
|
|
3142
|
+
if (symbolic.conf !== "opaque") {
|
|
3143
|
+
if (refineEntryShapes.size > 0) {
|
|
3144
|
+
entryShapes = new Map(refineEntryShapes);
|
|
3145
|
+
for (const [param, rec] of refineEntryShapes) {
|
|
3146
|
+
if (rec.abs.shape.k !== "fn") continue;
|
|
3147
|
+
if (!fnRels) fnRels = /* @__PURE__ */ new Map();
|
|
3148
|
+
fnRels.set(param, { abs: snapshotAbs(rec.abs), source: rec.source });
|
|
3149
|
+
}
|
|
3150
|
+
}
|
|
3151
|
+
for (const [param, rec] of promoteScan.fnRels) {
|
|
3152
|
+
if (refineEntryShapes.has(param)) continue;
|
|
3153
|
+
if (!fnRels) fnRels = /* @__PURE__ */ new Map();
|
|
3154
|
+
fnRels.set(param, { abs: snapshotAbs(rec.abs), source: rec.source });
|
|
3155
|
+
}
|
|
3156
|
+
for (const [param, rec] of promoteScan.entryShapes) {
|
|
3157
|
+
if (refineEntryShapes.has(param)) continue;
|
|
3158
|
+
if (!entryShapes) entryShapes = /* @__PURE__ */ new Map();
|
|
3159
|
+
entryShapes.set(param, { abs: snapshotAbs(rec.abs), source: rec.source });
|
|
3160
|
+
}
|
|
3161
|
+
if (promoteScan.sites.length > 0) {
|
|
3162
|
+
hofSites = promoteScan.sites.map((s) => ({
|
|
3163
|
+
...s,
|
|
3164
|
+
argTerms: [...s.argTerms],
|
|
3165
|
+
result: snapshotAbs(s.result)
|
|
3166
|
+
}));
|
|
3167
|
+
}
|
|
3168
|
+
}
|
|
3169
|
+
return {
|
|
3170
|
+
name: fnName,
|
|
3171
|
+
params,
|
|
3172
|
+
typeParams,
|
|
3173
|
+
symbolic,
|
|
3174
|
+
instantiate: (args, phi) => run(args, phi ?? pTrue),
|
|
3175
|
+
display: formatPoly(
|
|
3176
|
+
fnName,
|
|
3177
|
+
params,
|
|
3178
|
+
typeParams,
|
|
3179
|
+
symbolic,
|
|
3180
|
+
entryReqs,
|
|
3181
|
+
entryShapes,
|
|
3182
|
+
fnRels
|
|
3183
|
+
),
|
|
3184
|
+
...formals.length > 0 ? { formals } : {},
|
|
3185
|
+
...entryReqs ? { entryReqs } : {},
|
|
3186
|
+
...fnRels ? { fnRels } : {},
|
|
3187
|
+
...entryShapes ? { entryShapes } : {},
|
|
3188
|
+
...hofSites ? { hofSites } : {}
|
|
3189
|
+
};
|
|
3190
|
+
}
|
|
3191
|
+
function formatPoly(name, params, typeParams, symbolic, entryReqs, entryShapes, fnRels) {
|
|
3192
|
+
const reqByParam = new Map((entryReqs ?? []).map((r) => [r.param, r.pred]));
|
|
3193
|
+
const ps = params.map((p, i) => {
|
|
3194
|
+
const id = typeParams[i]?.id ?? "unknown";
|
|
3195
|
+
const pred = reqByParam.get(p);
|
|
3196
|
+
const promoted = entryShapes?.get(p)?.abs ?? fnRels?.get(p)?.abs;
|
|
3197
|
+
if (promoted) {
|
|
3198
|
+
const slot = formatShapeSlot(promoted);
|
|
3199
|
+
if (pred && pred.op !== "true") {
|
|
3200
|
+
return `${p}: ${slot} where ${predToString(pred)}`;
|
|
3201
|
+
}
|
|
3202
|
+
return `${p}: ${slot}`;
|
|
3203
|
+
}
|
|
3204
|
+
if (pred && pred.op !== "true") {
|
|
3205
|
+
return `${p}: ${id} where ${predToString(pred)}`;
|
|
3206
|
+
}
|
|
3207
|
+
return `${p}: ${id}`;
|
|
3208
|
+
}).join(", ");
|
|
3209
|
+
const ret = formatShapeSlot(symbolic);
|
|
3210
|
+
const predPart = symbolic.pred && symbolic.pred.op !== "true" ? ` where ${predToString(symbolic.pred)}` : "";
|
|
3211
|
+
return `${name}: (${ps}) => ${ret}${predPart}`;
|
|
3212
|
+
}
|
|
3213
|
+
function generalizeAll(source, opts = {}) {
|
|
3214
|
+
const file = parseSource(source);
|
|
3215
|
+
const names = [];
|
|
3216
|
+
for (const stmt of file.program.body) {
|
|
3217
|
+
if (stmt.type === "FunctionDeclaration" && stmt.id) names.push(stmt.id.name);
|
|
3218
|
+
if (stmt.type === "VariableDeclaration") {
|
|
3219
|
+
for (const d of stmt.declarations) {
|
|
3220
|
+
if (d.id.type === "Identifier" && d.init && (d.init.type === "ArrowFunctionExpression" || d.init.type === "FunctionExpression")) {
|
|
3221
|
+
names.push(d.id.name);
|
|
3222
|
+
}
|
|
3223
|
+
}
|
|
3224
|
+
}
|
|
3225
|
+
}
|
|
3226
|
+
const out = [];
|
|
3227
|
+
for (const n of names) {
|
|
3228
|
+
const g = generalizeFromAst(n, source, opts);
|
|
3229
|
+
if (g) out.push(g);
|
|
3230
|
+
}
|
|
3231
|
+
return out;
|
|
3232
|
+
}
|
|
3233
|
+
function listFunctionNames(source) {
|
|
3234
|
+
const file = parseSource(source);
|
|
3235
|
+
const names = [];
|
|
3236
|
+
const push = (n) => {
|
|
3237
|
+
if (n && !names.includes(n)) names.push(n);
|
|
3238
|
+
};
|
|
3239
|
+
const walk = (decl, stmtType) => {
|
|
3240
|
+
if (!decl) return;
|
|
3241
|
+
if (decl.type === "FunctionDeclaration") {
|
|
3242
|
+
const id = decl.id;
|
|
3243
|
+
if (id?.name) push(id.name);
|
|
3244
|
+
else if (stmtType === "ExportDefaultDeclaration") push("default");
|
|
3245
|
+
return;
|
|
3246
|
+
}
|
|
3247
|
+
if (decl.type === "VariableDeclaration") {
|
|
3248
|
+
for (const d of decl.declarations) {
|
|
3249
|
+
if (d.id?.type === "Identifier" && d.init && (d.init.type === "ArrowFunctionExpression" || d.init.type === "FunctionExpression")) {
|
|
3250
|
+
push(d.id.name);
|
|
3251
|
+
}
|
|
3252
|
+
}
|
|
3253
|
+
return;
|
|
3254
|
+
}
|
|
3255
|
+
if (decl.type === "ArrowFunctionExpression" || decl.type === "FunctionExpression") {
|
|
3256
|
+
push("default");
|
|
3257
|
+
}
|
|
3258
|
+
};
|
|
3259
|
+
for (const stmt of file.program.body) {
|
|
3260
|
+
if (stmt.type === "FunctionDeclaration" || stmt.type === "VariableDeclaration") {
|
|
3261
|
+
walk(stmt, stmt.type);
|
|
3262
|
+
continue;
|
|
3263
|
+
}
|
|
3264
|
+
if (stmt.type === "ExportNamedDeclaration" || stmt.type === "ExportDefaultDeclaration") {
|
|
3265
|
+
walk(stmt.declaration, stmt.type);
|
|
3266
|
+
}
|
|
3267
|
+
}
|
|
3268
|
+
return names;
|
|
3269
|
+
}
|
|
3270
|
+
function buildArgsFromAssume(source, fnName, assumeIds) {
|
|
3271
|
+
const extracted = extractFn(source, fnName);
|
|
3272
|
+
if (!extracted) return [];
|
|
3273
|
+
return extracted.params.map((p) => {
|
|
3274
|
+
if (assumeIds.has(p)) {
|
|
3275
|
+
return abs(
|
|
3276
|
+
{ k: "prim", type: "number" },
|
|
3277
|
+
v(p),
|
|
3278
|
+
{ op: "gt", a: v(p), b: { op: "lit", value: 0 } },
|
|
3279
|
+
"path"
|
|
3280
|
+
);
|
|
3281
|
+
}
|
|
3282
|
+
return abs({ k: "any" }, void 0, void 0, "path");
|
|
3283
|
+
});
|
|
3284
|
+
}
|
|
3285
|
+
|
|
3286
|
+
// src/algebra/domain-membership.ts
|
|
3287
|
+
function literalMeetsConstraint(lv, c) {
|
|
3288
|
+
const ext = c;
|
|
3289
|
+
if (c.fields || c.element) return false;
|
|
3290
|
+
if (ext.fn) return false;
|
|
3291
|
+
if (Array.isArray(ext.members) && ext.members.length > 0) {
|
|
3292
|
+
return ext.members.some((m) => literalMeetsConstraint(lv, m));
|
|
3293
|
+
}
|
|
3294
|
+
if (c.prim === void 0) {
|
|
3295
|
+
return c.preds.every((p) => predHolds(lv, p));
|
|
3296
|
+
}
|
|
3297
|
+
if (!primMatches(c.prim, lv)) return false;
|
|
3298
|
+
if (isIntFlag(c) && c.prim === "number" && !(typeof lv === "number" && Number.isInteger(lv))) {
|
|
3299
|
+
return false;
|
|
3300
|
+
}
|
|
3301
|
+
return c.preds.every((p) => predHolds(lv, p));
|
|
3302
|
+
}
|
|
3303
|
+
function primMatches(prim, lv) {
|
|
3304
|
+
switch (prim) {
|
|
3305
|
+
case "number":
|
|
3306
|
+
return typeof lv === "number";
|
|
3307
|
+
case "string":
|
|
3308
|
+
return typeof lv === "string";
|
|
3309
|
+
case "boolean":
|
|
3310
|
+
return typeof lv === "boolean";
|
|
3311
|
+
case "object":
|
|
3312
|
+
return lv === null;
|
|
3313
|
+
default:
|
|
3314
|
+
return false;
|
|
3315
|
+
}
|
|
3316
|
+
}
|
|
3317
|
+
function predHolds(lv, p) {
|
|
3318
|
+
switch (p.op) {
|
|
3319
|
+
case "eq": {
|
|
3320
|
+
const v2 = selfEqLiteral(p);
|
|
3321
|
+
return v2 !== void 0 && lv === v2;
|
|
3322
|
+
}
|
|
3323
|
+
case "gt":
|
|
3324
|
+
case "ge":
|
|
3325
|
+
case "lt":
|
|
3326
|
+
case "le":
|
|
3327
|
+
return boundHolds(lv, p);
|
|
3328
|
+
case "typeof":
|
|
3329
|
+
return p.t.op === "var" && p.t.id === SELF && primMatches(p.type, lv);
|
|
3330
|
+
// and 是合取:逐参数按同一规则判定(string().length(n) 的 preds 是
|
|
3331
|
+
// and 包裹的两个长度界,叶子仍是 gt/ge/lt/le,无假阳性风险)
|
|
3332
|
+
case "and":
|
|
3333
|
+
return p.args.every((x) => predHolds(lv, x));
|
|
3334
|
+
// ne / or / not / true / false 等其余形态:保守不满足
|
|
3335
|
+
default:
|
|
3336
|
+
return false;
|
|
3337
|
+
}
|
|
3338
|
+
}
|
|
3339
|
+
function selfEqLiteral(p) {
|
|
3340
|
+
if (isSelfVar(p.a) && p.b.op === "lit") return p.b.value;
|
|
3341
|
+
if (isSelfVar(p.b) && p.a.op === "lit") return p.a.value;
|
|
3342
|
+
return void 0;
|
|
3343
|
+
}
|
|
3344
|
+
function cmpHolds(x, op, n) {
|
|
3345
|
+
switch (op) {
|
|
3346
|
+
case "gt":
|
|
3347
|
+
return x > n;
|
|
3348
|
+
case "ge":
|
|
3349
|
+
return x >= n;
|
|
3350
|
+
case "lt":
|
|
3351
|
+
return x < n;
|
|
3352
|
+
case "le":
|
|
3353
|
+
return x <= n;
|
|
3354
|
+
}
|
|
3355
|
+
}
|
|
3356
|
+
function boundHolds(lv, p) {
|
|
3357
|
+
if (p.b.op !== "lit" || typeof p.b.value !== "number") return false;
|
|
3358
|
+
const n = p.b.value;
|
|
3359
|
+
if (isLengthSelf(p.a)) {
|
|
3360
|
+
return typeof lv === "string" && cmpHolds(lv.length, p.op, n);
|
|
3361
|
+
}
|
|
3362
|
+
if (isSelfVar(p.a)) {
|
|
3363
|
+
return typeof lv === "number" && cmpHolds(lv, p.op, n);
|
|
3364
|
+
}
|
|
3365
|
+
return false;
|
|
3366
|
+
}
|
|
3367
|
+
function isSelfVar(t) {
|
|
3368
|
+
return t.op === "var" && t.id === SELF;
|
|
3369
|
+
}
|
|
3370
|
+
function isLengthSelf(t) {
|
|
3371
|
+
return t.op === "app" && t.fn === "length" && t.args.length === 1 && isSelfVar(t.args[0]);
|
|
3372
|
+
}
|
|
3373
|
+
|
|
3374
|
+
// src/algebra/stable-source-key.ts
|
|
3375
|
+
function stableAnalyzeKeySource(source) {
|
|
3376
|
+
let end = source.length;
|
|
3377
|
+
while (end > 0) {
|
|
3378
|
+
const ch = source.charCodeAt(end - 1);
|
|
3379
|
+
if (ch === 10 || ch === 13 || ch === 32 || ch === 9) {
|
|
3380
|
+
end--;
|
|
3381
|
+
continue;
|
|
3382
|
+
}
|
|
3383
|
+
break;
|
|
3384
|
+
}
|
|
3385
|
+
const lineStart = source.lastIndexOf("\n", end - 1) + 1;
|
|
3386
|
+
const lastLine = source.slice(lineStart, end);
|
|
3387
|
+
const trimmedLast = lastLine.trim();
|
|
3388
|
+
if (trimmedLast !== "" && !trimmedLast.startsWith("//") && !trimmedLast.startsWith("/*")) {
|
|
3389
|
+
let body2 = source.slice(0, end);
|
|
3390
|
+
if (body2.endsWith("*/")) {
|
|
3391
|
+
body2 = stripTrailingBlockComments(body2);
|
|
3392
|
+
return body2 + "\n";
|
|
3393
|
+
}
|
|
3394
|
+
const after = source.slice(end);
|
|
3395
|
+
if (after === "" || after === "\n") return source;
|
|
3396
|
+
return source.slice(0, end) + "\n";
|
|
3397
|
+
}
|
|
3398
|
+
const lines = source.split("\n");
|
|
3399
|
+
while (lines.length > 0) {
|
|
3400
|
+
const last = lines[lines.length - 1];
|
|
3401
|
+
if (last.trim() === "") {
|
|
3402
|
+
lines.pop();
|
|
3403
|
+
continue;
|
|
3404
|
+
}
|
|
3405
|
+
if (last.trimStart().startsWith("//") && !last.includes("@nudo")) {
|
|
3406
|
+
lines.pop();
|
|
3407
|
+
continue;
|
|
3408
|
+
}
|
|
3409
|
+
if (last.trim() === "*/" || last.trimStart().startsWith("/*")) {
|
|
3410
|
+
const joined = stripTrailingBlockComments(lines.join("\n"));
|
|
3411
|
+
const stripped = joined.split("\n");
|
|
3412
|
+
if (stripped.length < lines.length) {
|
|
3413
|
+
lines.length = 0;
|
|
3414
|
+
lines.push(...stripped);
|
|
3415
|
+
continue;
|
|
3416
|
+
}
|
|
3417
|
+
}
|
|
3418
|
+
break;
|
|
3419
|
+
}
|
|
3420
|
+
let body = lines.join("\n").replace(/\s+$/, "");
|
|
3421
|
+
body = stripTrailingBlockComments(body);
|
|
3422
|
+
return body + "\n";
|
|
3423
|
+
}
|
|
3424
|
+
function stripTrailingBlockComments(body) {
|
|
3425
|
+
let t = body.replace(/\s+$/, "");
|
|
3426
|
+
while (t.endsWith("*/")) {
|
|
3427
|
+
const start = t.lastIndexOf("/*");
|
|
3428
|
+
if (start < 0) break;
|
|
3429
|
+
const block = t.slice(start);
|
|
3430
|
+
if (block.includes("@nudo")) break;
|
|
3431
|
+
const before = t.slice(0, start);
|
|
3432
|
+
const lineStart = before.lastIndexOf("\n") + 1;
|
|
3433
|
+
if (before.slice(lineStart).trim() !== "") break;
|
|
3434
|
+
t = before.replace(/\s+$/, "");
|
|
3435
|
+
}
|
|
3436
|
+
return t;
|
|
3437
|
+
}
|
|
3438
|
+
|
|
3439
|
+
// src/algebra/scan-top-functions.ts
|
|
3440
|
+
function listTopFunctions(source, file) {
|
|
3441
|
+
const f = file ?? parseSource(source);
|
|
3442
|
+
const names = [];
|
|
3443
|
+
const localClasses = /* @__PURE__ */ new Map();
|
|
3444
|
+
for (const stmt of f.program.body) {
|
|
3445
|
+
if (stmt.type === "ClassDeclaration") {
|
|
3446
|
+
const cname = stmt.id?.name;
|
|
3447
|
+
if (cname) localClasses.set(cname, stmt);
|
|
3448
|
+
}
|
|
3449
|
+
}
|
|
3450
|
+
const exportedClassNames = /* @__PURE__ */ new Set();
|
|
3451
|
+
const collectClassMethods = (cname, decl) => {
|
|
3452
|
+
const body = decl.body?.body ?? [];
|
|
3453
|
+
for (const m of body) {
|
|
3454
|
+
const mem = m;
|
|
3455
|
+
const isMethod = mem.type === "MethodDefinition" || mem.type === "ClassMethod" || mem.type === "TSDeclareMethod";
|
|
3456
|
+
if (!isMethod) continue;
|
|
3457
|
+
if (mem.kind && mem.kind !== "method") continue;
|
|
3458
|
+
const keyName = mem.key?.type === "Identifier" ? mem.key.name : void 0;
|
|
3459
|
+
if (keyName) names.push(`${cname}.${keyName}`);
|
|
3460
|
+
}
|
|
3461
|
+
};
|
|
3462
|
+
for (const stmt of f.program.body) {
|
|
3463
|
+
let decl = stmt;
|
|
3464
|
+
if (stmt.type === "ExportNamedDeclaration" && stmt.declaration) {
|
|
3465
|
+
decl = stmt.declaration;
|
|
3466
|
+
}
|
|
3467
|
+
if (stmt.type === "ExportDefaultDeclaration" && stmt.declaration) {
|
|
3468
|
+
decl = stmt.declaration;
|
|
3469
|
+
if (decl.type === "ArrowFunctionExpression" || decl.type === "FunctionExpression") {
|
|
3470
|
+
if (!names.includes("default")) names.push("default");
|
|
3471
|
+
}
|
|
3472
|
+
}
|
|
3473
|
+
if (decl.type === "FunctionDeclaration" && decl.id) names.push(decl.id.name);
|
|
3474
|
+
if (decl.type === "FunctionDeclaration" && !decl.id && stmt.type === "ExportDefaultDeclaration") {
|
|
3475
|
+
if (!names.includes("default")) names.push("default");
|
|
3476
|
+
}
|
|
3477
|
+
if (decl.type === "VariableDeclaration") {
|
|
3478
|
+
for (const d of decl.declarations) {
|
|
3479
|
+
if (d.id.type === "Identifier" && d.init && (d.init.type === "ArrowFunctionExpression" || d.init.type === "FunctionExpression")) {
|
|
3480
|
+
names.push(d.id.name);
|
|
3481
|
+
}
|
|
3482
|
+
}
|
|
3483
|
+
}
|
|
3484
|
+
if (decl.type === "ClassDeclaration" && decl.id?.name && (stmt.type === "ExportNamedDeclaration" || stmt.type === "ExportDefaultDeclaration")) {
|
|
3485
|
+
const cname = decl.id.name;
|
|
3486
|
+
exportedClassNames.add(cname);
|
|
3487
|
+
collectClassMethods(cname, decl);
|
|
3488
|
+
}
|
|
3489
|
+
}
|
|
3490
|
+
for (const stmt of f.program.body) {
|
|
3491
|
+
if (stmt.type === "ExportNamedDeclaration" && !stmt.declaration) {
|
|
3492
|
+
const specs = stmt.specifiers ?? [];
|
|
3493
|
+
for (const spec of specs) {
|
|
3494
|
+
const s = spec;
|
|
3495
|
+
if (s.type !== "ExportSpecifier") continue;
|
|
3496
|
+
const localName = s.local?.name;
|
|
3497
|
+
if (!localName || exportedClassNames.has(localName)) continue;
|
|
3498
|
+
const exportedName = s.exported?.name ?? s.exported?.value;
|
|
3499
|
+
const isDefault = exportedName === "default";
|
|
3500
|
+
if (!isDefault && exportedName !== localName) {
|
|
3501
|
+
}
|
|
3502
|
+
const decl = localClasses.get(localName);
|
|
3503
|
+
if (!decl) continue;
|
|
3504
|
+
exportedClassNames.add(localName);
|
|
3505
|
+
collectClassMethods(localName, decl);
|
|
3506
|
+
}
|
|
3507
|
+
}
|
|
3508
|
+
if (stmt.type === "ExportDefaultDeclaration" && stmt.declaration) {
|
|
3509
|
+
const d = stmt.declaration;
|
|
3510
|
+
if (d.type === "Identifier") {
|
|
3511
|
+
const localName = d.name;
|
|
3512
|
+
if (!exportedClassNames.has(localName)) {
|
|
3513
|
+
const decl = localClasses.get(localName);
|
|
3514
|
+
if (decl) {
|
|
3515
|
+
exportedClassNames.add(localName);
|
|
3516
|
+
collectClassMethods(localName, decl);
|
|
3517
|
+
}
|
|
3518
|
+
}
|
|
3519
|
+
}
|
|
3520
|
+
}
|
|
3521
|
+
}
|
|
3522
|
+
for (const stmt of f.program.body) {
|
|
3523
|
+
if (stmt.type !== "ExpressionStatement") continue;
|
|
3524
|
+
const expr = stmt.expression;
|
|
3525
|
+
if (!expr || expr.type !== "AssignmentExpression") continue;
|
|
3526
|
+
const left = expr.left;
|
|
3527
|
+
const right = expr.right;
|
|
3528
|
+
if (!left || left.type !== "MemberExpression" || left.computed) continue;
|
|
3529
|
+
const obj = left.object;
|
|
3530
|
+
const prop = left.property;
|
|
3531
|
+
const propName = prop?.type === "Identifier" ? prop.name : prop?.type === "StringLiteral" || prop?.type === "NumericLiteral" ? String(prop.value) : void 0;
|
|
3532
|
+
const isExportsIdent = !!obj && obj.type === "Identifier" && obj.name === "exports";
|
|
3533
|
+
const isModuleExportsMember = !!obj && obj.type === "MemberExpression" && obj.object?.name === "module" && obj.property?.name === "exports";
|
|
3534
|
+
const isModuleExportsIdent = !!obj && obj.type === "Identifier" && obj.name === "module" && propName === "exports";
|
|
3535
|
+
const pushFnFromInit = (fallbackName, init) => {
|
|
3536
|
+
const r = init;
|
|
3537
|
+
if (!r) return;
|
|
3538
|
+
if (r.type !== "FunctionExpression" && r.type !== "ArrowFunctionExpression") return;
|
|
3539
|
+
const n = r.id?.name ?? fallbackName;
|
|
3540
|
+
if (n && !names.includes(n)) names.push(n);
|
|
3541
|
+
};
|
|
3542
|
+
if (isModuleExportsIdent) {
|
|
3543
|
+
pushFnFromInit(void 0, right);
|
|
3544
|
+
const objLit = right;
|
|
3545
|
+
if (objLit?.type === "ObjectExpression") {
|
|
3546
|
+
for (const p of objLit.properties ?? []) {
|
|
3547
|
+
const prop2 = p;
|
|
3548
|
+
if (prop2.type === "ObjectMethod" || prop2.type === "ClassMethod") {
|
|
3549
|
+
const keyName2 = prop2.key?.type === "Identifier" ? prop2.key.name : prop2.key?.type === "StringLiteral" || prop2.key?.type === "NumericLiteral" ? String(prop2.key.value) : void 0;
|
|
3550
|
+
if (keyName2 && !names.includes(keyName2)) names.push(keyName2);
|
|
3551
|
+
continue;
|
|
3552
|
+
}
|
|
3553
|
+
if (prop2.type !== "ObjectProperty" && prop2.type !== "Property") continue;
|
|
3554
|
+
const keyName = prop2.key?.type === "Identifier" ? prop2.key.name : prop2.key?.type === "StringLiteral" || prop2.key?.type === "NumericLiteral" ? String(prop2.key.value) : void 0;
|
|
3555
|
+
if (keyName) pushFnFromInit(keyName, prop2.value);
|
|
3556
|
+
}
|
|
3557
|
+
}
|
|
3558
|
+
continue;
|
|
3559
|
+
}
|
|
3560
|
+
if ((isExportsIdent || isModuleExportsMember) && propName) {
|
|
3561
|
+
pushFnFromInit(propName, right);
|
|
3562
|
+
}
|
|
3563
|
+
}
|
|
3564
|
+
return names;
|
|
3565
|
+
}
|
|
3566
|
+
|
|
3567
|
+
// src/algebra/scan-injected-domain.ts
|
|
3568
|
+
function evidenceToString(v2) {
|
|
3569
|
+
return typeof v2 === "string" ? JSON.stringify(v2) : String(v2);
|
|
3570
|
+
}
|
|
3571
|
+
function checkInjectedDomainEvidence(fnName, source, records, opts) {
|
|
3572
|
+
const isLeaked = (r) => {
|
|
3573
|
+
if (r.resultAbs && r.throwsAbs) {
|
|
3574
|
+
return r.resultAbs.shape.k === "never" && r.throwsAbs.shape.k === "never";
|
|
3575
|
+
}
|
|
3576
|
+
return false;
|
|
3577
|
+
};
|
|
3578
|
+
const usable = records.filter((r) => !isLeaked(r));
|
|
3579
|
+
if (usable.length === 0) return [];
|
|
3580
|
+
let ei;
|
|
3581
|
+
try {
|
|
3582
|
+
ei = effectiveInterface(source, fnName, {
|
|
3583
|
+
...opts.loadModule ? { loadModule: opts.loadModule } : {},
|
|
3584
|
+
fromFile: opts.fromFile,
|
|
3585
|
+
...opts.autoBind !== void 0 ? { autoBind: opts.autoBind } : {},
|
|
3586
|
+
...opts.projectDir !== void 0 ? { projectDir: opts.projectDir } : {}
|
|
3587
|
+
});
|
|
3588
|
+
} catch (e) {
|
|
3589
|
+
return [
|
|
3590
|
+
{
|
|
3591
|
+
severity: "warning",
|
|
3592
|
+
code: "nudo:interface-load",
|
|
3593
|
+
message: `${fnName}: effective interface load failed (${e instanceof Error ? e.message : String(e)}); skipping domain-exceeds check`,
|
|
3594
|
+
fn: fnName,
|
|
3595
|
+
line: opts.loc?.line,
|
|
3596
|
+
column: opts.loc?.column
|
|
3597
|
+
}
|
|
3598
|
+
];
|
|
3599
|
+
}
|
|
3600
|
+
if (!ei || ei.source !== "handwritten") return [];
|
|
3601
|
+
const out = [];
|
|
3602
|
+
for (const { param, constraint } of ei.params) {
|
|
3603
|
+
if (constraint.fields || constraint.element || constraint.fn) continue;
|
|
3604
|
+
const idx = opts.paramNames.indexOf(param);
|
|
3605
|
+
if (idx < 0) continue;
|
|
3606
|
+
const failures = [];
|
|
3607
|
+
for (const rec of usable) {
|
|
3608
|
+
const lit2 = extractLiteralEvidence(rec, idx);
|
|
3609
|
+
if (lit2 === void 0) continue;
|
|
3610
|
+
if (!literalMeetsConstraint(lit2, constraint)) failures.push(lit2);
|
|
3611
|
+
}
|
|
3612
|
+
if (failures.length === 0) continue;
|
|
3613
|
+
const shown = [...new Set(failures)].map(evidenceToString).join(", ");
|
|
3614
|
+
out.push({
|
|
3615
|
+
severity: "error",
|
|
3616
|
+
code: "nudo:interface-domain-exceeds",
|
|
3617
|
+
message: `${fnName}[${param}]: cross-file call-site domain evidence ${shown} exceeds handwritten contract`,
|
|
3618
|
+
actual: shown,
|
|
3619
|
+
expected: formatConstraint(constraint),
|
|
3620
|
+
suggestion: `Loosen the handwritten contract for ${fnName} (${param}: ${formatConstraint(constraint)}), or fix the caller's values`,
|
|
3621
|
+
fn: fnName,
|
|
3622
|
+
line: opts.loc?.line,
|
|
3623
|
+
column: opts.loc?.column
|
|
3624
|
+
});
|
|
3625
|
+
}
|
|
3626
|
+
return out;
|
|
3627
|
+
}
|
|
3628
|
+
function extractLiteralEvidence(rec, idx) {
|
|
3629
|
+
const absArg = rec.argAbs?.[idx];
|
|
3630
|
+
if (!absArg) return void 0;
|
|
3631
|
+
if (absArg.conf !== "exact" && absArg.conf !== "path") return void 0;
|
|
3632
|
+
const lv = litValue(absArg);
|
|
3633
|
+
if (typeof lv === "number" || typeof lv === "string" || typeof lv === "boolean") {
|
|
3634
|
+
return lv;
|
|
3635
|
+
}
|
|
3636
|
+
return void 0;
|
|
3637
|
+
}
|
|
3638
|
+
|
|
3639
|
+
// src/algebra/scan-call-graph.ts
|
|
3640
|
+
function requireSpecOf(init) {
|
|
3641
|
+
if (init.type === "CallExpression") {
|
|
3642
|
+
const callee = init.callee;
|
|
3643
|
+
const args = init.arguments;
|
|
3644
|
+
if (callee?.type === "Identifier" && callee.name === "require" && args?.[0]) {
|
|
3645
|
+
return foldRequireSpecArg(args[0]);
|
|
3646
|
+
}
|
|
3647
|
+
}
|
|
3648
|
+
if (init.type === "MemberExpression") {
|
|
3649
|
+
const obj = init.object;
|
|
3650
|
+
if (obj) return requireSpecOf(obj);
|
|
3651
|
+
}
|
|
3652
|
+
return void 0;
|
|
3653
|
+
}
|
|
3654
|
+
function requireExportName(init) {
|
|
3655
|
+
if (init.type === "MemberExpression" && !init.computed) {
|
|
3656
|
+
const prop = init.property;
|
|
3657
|
+
if (prop?.type === "Identifier") return prop.name;
|
|
3658
|
+
}
|
|
3659
|
+
return void 0;
|
|
3660
|
+
}
|
|
3661
|
+
function dynamicImportSpec(node) {
|
|
3662
|
+
let n = node;
|
|
3663
|
+
if (n?.type === "AwaitExpression") {
|
|
3664
|
+
n = n.argument;
|
|
3665
|
+
}
|
|
3666
|
+
if (!n) return void 0;
|
|
3667
|
+
if (n.type === "ImportExpression") {
|
|
3668
|
+
const src = n.source;
|
|
3669
|
+
if (src?.type === "StringLiteral" && typeof src.value === "string") return String(src.value);
|
|
3670
|
+
return void 0;
|
|
3671
|
+
}
|
|
3672
|
+
if (n.type !== "CallExpression") return void 0;
|
|
3673
|
+
const callee = n.callee;
|
|
3674
|
+
if (callee?.type !== "Import") return void 0;
|
|
3675
|
+
const args = n.arguments;
|
|
3676
|
+
if (args?.[0]?.type === "StringLiteral") return String(args[0].value);
|
|
3677
|
+
return void 0;
|
|
3678
|
+
}
|
|
3679
|
+
function resolveExportSource(modSrc, fnName, loadSpecFrom, depth = 0, baseFromFile = "", modFromFile = "") {
|
|
3680
|
+
const here = () => ({
|
|
3681
|
+
source: modSrc,
|
|
3682
|
+
fromFile: modFromFile
|
|
3683
|
+
});
|
|
3684
|
+
if (depth > 3) return here();
|
|
3685
|
+
try {
|
|
3686
|
+
if (listTopFunctions(modSrc).includes(fnName)) return here();
|
|
3687
|
+
} catch {
|
|
3688
|
+
return here();
|
|
3689
|
+
}
|
|
3690
|
+
const file = parseSource(modSrc);
|
|
3691
|
+
let nextSpec;
|
|
3692
|
+
for (const stmt of file.program.body) {
|
|
3693
|
+
if (stmt.type !== "ExportNamedDeclaration" && stmt.type !== "ExportAllDeclaration") continue;
|
|
3694
|
+
const src = stmt.source;
|
|
3695
|
+
if (src?.type !== "StringLiteral" || typeof src.value !== "string") continue;
|
|
3696
|
+
if (stmt.type === "ExportAllDeclaration") {
|
|
3697
|
+
nextSpec = String(src.value);
|
|
3698
|
+
break;
|
|
3699
|
+
}
|
|
3700
|
+
const clause = stmt.specifiers ?? [];
|
|
3701
|
+
for (const sp of clause) {
|
|
3702
|
+
if (sp.type !== "ExportSpecifier") continue;
|
|
3703
|
+
const local = sp.local;
|
|
3704
|
+
if (local?.type === "Identifier" && local.name === fnName) {
|
|
3705
|
+
nextSpec = String(src.value);
|
|
3706
|
+
break;
|
|
3707
|
+
}
|
|
3708
|
+
}
|
|
3709
|
+
if (nextSpec) break;
|
|
3710
|
+
}
|
|
3711
|
+
if (!nextSpec) return here();
|
|
3712
|
+
const hopFrom = modFromFile || baseFromFile;
|
|
3713
|
+
const next = hopFrom ? loadSpecFrom(nextSpec, hopFrom) : void 0;
|
|
3714
|
+
if (!next) return here();
|
|
3715
|
+
const nextFromFile = hopFrom ? resolveDepPath(hopFrom, nextSpec) : "";
|
|
3716
|
+
return resolveExportSource(
|
|
3717
|
+
next,
|
|
3718
|
+
fnName,
|
|
3719
|
+
loadSpecFrom,
|
|
3720
|
+
depth + 1,
|
|
3721
|
+
baseFromFile,
|
|
3722
|
+
nextFromFile
|
|
3723
|
+
);
|
|
3724
|
+
}
|
|
3725
|
+
var STMT_CONTAINER = /* @__PURE__ */ new Set([
|
|
3726
|
+
"File",
|
|
3727
|
+
"Program",
|
|
3728
|
+
"BlockStatement",
|
|
3729
|
+
"FunctionDeclaration",
|
|
3730
|
+
"FunctionExpression",
|
|
3731
|
+
"ArrowFunctionExpression",
|
|
3732
|
+
"IfStatement",
|
|
3733
|
+
"ForStatement",
|
|
3734
|
+
"ForInStatement",
|
|
3735
|
+
"ForOfStatement",
|
|
3736
|
+
"WhileStatement",
|
|
3737
|
+
"DoWhileStatement",
|
|
3738
|
+
"SwitchStatement",
|
|
3739
|
+
"SwitchCase",
|
|
3740
|
+
"TryStatement",
|
|
3741
|
+
"CatchClause",
|
|
3742
|
+
"LabeledStatement",
|
|
3743
|
+
"ExportNamedDeclaration",
|
|
3744
|
+
"ExportDefaultDeclaration",
|
|
3745
|
+
"ExportAllDeclaration",
|
|
3746
|
+
"ClassDeclaration",
|
|
3747
|
+
"ClassBody",
|
|
3748
|
+
"ClassMethod",
|
|
3749
|
+
"StaticBlock",
|
|
3750
|
+
"ObjectMethod"
|
|
3751
|
+
]);
|
|
3752
|
+
function walkStatements(n, onStmt) {
|
|
3753
|
+
if (!n || typeof n !== "object") return;
|
|
3754
|
+
const obj = n;
|
|
3755
|
+
if (typeof obj.type === "string") onStmt(obj);
|
|
3756
|
+
if (!STMT_CONTAINER.has(String(obj.type))) return;
|
|
3757
|
+
for (const key of Object.keys(obj)) {
|
|
3758
|
+
if (key === "loc" || key === "start" || key === "end" || key === "leadingComments") continue;
|
|
3759
|
+
const val = obj[key];
|
|
3760
|
+
if (Array.isArray(val)) val.forEach((x) => walkStatements(x, onStmt));
|
|
3761
|
+
else if (val && typeof val === "object") walkStatements(val, onStmt);
|
|
3762
|
+
}
|
|
3763
|
+
}
|
|
3764
|
+
function collectCallResolvers(source, knownFns, opts) {
|
|
3765
|
+
const knownSet = new Set(knownFns);
|
|
3766
|
+
const aliasToFn = /* @__PURE__ */ new Map();
|
|
3767
|
+
const memberToFn = /* @__PURE__ */ new Map();
|
|
3768
|
+
const externalFn = /* @__PURE__ */ new Map();
|
|
3769
|
+
const externalMember = /* @__PURE__ */ new Map();
|
|
3770
|
+
const file = opts?.file ?? parseSource(source);
|
|
3771
|
+
const load = opts?.loadModule;
|
|
3772
|
+
const fromFile = opts?.fromFile ?? "";
|
|
3773
|
+
const modCache = /* @__PURE__ */ new Map();
|
|
3774
|
+
const loadSpecFrom = (spec, from) => {
|
|
3775
|
+
if (!load) return void 0;
|
|
3776
|
+
const key = from + "\0" + spec;
|
|
3777
|
+
if (!modCache.has(key)) modCache.set(key, load(spec, from));
|
|
3778
|
+
return modCache.get(key);
|
|
3779
|
+
};
|
|
3780
|
+
const extFromFile = (spec) => fromFile ? resolveDepPath(fromFile, spec) : void 0;
|
|
3781
|
+
const bindExternal = (local, modSrc, fnName, spec) => {
|
|
3782
|
+
const r = resolveExportSource(modSrc, fnName, loadSpecFrom, 0, fromFile, extFromFile(spec) ?? "");
|
|
3783
|
+
externalFn.set(local, {
|
|
3784
|
+
source: r.source,
|
|
3785
|
+
fnName,
|
|
3786
|
+
...r.fromFile ? { fromFile: r.fromFile } : {}
|
|
3787
|
+
});
|
|
3788
|
+
};
|
|
3789
|
+
const loadSpec = (spec) => loadSpecFrom(spec, fromFile);
|
|
3790
|
+
const visit2 = (n) => {
|
|
3791
|
+
if (!n || typeof n !== "object") return;
|
|
3792
|
+
const obj = n;
|
|
3793
|
+
if (obj.type === "ImportDeclaration") {
|
|
3794
|
+
const spec = obj.source;
|
|
3795
|
+
if (spec?.type === "StringLiteral" && typeof spec.value === "string") {
|
|
3796
|
+
const modSrc = loadSpec(String(spec.value));
|
|
3797
|
+
if (modSrc) {
|
|
3798
|
+
for (const sp of obj.specifiers ?? []) {
|
|
3799
|
+
if (sp.type === "ImportSpecifier") {
|
|
3800
|
+
const imported = sp.imported;
|
|
3801
|
+
const local = sp.local;
|
|
3802
|
+
const exportName = imported?.type === "Identifier" ? imported.name : imported?.type === "StringLiteral" ? String(imported.value) : void 0;
|
|
3803
|
+
if (exportName && local?.name) {
|
|
3804
|
+
bindExternal(local.name, modSrc, exportName, String(spec.value));
|
|
3805
|
+
}
|
|
3806
|
+
} else if (sp.type === "ImportNamespaceSpecifier") {
|
|
3807
|
+
const local = sp.local;
|
|
3808
|
+
if (local?.name) {
|
|
3809
|
+
const from = extFromFile(String(spec.value));
|
|
3810
|
+
externalMember.set(
|
|
3811
|
+
`${local.name}.__module__`,
|
|
3812
|
+
{ source: modSrc, fnName: "", ...from ? { fromFile: from } : {} }
|
|
3813
|
+
);
|
|
3814
|
+
}
|
|
3815
|
+
} else if (sp.type === "ImportDefaultSpecifier") {
|
|
3816
|
+
const local = sp.local;
|
|
3817
|
+
if (local?.name) {
|
|
3818
|
+
bindExternal(local.name, modSrc, "default", String(spec.value));
|
|
3819
|
+
}
|
|
3820
|
+
}
|
|
3821
|
+
}
|
|
3822
|
+
}
|
|
3823
|
+
}
|
|
3824
|
+
}
|
|
3825
|
+
if (obj.type === "VariableDeclaration") {
|
|
3826
|
+
for (const d of obj.declarations ?? []) {
|
|
3827
|
+
const id = d.id;
|
|
3828
|
+
const init = d.init;
|
|
3829
|
+
if (!id || !init) continue;
|
|
3830
|
+
const dynSpec = dynamicImportSpec(init);
|
|
3831
|
+
if (dynSpec) {
|
|
3832
|
+
const modSrc = loadSpec(dynSpec);
|
|
3833
|
+
if (modSrc && id.type === "ObjectPattern") {
|
|
3834
|
+
for (const p of id.properties ?? []) {
|
|
3835
|
+
const key = p.key;
|
|
3836
|
+
const val = p.value;
|
|
3837
|
+
const exported = key?.type === "Identifier" ? key.name : key?.type === "StringLiteral" ? String(key.value) : void 0;
|
|
3838
|
+
const local = val?.type === "Identifier" ? val.name : exported;
|
|
3839
|
+
if (exported && local) bindExternal(local, modSrc, exported, dynSpec);
|
|
3840
|
+
}
|
|
3841
|
+
}
|
|
3842
|
+
if (modSrc && id.type === "Identifier" && id.name) {
|
|
3843
|
+
const from = extFromFile(dynSpec);
|
|
3844
|
+
externalMember.set(
|
|
3845
|
+
`${id.name}.__module__`,
|
|
3846
|
+
{ source: modSrc, fnName: "", ...from ? { fromFile: from } : {} }
|
|
3847
|
+
);
|
|
3848
|
+
}
|
|
3849
|
+
}
|
|
3850
|
+
const spec = requireSpecOf(init);
|
|
3851
|
+
if (spec) {
|
|
3852
|
+
const modSrc = loadSpec(spec);
|
|
3853
|
+
if (id.type === "ObjectPattern" && modSrc) {
|
|
3854
|
+
for (const p of id.properties ?? []) {
|
|
3855
|
+
const key = p.key;
|
|
3856
|
+
const val = p.value;
|
|
3857
|
+
const exported = key?.type === "Identifier" ? key.name : key?.type === "StringLiteral" ? String(key.value) : void 0;
|
|
3858
|
+
const local = val?.type === "Identifier" ? val.name : exported;
|
|
3859
|
+
if (exported && local) {
|
|
3860
|
+
bindExternal(local, modSrc, exported, spec);
|
|
3861
|
+
}
|
|
3862
|
+
}
|
|
3863
|
+
}
|
|
3864
|
+
if (id.type === "Identifier" && id.name && modSrc) {
|
|
3865
|
+
const exportName = requireExportName(init);
|
|
3866
|
+
if (exportName) {
|
|
3867
|
+
bindExternal(id.name, modSrc, exportName, spec);
|
|
3868
|
+
} else {
|
|
3869
|
+
const from = extFromFile(spec);
|
|
3870
|
+
externalMember.set(
|
|
3871
|
+
`${id.name}.__module__`,
|
|
3872
|
+
{ source: modSrc, fnName: "", ...from ? { fromFile: from } : {} }
|
|
3873
|
+
);
|
|
3874
|
+
}
|
|
3875
|
+
}
|
|
3876
|
+
}
|
|
3877
|
+
if (id.type !== "Identifier" || !id.name) continue;
|
|
3878
|
+
if (init.type === "Identifier" && typeof init.name === "string" && knownSet.has(init.name)) {
|
|
3879
|
+
aliasToFn.set(id.name, init.name);
|
|
3880
|
+
}
|
|
3881
|
+
if (init.type === "ObjectExpression") {
|
|
3882
|
+
for (const p of init.properties ?? []) {
|
|
3883
|
+
if (p.type !== "ObjectProperty") continue;
|
|
3884
|
+
const key = p.key;
|
|
3885
|
+
const value = p.value;
|
|
3886
|
+
if (value?.type === "Identifier" && value.name && knownSet.has(value.name)) {
|
|
3887
|
+
const propKey = key?.type === "Identifier" ? key.name : key?.type === "StringLiteral" ? String(key.value) : void 0;
|
|
3888
|
+
if (propKey) memberToFn.set(`${id.name}.${propKey}`, value.name);
|
|
3889
|
+
}
|
|
3890
|
+
}
|
|
3891
|
+
}
|
|
3892
|
+
}
|
|
3893
|
+
}
|
|
3894
|
+
};
|
|
3895
|
+
walkStatements(file, visit2);
|
|
3896
|
+
return { aliasToFn, memberToFn, externalFn, externalMember };
|
|
3897
|
+
}
|
|
3898
|
+
function resolveCalleeFn(callee, resolve, knownFns) {
|
|
3899
|
+
if (callee.type === "Identifier" && typeof callee.name === "string") {
|
|
3900
|
+
const ext = resolve.externalFn.get(callee.name);
|
|
3901
|
+
if (ext) return { external: ext };
|
|
3902
|
+
const aliased = resolve.aliasToFn.get(callee.name);
|
|
3903
|
+
if (aliased) return aliased;
|
|
3904
|
+
if (knownFns.has(callee.name)) return callee.name;
|
|
3905
|
+
return void 0;
|
|
3906
|
+
}
|
|
3907
|
+
if (callee.type === "MemberExpression") {
|
|
3908
|
+
const obj = callee.object;
|
|
3909
|
+
const prop = callee.property;
|
|
3910
|
+
if (!callee.computed && obj?.type === "Identifier" && obj.name && prop?.type === "Identifier" && prop.name) {
|
|
3911
|
+
const key = `${obj.name}.${prop.name}`;
|
|
3912
|
+
const extM = resolve.externalMember.get(key);
|
|
3913
|
+
if (extM) return { external: extM };
|
|
3914
|
+
const mod = resolve.externalMember.get(`${obj.name}.__module__`);
|
|
3915
|
+
if (mod?.source) {
|
|
3916
|
+
return {
|
|
3917
|
+
external: {
|
|
3918
|
+
source: mod.source,
|
|
3919
|
+
fnName: prop.name,
|
|
3920
|
+
...mod.fromFile ? { fromFile: mod.fromFile } : {}
|
|
3921
|
+
}
|
|
3922
|
+
};
|
|
3923
|
+
}
|
|
3924
|
+
const local = resolve.memberToFn.get(key);
|
|
3925
|
+
if (local) return local;
|
|
3926
|
+
}
|
|
3927
|
+
}
|
|
3928
|
+
return void 0;
|
|
3929
|
+
}
|
|
3930
|
+
function collectForwarders(source, knownFns, resolve, fileAst) {
|
|
3931
|
+
const forwards = /* @__PURE__ */ new Map();
|
|
3932
|
+
const file = fileAst ?? parseSource(source);
|
|
3933
|
+
const tryFn = (name, params, body) => {
|
|
3934
|
+
if (!name || !params.length) return;
|
|
3935
|
+
const paramNames = params.map((p) => p?.type === "Identifier" ? p.name : void 0);
|
|
3936
|
+
if (paramNames.some((n) => !n)) return;
|
|
3937
|
+
let call;
|
|
3938
|
+
if (body?.type === "BlockStatement") {
|
|
3939
|
+
const stmts = body.body ?? [];
|
|
3940
|
+
if (stmts.length !== 1 || stmts[0].type !== "ReturnStatement") return;
|
|
3941
|
+
const arg = stmts[0].argument;
|
|
3942
|
+
if (arg?.type !== "CallExpression") return;
|
|
3943
|
+
call = arg;
|
|
3944
|
+
} else if (body?.type === "CallExpression") {
|
|
3945
|
+
call = body;
|
|
3946
|
+
}
|
|
3947
|
+
if (!call) return;
|
|
3948
|
+
const resolved = resolveCalleeFn(call.callee, resolve, knownFns);
|
|
3949
|
+
if (!resolved || typeof resolved !== "string") return;
|
|
3950
|
+
if (!knownFns.has(resolved) || resolved === name) return;
|
|
3951
|
+
const args = call.arguments ?? [];
|
|
3952
|
+
if (args.length === 0) return;
|
|
3953
|
+
const map = [];
|
|
3954
|
+
for (const a of args) {
|
|
3955
|
+
if (a.type !== "Identifier" || typeof a.name !== "string") return;
|
|
3956
|
+
const idx = paramNames.indexOf(a.name);
|
|
3957
|
+
if (idx < 0) return;
|
|
3958
|
+
map.push(idx);
|
|
3959
|
+
}
|
|
3960
|
+
forwards.set(name, { target: resolved, map });
|
|
3961
|
+
};
|
|
3962
|
+
walkStatements(file, (obj) => {
|
|
3963
|
+
if (obj.type === "FunctionDeclaration") {
|
|
3964
|
+
const id = obj.id;
|
|
3965
|
+
tryFn(
|
|
3966
|
+
id?.name ?? "",
|
|
3967
|
+
obj.params ?? [],
|
|
3968
|
+
obj.body
|
|
3969
|
+
);
|
|
3970
|
+
}
|
|
3971
|
+
if (obj.type === "VariableDeclaration") {
|
|
3972
|
+
for (const d of obj.declarations ?? []) {
|
|
3973
|
+
const id = d.id;
|
|
3974
|
+
const init = d.init;
|
|
3975
|
+
if (id?.type === "Identifier" && id.name && init && (init.type === "ArrowFunctionExpression" || init.type === "FunctionExpression")) {
|
|
3976
|
+
tryFn(
|
|
3977
|
+
id.name,
|
|
3978
|
+
init.params ?? [],
|
|
3979
|
+
init.body
|
|
3980
|
+
);
|
|
3981
|
+
}
|
|
3982
|
+
}
|
|
3983
|
+
}
|
|
3984
|
+
});
|
|
3985
|
+
return forwards;
|
|
3986
|
+
}
|
|
3987
|
+
|
|
3988
|
+
// src/algebra/scan.ts
|
|
3989
|
+
function hofFnArgOk(src, tgt) {
|
|
3990
|
+
if (src.shape.k !== "fn") return false;
|
|
3991
|
+
const t = tgt.shape;
|
|
3992
|
+
if (t.k !== "fn") return false;
|
|
3993
|
+
if (src.shape.params.length < t.params.length) return false;
|
|
3994
|
+
return true;
|
|
3995
|
+
}
|
|
3996
|
+
function absUnknown() {
|
|
3997
|
+
return { shape: { k: "unknown" }, conf: "partial" };
|
|
3998
|
+
}
|
|
3999
|
+
function isExactUndef(a) {
|
|
4000
|
+
return a.term?.op === "lit" && (a.term.value === void 0 || a.term.value === null);
|
|
4001
|
+
}
|
|
4002
|
+
function isInformativeArg(a) {
|
|
4003
|
+
if (!a || typeof a !== "object") return false;
|
|
4004
|
+
if (a.term?.op === "lit") return true;
|
|
4005
|
+
return a.shape?.k !== "unknown" && a.shape !== void 0;
|
|
4006
|
+
}
|
|
4007
|
+
function evalArgAbs(node, lookupVar) {
|
|
4008
|
+
if (node.type === "Identifier" && typeof node.name === "string" && lookupVar) {
|
|
4009
|
+
return lookupVar(node.name);
|
|
4010
|
+
}
|
|
4011
|
+
try {
|
|
4012
|
+
const free = freeIdentifiers(node, []);
|
|
4013
|
+
const bindings = {};
|
|
4014
|
+
if (lookupVar) {
|
|
4015
|
+
for (const name of free) {
|
|
4016
|
+
const v2 = lookupVar(name);
|
|
4017
|
+
if (v2) bindings[name] = v2;
|
|
4018
|
+
}
|
|
4019
|
+
}
|
|
4020
|
+
return evalExprAbs(node, bindings);
|
|
4021
|
+
} catch {
|
|
4022
|
+
return void 0;
|
|
4023
|
+
}
|
|
4024
|
+
}
|
|
4025
|
+
function scanLiteralCalls(source, knownFns, phi, opts) {
|
|
4026
|
+
const out = [];
|
|
4027
|
+
const file = opts?.file ?? parseSource(source);
|
|
4028
|
+
const knownSet = new Set(knownFns);
|
|
4029
|
+
const resolve = collectCallResolvers(source, knownFns, opts);
|
|
4030
|
+
const forwards = collectForwarders(source, knownSet, resolve, file);
|
|
4031
|
+
const varAbs = opts?.varAbs ?? /* @__PURE__ */ new Map();
|
|
4032
|
+
const bCallIndex = /* @__PURE__ */ new Map();
|
|
4033
|
+
for (const r of opts?.bCalls ?? []) {
|
|
4034
|
+
if (!r.callLoc) continue;
|
|
4035
|
+
const key = `${r.fnName}\0${r.callLoc.line}`;
|
|
4036
|
+
const list = bCallIndex.get(key) ?? [];
|
|
4037
|
+
list.push(r.args);
|
|
4038
|
+
bCallIndex.set(key, list);
|
|
4039
|
+
}
|
|
4040
|
+
const takeBCallArgs = (fnName, line) => {
|
|
4041
|
+
if (line === void 0) return void 0;
|
|
4042
|
+
const list = bCallIndex.get(`${fnName}\0${line}`);
|
|
4043
|
+
if (!list || list.length === 0) return void 0;
|
|
4044
|
+
return list.shift();
|
|
4045
|
+
};
|
|
4046
|
+
const flattenPred = (p) => p.op === "and" ? p.args.flatMap(flattenPred) : p.op === "true" ? [] : [p];
|
|
4047
|
+
const eiCache = /* @__PURE__ */ new Map();
|
|
4048
|
+
const effectiveInterfaceOf = (fnName, fnSource, eiOpts) => {
|
|
4049
|
+
const key = `${fnName}\0${eiOpts.fromFile ?? ""}\0${eiOpts.autoBind === false ? "0" : "1"}\0${eiOpts.projectDir ?? "-"}\0${hashSource(fnSource)}`;
|
|
4050
|
+
if (eiCache.has(key)) return eiCache.get(key);
|
|
4051
|
+
const r = effectiveInterface(fnSource, fnName, eiOpts);
|
|
4052
|
+
eiCache.set(key, r);
|
|
4053
|
+
return r;
|
|
4054
|
+
};
|
|
4055
|
+
const projectArgField = (arg, field) => {
|
|
4056
|
+
if (!arg) return void 0;
|
|
4057
|
+
if (arg.shape.k === "brand") {
|
|
4058
|
+
return projectArgField(arg.shape.shape, field);
|
|
4059
|
+
}
|
|
4060
|
+
if (arg.shape.k !== "obj") return void 0;
|
|
4061
|
+
return getSlot(arg.shape.slots, field)?.value;
|
|
4062
|
+
};
|
|
4063
|
+
const interfaceToIndexed = (ei, paramNames, formals) => {
|
|
4064
|
+
const conflict = new Set(ei.conflict?.params ?? []);
|
|
4065
|
+
const entries = [];
|
|
4066
|
+
for (const { param, constraint } of ei.params) {
|
|
4067
|
+
if (!param || conflict.has(param)) continue;
|
|
4068
|
+
const idx = paramNames.indexOf(param);
|
|
4069
|
+
if (idx >= 0) {
|
|
4070
|
+
entries.push([
|
|
4071
|
+
idx,
|
|
4072
|
+
{ param, pred: instantiateConstraint(constraint, param), constraint },
|
|
4073
|
+
void 0
|
|
4074
|
+
]);
|
|
4075
|
+
continue;
|
|
4076
|
+
}
|
|
4077
|
+
if (formals && formals.length > 0) {
|
|
4078
|
+
const hit = locateContractParam(formals, param);
|
|
4079
|
+
if (hit) {
|
|
4080
|
+
entries.push([
|
|
4081
|
+
hit.index,
|
|
4082
|
+
{ param, pred: instantiateConstraint(constraint, param), constraint },
|
|
4083
|
+
hit.field
|
|
4084
|
+
]);
|
|
4085
|
+
}
|
|
4086
|
+
}
|
|
4087
|
+
}
|
|
4088
|
+
return entries;
|
|
4089
|
+
};
|
|
4090
|
+
const predEqLiteral = (p) => {
|
|
4091
|
+
if (p.op !== "eq") return void 0;
|
|
4092
|
+
const v2 = p.a.op === "var" && p.b.op === "lit" ? p.b.value : p.b.op === "var" && p.a.op === "lit" ? p.a.value : void 0;
|
|
4093
|
+
return typeof v2 === "number" || typeof v2 === "string" || typeof v2 === "boolean" ? v2 : void 0;
|
|
4094
|
+
};
|
|
4095
|
+
const judgeLiteralPred = (p, lv, strLen) => {
|
|
4096
|
+
if (p.op === "eq") {
|
|
4097
|
+
const v2 = predEqLiteral(p);
|
|
4098
|
+
return v2 === void 0 ? void 0 : lv === v2;
|
|
4099
|
+
}
|
|
4100
|
+
if (p.op !== "gt" && p.op !== "ge" && p.op !== "lt" && p.op !== "le") {
|
|
4101
|
+
return void 0;
|
|
4102
|
+
}
|
|
4103
|
+
if (p.b.op !== "lit" || typeof p.b.value !== "number") return void 0;
|
|
4104
|
+
const n = p.b.value;
|
|
4105
|
+
if (p.a.op === "app" && p.a.fn === "length") {
|
|
4106
|
+
if (strLen === void 0) return void 0;
|
|
4107
|
+
if (p.op === "gt") return strLen > n;
|
|
4108
|
+
if (p.op === "ge") return strLen >= n;
|
|
4109
|
+
if (p.op === "lt") return strLen < n;
|
|
4110
|
+
return strLen <= n;
|
|
4111
|
+
}
|
|
4112
|
+
if (typeof lv !== "number") return void 0;
|
|
4113
|
+
if (p.op === "gt") return lv > n;
|
|
4114
|
+
if (p.op === "ge") return lv >= n;
|
|
4115
|
+
if (p.op === "lt") return lv < n;
|
|
4116
|
+
return lv <= n;
|
|
4117
|
+
};
|
|
4118
|
+
const checkReqs = (displayName, reqs, paramNames, absArgs, argIndexOf, loc) => {
|
|
4119
|
+
for (const [idx, pred, field, contractParam] of reqs) {
|
|
4120
|
+
const argIdx = argIndexOf(idx);
|
|
4121
|
+
if (argIdx === void 0) continue;
|
|
4122
|
+
let arg = absArgs[argIdx];
|
|
4123
|
+
if (!arg) continue;
|
|
4124
|
+
const paramName = contractParam || paramNames[idx] || `arg${idx}`;
|
|
4125
|
+
if (field) {
|
|
4126
|
+
const projected = projectArgField(arg, field);
|
|
4127
|
+
if (!projected) {
|
|
4128
|
+
const k = arg.shape.k;
|
|
4129
|
+
if (k !== "unknown" && k !== "any") {
|
|
4130
|
+
out.push({
|
|
4131
|
+
severity: "error",
|
|
4132
|
+
code: "nudo:constraint-violated",
|
|
4133
|
+
message: `${displayName}[${paramName}]: argument \u22AD precondition`,
|
|
4134
|
+
actual: formatAbs(arg),
|
|
4135
|
+
expected: `missing field ${field}`,
|
|
4136
|
+
suggestion: `add the missing field ${field}`,
|
|
4137
|
+
fn: displayName,
|
|
4138
|
+
line: loc?.start.line,
|
|
4139
|
+
column: loc?.start.column
|
|
4140
|
+
});
|
|
4141
|
+
}
|
|
4142
|
+
continue;
|
|
4143
|
+
}
|
|
4144
|
+
arg = projected;
|
|
4145
|
+
}
|
|
4146
|
+
const lv = litValue(arg);
|
|
4147
|
+
const isStr = arg.shape.k === "prim" && arg.shape.type === "string";
|
|
4148
|
+
const strLen = typeof lv === "string" ? lv.length : void 0;
|
|
4149
|
+
const argIsUndef = isExactUndef(arg);
|
|
4150
|
+
for (const p of flattenPred(pred)) {
|
|
4151
|
+
if (p.op === "typeof") {
|
|
4152
|
+
if (p.t.op !== "var") continue;
|
|
4153
|
+
const expected = p.type;
|
|
4154
|
+
const actualPrim = arg.shape.k === "prim" ? arg.shape.type : arg.shape.k === "obj" || arg.shape.k === "arr" || arg.shape.k === "tuple" ? "object" : arg.shape.k === "fn" ? "function" : argIsUndef ? "undefined" : void 0;
|
|
4155
|
+
if (actualPrim !== void 0 && actualPrim !== expected) {
|
|
4156
|
+
out.push({
|
|
4157
|
+
severity: "error",
|
|
4158
|
+
code: "nudo:constraint-violated",
|
|
4159
|
+
message: `${displayName}[${paramName}]: argument \u22AD precondition`,
|
|
4160
|
+
actual: formatAbs(arg),
|
|
4161
|
+
expected: `typeof ${paramName} = "${expected}"`,
|
|
4162
|
+
suggestion: `use a value of type ${expected}, or relax the refine on ${paramName}`,
|
|
4163
|
+
fn: displayName,
|
|
4164
|
+
line: loc?.start.line,
|
|
4165
|
+
column: loc?.start.column
|
|
4166
|
+
});
|
|
4167
|
+
}
|
|
4168
|
+
continue;
|
|
4169
|
+
}
|
|
4170
|
+
if (argIsUndef) {
|
|
4171
|
+
if (p.op === "gt" || p.op === "ge" || p.op === "lt" || p.op === "le" || p.op === "eq" || p.op === "or") {
|
|
4172
|
+
out.push({
|
|
4173
|
+
severity: "error",
|
|
4174
|
+
code: "nudo:constraint-violated",
|
|
4175
|
+
message: `${displayName}[${paramName}]: argument \u22AD precondition`,
|
|
4176
|
+
actual: formatAbs(arg),
|
|
4177
|
+
expected: predToString(p),
|
|
4178
|
+
suggestion: `use a value satisfying ${predToString(p)}, or relax the precondition on ${paramName}`,
|
|
4179
|
+
fn: displayName,
|
|
4180
|
+
line: loc?.start.line,
|
|
4181
|
+
column: loc?.start.column
|
|
4182
|
+
});
|
|
4183
|
+
}
|
|
4184
|
+
continue;
|
|
4185
|
+
}
|
|
4186
|
+
if (lv === void 0) continue;
|
|
4187
|
+
if (typeof lv === "number" || typeof lv === "string" || typeof lv === "boolean") {
|
|
4188
|
+
if (p.op === "eq") {
|
|
4189
|
+
const v2 = predEqLiteral(p);
|
|
4190
|
+
if (v2 !== void 0 && lv !== v2) {
|
|
4191
|
+
out.push({
|
|
4192
|
+
severity: "error",
|
|
4193
|
+
code: "nudo:constraint-violated",
|
|
4194
|
+
message: `${displayName}[${paramName}]: argument \u22AD precondition`,
|
|
4195
|
+
actual: formatAbs(arg),
|
|
4196
|
+
expected: predToString(p),
|
|
4197
|
+
suggestion: `use a value satisfying ${predToString(p)}, or relax the precondition on ${paramName}`,
|
|
4198
|
+
fn: displayName,
|
|
4199
|
+
line: loc?.start.line,
|
|
4200
|
+
column: loc?.start.column
|
|
4201
|
+
});
|
|
4202
|
+
}
|
|
4203
|
+
continue;
|
|
4204
|
+
}
|
|
4205
|
+
if (p.op === "or") {
|
|
4206
|
+
let pass = false;
|
|
4207
|
+
let unknown3 = false;
|
|
4208
|
+
for (const q of p.args) {
|
|
4209
|
+
const r = judgeLiteralPred(q, lv, strLen);
|
|
4210
|
+
if (r === true) {
|
|
4211
|
+
pass = true;
|
|
4212
|
+
break;
|
|
4213
|
+
}
|
|
4214
|
+
if (r === void 0) unknown3 = true;
|
|
4215
|
+
}
|
|
4216
|
+
if (!pass && !unknown3) {
|
|
4217
|
+
out.push({
|
|
4218
|
+
severity: "error",
|
|
4219
|
+
code: "nudo:constraint-violated",
|
|
4220
|
+
message: `${displayName}[${paramName}]: argument \u22AD precondition`,
|
|
4221
|
+
actual: formatAbs(arg),
|
|
4222
|
+
expected: predToString(p),
|
|
4223
|
+
suggestion: `use a value satisfying ${predToString(p)}, or relax the precondition on ${paramName}`,
|
|
4224
|
+
fn: displayName,
|
|
4225
|
+
line: loc?.start.line,
|
|
4226
|
+
column: loc?.start.column
|
|
4227
|
+
});
|
|
4228
|
+
}
|
|
4229
|
+
continue;
|
|
4230
|
+
}
|
|
4231
|
+
}
|
|
4232
|
+
if ((p.op === "gt" || p.op === "ge" || p.op === "lt" || p.op === "le") && p.b.op === "lit" && typeof p.b.value === "number") {
|
|
4233
|
+
const n = p.b.value;
|
|
4234
|
+
const opSym = { gt: ">", ge: "\u2265", lt: "<", le: "\u2264" }[p.op];
|
|
4235
|
+
if (p.a.op === "app" && p.a.fn === "length" && strLen !== void 0) {
|
|
4236
|
+
let ok2 = true;
|
|
4237
|
+
if (p.op === "gt") ok2 = strLen > n;
|
|
4238
|
+
if (p.op === "ge") ok2 = strLen >= n;
|
|
4239
|
+
if (p.op === "lt") ok2 = strLen < n;
|
|
4240
|
+
if (p.op === "le") ok2 = strLen <= n;
|
|
4241
|
+
if (!ok2) {
|
|
4242
|
+
out.push({
|
|
4243
|
+
severity: "error",
|
|
4244
|
+
code: "nudo:constraint-violated",
|
|
4245
|
+
message: `${displayName}[${paramName}]: argument \u22AD precondition`,
|
|
4246
|
+
actual: formatAbs(arg),
|
|
4247
|
+
expected: `length(${paramName}) ${opSym} ${n}`,
|
|
4248
|
+
suggestion: `use a value whose length is ${opSym} ${n}, or relax the precondition on ${paramName}`,
|
|
4249
|
+
fn: displayName,
|
|
4250
|
+
line: loc?.start.line,
|
|
4251
|
+
column: loc?.start.column
|
|
4252
|
+
});
|
|
4253
|
+
}
|
|
4254
|
+
continue;
|
|
4255
|
+
}
|
|
4256
|
+
if (typeof lv === "boolean" || lv === null) {
|
|
4257
|
+
out.push({
|
|
4258
|
+
severity: "error",
|
|
4259
|
+
code: "nudo:constraint-violated",
|
|
4260
|
+
message: `${displayName}[${paramName}]: argument \u22AD precondition`,
|
|
4261
|
+
actual: formatAbs(arg),
|
|
4262
|
+
expected: predToString(p),
|
|
4263
|
+
suggestion: `use a value satisfying ${predToString(p)}, or relax the precondition on ${paramName}`,
|
|
4264
|
+
fn: displayName,
|
|
4265
|
+
line: loc?.start.line,
|
|
4266
|
+
column: loc?.start.column
|
|
4267
|
+
});
|
|
4268
|
+
continue;
|
|
4269
|
+
}
|
|
4270
|
+
if (isStr || typeof lv !== "number") continue;
|
|
4271
|
+
let ok = true;
|
|
4272
|
+
if (p.op === "gt") ok = lv > n;
|
|
4273
|
+
if (p.op === "ge") ok = lv >= n;
|
|
4274
|
+
if (p.op === "lt") ok = lv < n;
|
|
4275
|
+
if (p.op === "le") ok = lv <= n;
|
|
4276
|
+
if (!ok) {
|
|
4277
|
+
out.push({
|
|
4278
|
+
severity: "error",
|
|
4279
|
+
code: "nudo:constraint-violated",
|
|
4280
|
+
message: `${displayName}[${paramName}]: argument \u22AD precondition`,
|
|
4281
|
+
actual: formatAbs(arg),
|
|
4282
|
+
expected: predToString(p),
|
|
4283
|
+
suggestion: `use a value satisfying ${predToString(p)}, or relax the precondition on ${paramName}`,
|
|
4284
|
+
fn: displayName,
|
|
4285
|
+
line: loc?.start.line,
|
|
4286
|
+
column: loc?.start.column
|
|
4287
|
+
});
|
|
4288
|
+
}
|
|
4289
|
+
}
|
|
4290
|
+
}
|
|
4291
|
+
}
|
|
4292
|
+
};
|
|
4293
|
+
const checkShapeAgainstAbs = (displayName, paramName, constraint, absArg, path, loc) => {
|
|
4294
|
+
if (!constraint.fields) return;
|
|
4295
|
+
if (absArg.shape.k === "unknown" && !absArg.term) return;
|
|
4296
|
+
const slots = absArg.shape.k === "obj" ? absArg.shape.slots : void 0;
|
|
4297
|
+
if (!slots) {
|
|
4298
|
+
if (absArg.shape.k !== "unknown" && absArg.shape.k !== "never") {
|
|
4299
|
+
out.push({
|
|
4300
|
+
severity: "error",
|
|
4301
|
+
code: "nudo:constraint-violated",
|
|
4302
|
+
message: `${displayName}[${paramName}]: argument \u22AD precondition`,
|
|
4303
|
+
actual: formatAbs(absArg),
|
|
4304
|
+
expected: `object shape at ${path}`,
|
|
4305
|
+
suggestion: `use an object satisfying the ${path} shape constraint`,
|
|
4306
|
+
fn: displayName,
|
|
4307
|
+
line: loc?.start.line,
|
|
4308
|
+
column: loc?.start.column
|
|
4309
|
+
});
|
|
4310
|
+
}
|
|
4311
|
+
return;
|
|
4312
|
+
}
|
|
4313
|
+
for (const [key, field] of Object.entries(constraint.fields)) {
|
|
4314
|
+
const slot = getSlot(slots, key);
|
|
4315
|
+
const fieldPath = path === paramName ? `${paramName}.${key}` : `${path}.${key}`;
|
|
4316
|
+
if (!slot) {
|
|
4317
|
+
if (!field.optional && !field.constraint.isOptional) {
|
|
4318
|
+
out.push({
|
|
4319
|
+
severity: "error",
|
|
4320
|
+
code: "nudo:constraint-violated",
|
|
4321
|
+
message: `${displayName}[${paramName}]: argument \u22AD precondition`,
|
|
4322
|
+
actual: formatAbs(absArg),
|
|
4323
|
+
expected: `missing field ${fieldPath}`,
|
|
4324
|
+
suggestion: `add the missing field ${fieldPath}`,
|
|
4325
|
+
fn: displayName,
|
|
4326
|
+
line: loc?.start.line,
|
|
4327
|
+
column: loc?.start.column
|
|
4328
|
+
});
|
|
4329
|
+
}
|
|
4330
|
+
continue;
|
|
4331
|
+
}
|
|
4332
|
+
checkFieldConstraint(displayName, paramName, field.constraint, slot.value, fieldPath, loc);
|
|
4333
|
+
}
|
|
4334
|
+
};
|
|
4335
|
+
const checkFieldConstraint = (displayName, paramName, constraint, fieldAbs, fieldPath, loc) => {
|
|
4336
|
+
if (fieldAbs.shape.k === "unknown" && !fieldAbs.term) return;
|
|
4337
|
+
if (constraint.prim && fieldAbs.shape.k === "prim") {
|
|
4338
|
+
const actualPrim = fieldAbs.shape.type;
|
|
4339
|
+
if (actualPrim !== constraint.prim) {
|
|
4340
|
+
out.push({
|
|
4341
|
+
severity: "error",
|
|
4342
|
+
code: "nudo:constraint-violated",
|
|
4343
|
+
message: `${displayName}[${paramName}]: argument \u22AD precondition`,
|
|
4344
|
+
actual: formatAbs(fieldAbs),
|
|
4345
|
+
expected: `typeof ${fieldPath} = "${constraint.prim}"`,
|
|
4346
|
+
suggestion: `change ${fieldPath} to ${constraint.prim}`,
|
|
4347
|
+
fn: displayName,
|
|
4348
|
+
line: loc?.start.line,
|
|
4349
|
+
column: loc?.start.column
|
|
4350
|
+
});
|
|
4351
|
+
return;
|
|
4352
|
+
}
|
|
4353
|
+
}
|
|
4354
|
+
if (isIntFlag(constraint)) {
|
|
4355
|
+
const iv = litValue(fieldAbs);
|
|
4356
|
+
if (typeof iv === "number" && !Number.isInteger(iv)) {
|
|
4357
|
+
out.push({
|
|
4358
|
+
severity: "error",
|
|
4359
|
+
code: "nudo:constraint-violated",
|
|
4360
|
+
message: `${displayName}[${paramName}]: argument \u22AD precondition`,
|
|
4361
|
+
actual: formatAbs(fieldAbs),
|
|
4362
|
+
expected: `${fieldPath} is int`,
|
|
4363
|
+
suggestion: `change ${fieldPath} to an integer`,
|
|
4364
|
+
fn: displayName,
|
|
4365
|
+
line: loc?.start.line,
|
|
4366
|
+
column: loc?.start.column
|
|
4367
|
+
});
|
|
4368
|
+
}
|
|
4369
|
+
}
|
|
4370
|
+
if (constraint.fields) {
|
|
4371
|
+
checkShapeAgainstAbs(displayName, paramName, constraint, fieldAbs, fieldPath, loc);
|
|
4372
|
+
}
|
|
4373
|
+
if (constraint.element) {
|
|
4374
|
+
if (fieldAbs.shape.k === "arr") {
|
|
4375
|
+
checkFieldConstraint(
|
|
4376
|
+
displayName,
|
|
4377
|
+
paramName,
|
|
4378
|
+
constraint.element,
|
|
4379
|
+
fieldAbs.shape.element,
|
|
4380
|
+
`${fieldPath}[]`,
|
|
4381
|
+
loc
|
|
4382
|
+
);
|
|
4383
|
+
} else if (fieldAbs.shape.k === "tuple") {
|
|
4384
|
+
const els = fieldAbs.shape.elements;
|
|
4385
|
+
els.forEach((el, i) => {
|
|
4386
|
+
checkFieldConstraint(
|
|
4387
|
+
displayName,
|
|
4388
|
+
paramName,
|
|
4389
|
+
constraint.element,
|
|
4390
|
+
el,
|
|
4391
|
+
`${fieldPath}[${i}]`,
|
|
4392
|
+
loc
|
|
4393
|
+
);
|
|
4394
|
+
});
|
|
4395
|
+
}
|
|
4396
|
+
}
|
|
4397
|
+
const lv = litValue(fieldAbs);
|
|
4398
|
+
const sv = typeof lv === "string" ? lv.length : void 0;
|
|
4399
|
+
const isStr = fieldAbs.shape.k === "prim" && fieldAbs.shape.type === "string";
|
|
4400
|
+
for (const p of constraint.preds) {
|
|
4401
|
+
const flat = p.op === "and" ? p.args : [p];
|
|
4402
|
+
for (const atom of flat) {
|
|
4403
|
+
if ((atom.op === "gt" || atom.op === "ge" || atom.op === "lt" || atom.op === "le") && atom.b.op === "lit" && typeof atom.b.value === "number") {
|
|
4404
|
+
const n = atom.b.value;
|
|
4405
|
+
const opSym = { gt: ">", ge: "\u2265", lt: "<", le: "\u2264" }[atom.op];
|
|
4406
|
+
if (atom.a.op === "app" && atom.a.fn === "length") {
|
|
4407
|
+
if (sv === void 0) continue;
|
|
4408
|
+
let ok2 = true;
|
|
4409
|
+
if (atom.op === "gt") ok2 = sv > n;
|
|
4410
|
+
if (atom.op === "ge") ok2 = sv >= n;
|
|
4411
|
+
if (atom.op === "lt") ok2 = sv < n;
|
|
4412
|
+
if (atom.op === "le") ok2 = sv <= n;
|
|
4413
|
+
if (!ok2) {
|
|
4414
|
+
out.push({
|
|
4415
|
+
severity: "error",
|
|
4416
|
+
code: "nudo:constraint-violated",
|
|
4417
|
+
message: `${displayName}[${paramName}]: argument \u22AD precondition`,
|
|
4418
|
+
actual: formatAbs(fieldAbs),
|
|
4419
|
+
expected: `length(${fieldPath}) ${opSym} ${n}`,
|
|
4420
|
+
suggestion: `use a ${fieldPath} whose length is ${opSym} ${n}`,
|
|
4421
|
+
fn: displayName,
|
|
4422
|
+
line: loc?.start.line,
|
|
4423
|
+
column: loc?.start.column
|
|
4424
|
+
});
|
|
4425
|
+
}
|
|
4426
|
+
continue;
|
|
4427
|
+
}
|
|
4428
|
+
if (lv === void 0 || typeof lv !== "number" || isStr) continue;
|
|
4429
|
+
let ok = true;
|
|
4430
|
+
if (atom.op === "gt") ok = lv > n;
|
|
4431
|
+
if (atom.op === "ge") ok = lv >= n;
|
|
4432
|
+
if (atom.op === "lt") ok = lv < n;
|
|
4433
|
+
if (atom.op === "le") ok = lv <= n;
|
|
4434
|
+
if (!ok) {
|
|
4435
|
+
out.push({
|
|
4436
|
+
severity: "error",
|
|
4437
|
+
code: "nudo:constraint-violated",
|
|
4438
|
+
message: `${displayName}[${paramName}]: argument \u22AD precondition`,
|
|
4439
|
+
actual: formatAbs(fieldAbs),
|
|
4440
|
+
expected: `${fieldPath} ${opSym} ${n}`,
|
|
4441
|
+
suggestion: `use a value satisfying ${fieldPath} ${opSym} ${n}, or relax the precondition on ${paramName}`,
|
|
4442
|
+
fn: displayName,
|
|
4443
|
+
line: loc?.start.line,
|
|
4444
|
+
column: loc?.start.column
|
|
4445
|
+
});
|
|
4446
|
+
}
|
|
4447
|
+
}
|
|
4448
|
+
}
|
|
4449
|
+
}
|
|
4450
|
+
if (lv !== void 0 && (typeof lv === "number" || typeof lv === "string" || typeof lv === "boolean") && ((constraint.members?.length ?? 0) > 0 || constraint.preds.some((p) => p.op === "eq")) && !literalMeetsConstraint(lv, constraint)) {
|
|
4451
|
+
out.push({
|
|
4452
|
+
severity: "error",
|
|
4453
|
+
code: "nudo:constraint-violated",
|
|
4454
|
+
message: `${displayName}[${paramName}]: argument \u22AD precondition`,
|
|
4455
|
+
actual: formatAbs(fieldAbs),
|
|
4456
|
+
expected: `${fieldPath} \u2208 ${formatConstraint(constraint)}`,
|
|
4457
|
+
suggestion: `use a ${fieldPath} satisfying ${formatConstraint(constraint)}`,
|
|
4458
|
+
fn: displayName,
|
|
4459
|
+
line: loc?.start.line,
|
|
4460
|
+
column: loc?.start.column
|
|
4461
|
+
});
|
|
4462
|
+
}
|
|
4463
|
+
};
|
|
4464
|
+
const checkShapeReqs = (displayName, reqs, paramNames, absArgs, argIndexOf, loc) => {
|
|
4465
|
+
for (const [idx, entry, field] of reqs) {
|
|
4466
|
+
const c = entry.constraint;
|
|
4467
|
+
if (!c.fields && !c.element && !isIntFlag(c) && !c.prim) continue;
|
|
4468
|
+
const argIdx = argIndexOf(idx);
|
|
4469
|
+
if (argIdx === void 0) continue;
|
|
4470
|
+
let arg = absArgs[argIdx];
|
|
4471
|
+
if (!arg) continue;
|
|
4472
|
+
const paramName = entry.param || paramNames[idx] || `arg${idx}`;
|
|
4473
|
+
if (field) {
|
|
4474
|
+
const projected = projectArgField(arg, field);
|
|
4475
|
+
if (!projected) {
|
|
4476
|
+
const k = arg.shape.k;
|
|
4477
|
+
if (k !== "unknown" && k !== "any") {
|
|
4478
|
+
out.push({
|
|
4479
|
+
severity: "error",
|
|
4480
|
+
code: "nudo:constraint-violated",
|
|
4481
|
+
message: `${displayName}[${paramName}]: argument \u22AD precondition`,
|
|
4482
|
+
actual: formatAbs(arg),
|
|
4483
|
+
expected: `missing field ${field}`,
|
|
4484
|
+
suggestion: `add the missing field ${field}`,
|
|
4485
|
+
fn: displayName,
|
|
4486
|
+
line: loc?.start.line,
|
|
4487
|
+
column: loc?.start.column
|
|
4488
|
+
});
|
|
4489
|
+
}
|
|
4490
|
+
continue;
|
|
4491
|
+
}
|
|
4492
|
+
arg = projected;
|
|
4493
|
+
}
|
|
4494
|
+
if (c.fields) {
|
|
4495
|
+
checkShapeAgainstAbs(displayName, paramName, c, arg, paramName, loc);
|
|
4496
|
+
}
|
|
4497
|
+
if (c.element) {
|
|
4498
|
+
if (arg.shape.k === "arr") {
|
|
4499
|
+
checkFieldConstraint(
|
|
4500
|
+
displayName,
|
|
4501
|
+
paramName,
|
|
4502
|
+
c.element,
|
|
4503
|
+
arg.shape.element,
|
|
4504
|
+
`${paramName}[]`,
|
|
4505
|
+
loc
|
|
4506
|
+
);
|
|
4507
|
+
} else if (arg.shape.k === "tuple") {
|
|
4508
|
+
const els = arg.shape.elements;
|
|
4509
|
+
els.forEach((el, i) => {
|
|
4510
|
+
checkFieldConstraint(
|
|
4511
|
+
displayName,
|
|
4512
|
+
paramName,
|
|
4513
|
+
c.element,
|
|
4514
|
+
el,
|
|
4515
|
+
`${paramName}[${i}]`,
|
|
4516
|
+
loc
|
|
4517
|
+
);
|
|
4518
|
+
});
|
|
4519
|
+
} else if (isExactUndef(arg) || arg.shape.k !== "unknown" && arg.shape.k !== "any") {
|
|
4520
|
+
out.push({
|
|
4521
|
+
severity: "error",
|
|
4522
|
+
code: "nudo:constraint-violated",
|
|
4523
|
+
message: `${displayName}[${paramName}]: argument \u22AD precondition`,
|
|
4524
|
+
actual: formatAbs(arg),
|
|
4525
|
+
expected: `array at ${paramName}`,
|
|
4526
|
+
suggestion: `use a value satisfying the array constraint`,
|
|
4527
|
+
fn: displayName,
|
|
4528
|
+
line: loc?.start.line,
|
|
4529
|
+
column: loc?.start.column
|
|
4530
|
+
});
|
|
4531
|
+
}
|
|
4532
|
+
}
|
|
4533
|
+
if (isIntFlag(c)) {
|
|
4534
|
+
const iv = litValue(arg);
|
|
4535
|
+
if (typeof iv === "number" && !Number.isInteger(iv)) {
|
|
4536
|
+
out.push({
|
|
4537
|
+
severity: "error",
|
|
4538
|
+
code: "nudo:constraint-violated",
|
|
4539
|
+
message: `${displayName}[${paramName}]: argument \u22AD precondition`,
|
|
4540
|
+
actual: formatAbs(arg),
|
|
4541
|
+
expected: `${paramName} is int`,
|
|
4542
|
+
suggestion: `use an integer`,
|
|
4543
|
+
fn: displayName,
|
|
4544
|
+
line: loc?.start.line,
|
|
4545
|
+
column: loc?.start.column
|
|
4546
|
+
});
|
|
4547
|
+
}
|
|
4548
|
+
}
|
|
4549
|
+
}
|
|
4550
|
+
};
|
|
4551
|
+
const parseCallArgs = (args) => {
|
|
4552
|
+
const absArgs = [];
|
|
4553
|
+
let hasInfo = false;
|
|
4554
|
+
const note = (abs2) => {
|
|
4555
|
+
if (abs2 && isInformativeArg(abs2)) hasInfo = true;
|
|
4556
|
+
};
|
|
4557
|
+
for (const a of args ?? []) {
|
|
4558
|
+
if (a.type === "NumericLiteral" && typeof a.value === "number") {
|
|
4559
|
+
absArgs.push(numLit(a.value));
|
|
4560
|
+
hasInfo = true;
|
|
4561
|
+
} else if (a.type === "UnaryExpression" && a.operator === "-" && a.argument?.type === "NumericLiteral") {
|
|
4562
|
+
const num = a.argument;
|
|
4563
|
+
absArgs.push(numLit(-num.value));
|
|
4564
|
+
hasInfo = true;
|
|
4565
|
+
} else if (a.type === "ObjectExpression") {
|
|
4566
|
+
const abs2 = evalArgAbs(a, (n) => varAbs.get(n));
|
|
4567
|
+
absArgs.push(abs2 ?? absUnknown());
|
|
4568
|
+
note(abs2);
|
|
4569
|
+
} else if (a.type === "ArrayExpression") {
|
|
4570
|
+
const abs2 = evalArgAbs(a, (n) => varAbs.get(n));
|
|
4571
|
+
absArgs.push(abs2 ?? absUnknown());
|
|
4572
|
+
note(abs2);
|
|
4573
|
+
} else if (a.type === "StringLiteral" && typeof a.value === "string") {
|
|
4574
|
+
absArgs.push({
|
|
4575
|
+
shape: { k: "prim", type: "string" },
|
|
4576
|
+
term: { op: "lit", value: a.value },
|
|
4577
|
+
conf: "exact"
|
|
4578
|
+
});
|
|
4579
|
+
hasInfo = true;
|
|
4580
|
+
} else if (a.type === "Identifier" && typeof a.name === "string") {
|
|
4581
|
+
const abs2 = varAbs.get(a.name);
|
|
4582
|
+
absArgs.push(abs2 ?? absUnknown());
|
|
4583
|
+
note(abs2);
|
|
4584
|
+
} else if (a.type === "MemberExpression") {
|
|
4585
|
+
const computed = a.computed === true;
|
|
4586
|
+
const prop = a.property;
|
|
4587
|
+
const litKey = prop?.type === "StringLiteral" || prop?.type === "NumericLiteral";
|
|
4588
|
+
if (computed && !litKey) {
|
|
4589
|
+
absArgs.push(anyAbs);
|
|
4590
|
+
hasInfo = true;
|
|
4591
|
+
continue;
|
|
4592
|
+
}
|
|
4593
|
+
const abs2 = evalArgAbs(a, (n) => varAbs.get(n));
|
|
4594
|
+
absArgs.push(abs2 ?? absUnknown());
|
|
4595
|
+
note(abs2);
|
|
4596
|
+
} else if (a.type === "SpreadElement") {
|
|
4597
|
+
const inner = a.argument;
|
|
4598
|
+
if (inner?.type === "ArrayExpression") {
|
|
4599
|
+
for (const el of inner.elements ?? []) {
|
|
4600
|
+
if (!el) continue;
|
|
4601
|
+
if (el.type === "NumericLiteral" && typeof el.value === "number") {
|
|
4602
|
+
absArgs.push(numLit(el.value));
|
|
4603
|
+
hasInfo = true;
|
|
4604
|
+
} else if (el.type === "StringLiteral" && typeof el.value === "string") {
|
|
4605
|
+
absArgs.push({
|
|
4606
|
+
shape: { k: "prim", type: "string" },
|
|
4607
|
+
term: { op: "lit", value: el.value },
|
|
4608
|
+
conf: "exact"
|
|
4609
|
+
});
|
|
4610
|
+
hasInfo = true;
|
|
4611
|
+
} else {
|
|
4612
|
+
const abs2 = evalArgAbs(el, (n) => varAbs.get(n));
|
|
4613
|
+
absArgs.push(abs2 ?? absUnknown());
|
|
4614
|
+
note(abs2);
|
|
4615
|
+
}
|
|
4616
|
+
}
|
|
4617
|
+
} else {
|
|
4618
|
+
absArgs.push(absUnknown());
|
|
4619
|
+
}
|
|
4620
|
+
} else if (a.type === "CallExpression") {
|
|
4621
|
+
absArgs.push(absUnknown());
|
|
4622
|
+
} else {
|
|
4623
|
+
absArgs.push(absUnknown());
|
|
4624
|
+
}
|
|
4625
|
+
}
|
|
4626
|
+
return { absArgs, hasInfo };
|
|
4627
|
+
};
|
|
4628
|
+
const checkOneCall = (fnName, args, loc) => {
|
|
4629
|
+
if (!knownFns.includes(fnName)) return;
|
|
4630
|
+
checkArgStructures(fnName, source, args, loc);
|
|
4631
|
+
const parsed = parseCallArgs(args);
|
|
4632
|
+
let absArgs = parsed.absArgs;
|
|
4633
|
+
let hasInfo = parsed.hasInfo;
|
|
4634
|
+
if (!hasInfo) {
|
|
4635
|
+
const bArgs = takeBCallArgs(fnName, loc?.start.line);
|
|
4636
|
+
if (bArgs && bArgs.length > 0) {
|
|
4637
|
+
absArgs = bArgs;
|
|
4638
|
+
hasInfo = bArgs.some(isInformativeArg);
|
|
4639
|
+
}
|
|
4640
|
+
}
|
|
4641
|
+
if (!hasInfo || absArgs.length === 0) return;
|
|
4642
|
+
const g = generalizeFromAst(fnName, source, {
|
|
4643
|
+
file,
|
|
4644
|
+
refine: {
|
|
4645
|
+
loadModule: opts?.loadModule,
|
|
4646
|
+
fromFile: opts?.fromFile ?? "",
|
|
4647
|
+
...opts?.autoBind !== void 0 ? { autoBind: opts.autoBind } : {},
|
|
4648
|
+
...opts?.projectDir !== void 0 ? { projectDir: opts.projectDir } : {}
|
|
4649
|
+
}
|
|
4650
|
+
});
|
|
4651
|
+
const paramNames = g?.params ?? [];
|
|
4652
|
+
const optsR = {
|
|
4653
|
+
loadModule: opts?.loadModule,
|
|
4654
|
+
fromFile: opts?.fromFile ?? "",
|
|
4655
|
+
...opts?.autoBind !== void 0 ? { autoBind: opts.autoBind } : {},
|
|
4656
|
+
...opts?.projectDir !== void 0 ? { projectDir: opts.projectDir } : {}
|
|
4657
|
+
};
|
|
4658
|
+
const ownEi = effectiveInterfaceOf(fnName, source, optsR);
|
|
4659
|
+
if (ownEi?.source === "handwritten") {
|
|
4660
|
+
const ownFull = interfaceToIndexed(ownEi, paramNames, g?.formals);
|
|
4661
|
+
checkShapeReqs(fnName, ownFull, paramNames, absArgs, (i) => i, loc);
|
|
4662
|
+
checkReqs(
|
|
4663
|
+
fnName,
|
|
4664
|
+
ownFull.map(([i, e, f]) => [i, e.pred, f, e.param]),
|
|
4665
|
+
paramNames,
|
|
4666
|
+
absArgs,
|
|
4667
|
+
(i) => i,
|
|
4668
|
+
loc
|
|
4669
|
+
);
|
|
4670
|
+
}
|
|
4671
|
+
const fwd = forwards.get(fnName);
|
|
4672
|
+
if (fwd) {
|
|
4673
|
+
const tg = generalizeFromAst(fwd.target, source, file ? { file } : {});
|
|
4674
|
+
const tParams = tg?.params ?? [];
|
|
4675
|
+
const tEi = effectiveInterfaceOf(fwd.target, source, optsR);
|
|
4676
|
+
const tFull = tEi?.source === "handwritten" ? interfaceToIndexed(tEi, tParams, tg?.formals) : [];
|
|
4677
|
+
if (tFull.length > 0) {
|
|
4678
|
+
const wrapperArgOfTarget = /* @__PURE__ */ new Map();
|
|
4679
|
+
fwd.map.forEach((wrapperIdx, targetIdx) => {
|
|
4680
|
+
wrapperArgOfTarget.set(targetIdx, wrapperIdx);
|
|
4681
|
+
});
|
|
4682
|
+
const mapArg = (targetIdx) => wrapperArgOfTarget.get(targetIdx);
|
|
4683
|
+
checkShapeReqs(`${fnName}\u2192${fwd.target}`, tFull, tParams, absArgs, mapArg, loc);
|
|
4684
|
+
checkReqs(
|
|
4685
|
+
`${fnName}\u2192${fwd.target}`,
|
|
4686
|
+
tFull.map(([i, e, f]) => [i, e.pred, f, e.param]),
|
|
4687
|
+
tParams,
|
|
4688
|
+
absArgs,
|
|
4689
|
+
mapArg,
|
|
4690
|
+
loc
|
|
4691
|
+
);
|
|
4692
|
+
}
|
|
4693
|
+
}
|
|
4694
|
+
};
|
|
4695
|
+
const checkExternalCall = (ext, args, displayName, loc) => {
|
|
4696
|
+
checkArgStructures(ext.fnName, ext.source, args, loc, displayName, ext.fromFile);
|
|
4697
|
+
const parsedExt = parseCallArgs(args);
|
|
4698
|
+
let absArgs = parsedExt.absArgs;
|
|
4699
|
+
let hasInfo = parsedExt.hasInfo;
|
|
4700
|
+
if (!hasInfo) {
|
|
4701
|
+
const bArgs = takeBCallArgs(ext.fnName, loc?.start.line);
|
|
4702
|
+
if (bArgs && bArgs.length > 0) {
|
|
4703
|
+
absArgs = bArgs;
|
|
4704
|
+
hasInfo = bArgs.some(isInformativeArg);
|
|
4705
|
+
}
|
|
4706
|
+
}
|
|
4707
|
+
if (!hasInfo || absArgs.length === 0) return;
|
|
4708
|
+
let full = [];
|
|
4709
|
+
let paramNames = [];
|
|
4710
|
+
try {
|
|
4711
|
+
const g = generalizeFromAst(ext.fnName, ext.source);
|
|
4712
|
+
paramNames = g?.params ?? [];
|
|
4713
|
+
const eiOpts = {
|
|
4714
|
+
loadModule: opts?.loadModule,
|
|
4715
|
+
fromFile: ext.fromFile ?? opts?.fromFile ?? "",
|
|
4716
|
+
...ext.fromFile ? {} : { autoBind: false },
|
|
4717
|
+
...ext.fromFile && opts?.autoBind !== void 0 ? { autoBind: opts.autoBind } : {},
|
|
4718
|
+
...opts?.projectDir !== void 0 ? { projectDir: opts.projectDir } : {}
|
|
4719
|
+
};
|
|
4720
|
+
const ei = effectiveInterfaceOf(ext.fnName, ext.source, eiOpts);
|
|
4721
|
+
if (ei?.source === "handwritten") {
|
|
4722
|
+
full = interfaceToIndexed(ei, paramNames, g?.formals);
|
|
4723
|
+
}
|
|
4724
|
+
} catch {
|
|
4725
|
+
out.push({
|
|
4726
|
+
severity: "warning",
|
|
4727
|
+
code: "nudo:no-signature",
|
|
4728
|
+
message: `${displayName}: external callee contract could not be resolved; skipping call-site precondition check`,
|
|
4729
|
+
fn: displayName,
|
|
4730
|
+
line: loc?.start.line,
|
|
4731
|
+
column: loc?.start.column
|
|
4732
|
+
});
|
|
4733
|
+
return;
|
|
4734
|
+
}
|
|
4735
|
+
checkShapeReqs(displayName, full, paramNames, absArgs, (i) => i, loc);
|
|
4736
|
+
checkReqs(
|
|
4737
|
+
displayName,
|
|
4738
|
+
full.map(([i, e, f]) => [i, e.pred, f, e.param]),
|
|
4739
|
+
paramNames,
|
|
4740
|
+
absArgs,
|
|
4741
|
+
(i) => i,
|
|
4742
|
+
loc
|
|
4743
|
+
);
|
|
4744
|
+
};
|
|
4745
|
+
const checkHofFnRelArgs = (g, args, loc, displayName, evalArg) => {
|
|
4746
|
+
const fnRels = g.fnRels;
|
|
4747
|
+
if (!fnRels) return;
|
|
4748
|
+
for (let i = 0; i < g.params.length; i++) {
|
|
4749
|
+
const pname = g.params[i];
|
|
4750
|
+
const rel = fnRels.get(pname);
|
|
4751
|
+
if (!rel) continue;
|
|
4752
|
+
const expected = rel.abs;
|
|
4753
|
+
if (expected.shape.k !== "fn") continue;
|
|
4754
|
+
const argNode = args[i];
|
|
4755
|
+
if (!argNode) continue;
|
|
4756
|
+
const absArg = evalArg(argNode);
|
|
4757
|
+
if (!absArg) continue;
|
|
4758
|
+
if (absArg.shape.k === "any" || absArg.shape.k === "unknown") continue;
|
|
4759
|
+
if (getFnImpl(absArg)) continue;
|
|
4760
|
+
if (absArg.shape.k === "sum") {
|
|
4761
|
+
const okAny = absArg.shape.members.some(
|
|
4762
|
+
(m) => hofFnArgOk(m, expected)
|
|
4763
|
+
);
|
|
4764
|
+
if (okAny) continue;
|
|
4765
|
+
}
|
|
4766
|
+
if (!hofFnArgOk(absArg, expected)) {
|
|
4767
|
+
const isPromote = rel.source === "promote";
|
|
4768
|
+
out.push({
|
|
4769
|
+
severity: isPromote ? "warning" : "error",
|
|
4770
|
+
code: "nudo:arg-structure",
|
|
4771
|
+
message: `${displayName}[${pname}]: argument is not a callable fn`,
|
|
4772
|
+
actual: formatAbs(absArg),
|
|
4773
|
+
expected: formatShape(expected),
|
|
4774
|
+
suggestion: `expected ${formatShape(expected)}`,
|
|
4775
|
+
fn: displayName,
|
|
4776
|
+
line: loc?.start.line,
|
|
4777
|
+
column: loc?.start.column
|
|
4778
|
+
});
|
|
4779
|
+
}
|
|
4780
|
+
}
|
|
4781
|
+
};
|
|
4782
|
+
const checkArgStructures = (fnName, fnSource, args, loc, displayName, _interfaceFromFile) => {
|
|
4783
|
+
let gFn;
|
|
4784
|
+
try {
|
|
4785
|
+
const sameFile = fnSource === source;
|
|
4786
|
+
gFn = generalizeFromAst(
|
|
4787
|
+
fnName,
|
|
4788
|
+
fnSource,
|
|
4789
|
+
{
|
|
4790
|
+
...sameFile && file ? { file } : {},
|
|
4791
|
+
// refine 侧车/源码契约必须进 generalize——否则 fnRels 只剩 promote,
|
|
4792
|
+
// refine→error 路径永远打不开(source 永远是 warning)。
|
|
4793
|
+
refine: {
|
|
4794
|
+
loadModule: opts?.loadModule,
|
|
4795
|
+
fromFile: opts?.fromFile ?? "",
|
|
4796
|
+
...opts?.autoBind !== void 0 ? { autoBind: opts.autoBind } : {},
|
|
4797
|
+
...opts?.projectDir !== void 0 ? { projectDir: opts.projectDir } : {}
|
|
4798
|
+
}
|
|
4799
|
+
}
|
|
4800
|
+
);
|
|
4801
|
+
} catch (e) {
|
|
4802
|
+
out.push({
|
|
4803
|
+
severity: "warning",
|
|
4804
|
+
code: "nudo:no-signature",
|
|
4805
|
+
message: `${displayName ?? fnName}: signature recovery failed (${e instanceof Error ? e.message : String(e)}); skipping call-site checks`,
|
|
4806
|
+
fn: displayName ?? fnName,
|
|
4807
|
+
line: loc?.start.line,
|
|
4808
|
+
column: loc?.start.column
|
|
4809
|
+
});
|
|
4810
|
+
return;
|
|
4811
|
+
}
|
|
4812
|
+
if (gFn?.fnRels && gFn.fnRels.size > 0) {
|
|
4813
|
+
checkHofFnRelArgs(
|
|
4814
|
+
gFn,
|
|
4815
|
+
args,
|
|
4816
|
+
loc,
|
|
4817
|
+
displayName ?? fnName,
|
|
4818
|
+
(n) => evalArgAbs(n, (x) => varAbs.get(x))
|
|
4819
|
+
);
|
|
4820
|
+
}
|
|
4821
|
+
};
|
|
4822
|
+
const visit2 = (n) => {
|
|
4823
|
+
if (!n || typeof n !== "object") return;
|
|
4824
|
+
const obj = n;
|
|
4825
|
+
if (obj.type === "CallExpression") {
|
|
4826
|
+
const callee = obj.callee;
|
|
4827
|
+
const args = obj.arguments ?? [];
|
|
4828
|
+
const resolved = resolveCalleeFn(callee, resolve, knownSet);
|
|
4829
|
+
if (typeof resolved === "string") {
|
|
4830
|
+
checkOneCall(resolved, args, obj.loc);
|
|
4831
|
+
} else if (resolved?.external) {
|
|
4832
|
+
const name = resolved.external.fnName || "require()";
|
|
4833
|
+
checkExternalCall(resolved.external, args, name, obj.loc);
|
|
4834
|
+
}
|
|
4835
|
+
}
|
|
4836
|
+
for (const key of Object.keys(obj)) {
|
|
4837
|
+
if (key === "loc" || key === "start" || key === "end") continue;
|
|
4838
|
+
const val = obj[key];
|
|
4839
|
+
if (Array.isArray(val)) val.forEach(visit2);
|
|
4840
|
+
else if (val && typeof val === "object") visit2(val);
|
|
4841
|
+
}
|
|
4842
|
+
};
|
|
4843
|
+
visit2(file);
|
|
4844
|
+
return out;
|
|
4845
|
+
}
|
|
4846
|
+
|
|
4847
|
+
export {
|
|
4848
|
+
SELF,
|
|
4849
|
+
getTerm,
|
|
4850
|
+
lenTerm,
|
|
4851
|
+
isNudoConstraint,
|
|
4852
|
+
isIntFlag,
|
|
4853
|
+
number,
|
|
4854
|
+
string,
|
|
4855
|
+
boolean,
|
|
4856
|
+
any,
|
|
4857
|
+
array,
|
|
4858
|
+
shape,
|
|
4859
|
+
litC,
|
|
4860
|
+
union,
|
|
4861
|
+
fn,
|
|
4862
|
+
andC,
|
|
4863
|
+
partial,
|
|
4864
|
+
pick,
|
|
4865
|
+
omit,
|
|
4866
|
+
instantiateConstraint,
|
|
4867
|
+
constraintToEntryAbs,
|
|
4868
|
+
fnConstraintToEntryReqs,
|
|
4869
|
+
throwConstraintToKinds,
|
|
4870
|
+
hashSource,
|
|
4871
|
+
resetHashSourceCache,
|
|
4872
|
+
normPath,
|
|
4873
|
+
resolveDepPath,
|
|
4874
|
+
isNodeModulesPath,
|
|
4875
|
+
sidecarPathOf,
|
|
4876
|
+
extractNudoImports,
|
|
4877
|
+
setRefineDiagCollector,
|
|
4878
|
+
refineDiagCount,
|
|
4879
|
+
takeRefineDiags,
|
|
4880
|
+
takeRefineDiagsSince,
|
|
4881
|
+
NudoSidecarError,
|
|
4882
|
+
execNudoModule,
|
|
4883
|
+
resetNudoModuleExecCache,
|
|
4884
|
+
extractRefinesFromSource,
|
|
4885
|
+
refineToIndexedFull,
|
|
4886
|
+
extractRefineReturnFromSource,
|
|
4887
|
+
extractDeclaredThrows,
|
|
4888
|
+
setInterfaceDiagCollector,
|
|
4889
|
+
interfaceDiagCount,
|
|
4890
|
+
takeInterfaceDiags,
|
|
4891
|
+
takeInterfaceDiagsSince,
|
|
4892
|
+
localNamedExports,
|
|
4893
|
+
generatedExportNames,
|
|
4894
|
+
effectiveInterface,
|
|
4895
|
+
sidecarClosureFingerprint,
|
|
4896
|
+
formatConstraint,
|
|
4897
|
+
formatEffectiveInterfaceDisplay,
|
|
4898
|
+
formatInterfaceTierLine,
|
|
4899
|
+
interfaceTierOf,
|
|
4900
|
+
interfaceSourceOf,
|
|
4901
|
+
resetFnFpCache,
|
|
4902
|
+
getFnNameAndBodies,
|
|
4903
|
+
fnFingerprints,
|
|
4904
|
+
generalizeSourceKeyPart,
|
|
4905
|
+
canSkipLiteralCallScan,
|
|
4906
|
+
extractAllLoadSpecs,
|
|
4907
|
+
sidecarSpecsOf,
|
|
4908
|
+
loadModuleDepsFingerprint,
|
|
4909
|
+
collectAbsFreeVars,
|
|
4910
|
+
resetGeneralizeMemo,
|
|
4911
|
+
getGeneralizeMemoSize,
|
|
4912
|
+
evictGeneralizeMemoForPaths,
|
|
4913
|
+
extractFn,
|
|
4914
|
+
generalizeFromAst,
|
|
4915
|
+
generalizeAll,
|
|
4916
|
+
listFunctionNames,
|
|
4917
|
+
buildArgsFromAssume,
|
|
4918
|
+
literalMeetsConstraint,
|
|
4919
|
+
stableAnalyzeKeySource,
|
|
4920
|
+
listTopFunctions,
|
|
4921
|
+
checkInjectedDomainEvidence,
|
|
4922
|
+
scanLiteralCalls
|
|
4923
|
+
};
|