@nudojs/harvester 0.2.6 → 0.2.8-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,5 +1,33 @@
1
1
  import { Abs } from '@nudojs/core';
2
2
 
3
+ type DtsExportDraft = {
4
+ name: string;
5
+ /** `fn({ … }, …)` 或值约束 DSL;不可投影 → undefined + note */
6
+ dsl?: string;
7
+ note?: string;
8
+ kind: "function" | "value" | "class" | "other";
9
+ };
10
+ type DtsContractDraft = {
11
+ label: string;
12
+ files: string[];
13
+ exports: DtsExportDraft[];
14
+ draftSource: string;
15
+ stats: {
16
+ files: number;
17
+ exports: number;
18
+ skipped: number;
19
+ };
20
+ };
21
+ /** 组装 `@nudo:draft` 模块(与 formatDraftModule 同纪律:不 ambient 绑定)。 */
22
+ declare function formatDtsContractDraft(label: string, files: string[], exports: DtsExportDraft[]): string;
23
+ /**
24
+ * 从 .d.ts 路径列表生成契约草稿(不写盘)。
25
+ * `label` 用于头注释(包名或描述)。
26
+ */
27
+ declare function dtsToContractDraft(dtsFiles: string[], label?: string): DtsContractDraft;
28
+ /** 目录/文件入口:内部解析 .d.ts 列表。 */
29
+ declare function dtsPathToContractDraft(path: string, label?: string): DtsContractDraft;
30
+
3
31
  /**
4
32
  * Harvested env:Abs 原生(与 EnvDefinition 同形)。
5
33
  * 收集与物化全程 Abs,不再经 TypeValue 桥。
@@ -19,4 +47,4 @@ declare function harvestDts(files: string[], opts?: {
19
47
  }): HarvestedEnv;
20
48
  declare function emitEnvModule(env: HarvestedEnv, pkgName: string): string;
21
49
 
22
- export { type HarvestedEnv, emitEnvModule, harvestDts };
50
+ export { type DtsContractDraft, type DtsExportDraft, type HarvestedEnv, dtsPathToContractDraft, dtsToContractDraft, emitEnvModule, formatDtsContractDraft, harvestDts };
package/dist/index.js CHANGED
@@ -1,15 +1,364 @@
1
1
  // src/index.ts
2
- import { readFileSync, statSync } from "fs";
3
- import ts from "typescript";
2
+ import { readFileSync as readFileSync2, statSync as statSync2 } from "fs";
3
+ import ts2 from "typescript";
4
4
  import {
5
5
  abs as makeAbs,
6
6
  lit as termLit,
7
+ v as termVar,
7
8
  numLit,
8
9
  strLit,
9
10
  boolLit,
10
11
  objOf,
11
- joinAbs
12
+ joinAbs,
13
+ relationFn,
14
+ getFnImpl,
15
+ substAbs
12
16
  } from "@nudojs/core";
17
+
18
+ // src/dts-contract-draft.ts
19
+ import { readFileSync, existsSync, statSync, readdirSync } from "fs";
20
+ import { basename, dirname, join } from "path";
21
+ import ts from "typescript";
22
+ var BUILDERS = [
23
+ "fn",
24
+ "number",
25
+ "string",
26
+ "boolean",
27
+ "any",
28
+ "shape",
29
+ "array",
30
+ "lit",
31
+ "union"
32
+ ];
33
+ function keywordDsl(node) {
34
+ switch (node.kind) {
35
+ case ts.SyntaxKind.NumberKeyword:
36
+ return "number()";
37
+ case ts.SyntaxKind.StringKeyword:
38
+ return "string()";
39
+ case ts.SyntaxKind.BooleanKeyword:
40
+ return "boolean()";
41
+ case ts.SyntaxKind.AnyKeyword:
42
+ case ts.SyntaxKind.UnknownKeyword:
43
+ case ts.SyntaxKind.VoidKeyword:
44
+ case ts.SyntaxKind.UndefinedKeyword:
45
+ case ts.SyntaxKind.NullKeyword:
46
+ case ts.SyntaxKind.ObjectKeyword:
47
+ case ts.SyntaxKind.NeverKeyword:
48
+ return "any()";
49
+ case ts.SyntaxKind.BigIntKeyword:
50
+ case ts.SyntaxKind.SymbolKeyword:
51
+ return void 0;
52
+ default:
53
+ return void 0;
54
+ }
55
+ }
56
+ function litDsl(node) {
57
+ const lit = node.literal;
58
+ if (ts.isStringLiteral(lit)) return `lit(${JSON.stringify(lit.text)})`;
59
+ if (ts.isNumericLiteral(lit)) return `lit(${lit.text})`;
60
+ if (lit.kind === ts.SyntaxKind.TrueKeyword) return "lit(true)";
61
+ if (lit.kind === ts.SyntaxKind.FalseKeyword) return "lit(false)";
62
+ return void 0;
63
+ }
64
+ function typeRefName(node) {
65
+ const n = node.typeName;
66
+ if (ts.isIdentifier(n)) return n.text;
67
+ if (ts.isQualifiedName(n)) {
68
+ const parts = [];
69
+ let cur = n;
70
+ while (ts.isQualifiedName(cur)) {
71
+ parts.unshift(cur.right.text);
72
+ cur = cur.left;
73
+ }
74
+ if (ts.isIdentifier(cur)) parts.unshift(cur.text);
75
+ return parts.join(".");
76
+ }
77
+ return "?";
78
+ }
79
+ function typeToDsl(node, depth = 0) {
80
+ if (!node || depth > 6) return "any()";
81
+ const kw = keywordDsl(node);
82
+ if (kw !== void 0) return kw;
83
+ if (ts.isLiteralTypeNode(node)) {
84
+ return litDsl(node) ?? "any()";
85
+ }
86
+ if (ts.isArrayTypeNode(node)) {
87
+ const el = typeToDsl(node.elementType, depth + 1) ?? "any()";
88
+ return `array(${el})`;
89
+ }
90
+ if (ts.isUnionTypeNode(node)) {
91
+ const parts = node.types.map((t) => typeToDsl(t, depth + 1) ?? "any()");
92
+ if (parts.length === 0) return "any()";
93
+ if (parts.length === 1) return parts[0];
94
+ return `union(${parts.join(", ")})`;
95
+ }
96
+ if (ts.isParenthesizedTypeNode(node)) {
97
+ return typeToDsl(node.type, depth + 1);
98
+ }
99
+ if (ts.isTypeLiteralNode(node)) {
100
+ const fields = [];
101
+ for (const m of node.members) {
102
+ if (!ts.isPropertySignature(m)) continue;
103
+ const key = m.name && (ts.isIdentifier(m.name) || ts.isStringLiteral(m.name)) ? ts.isIdentifier(m.name) ? m.name.text : m.name.text : void 0;
104
+ if (!key) continue;
105
+ const optional = m.questionToken ? true : false;
106
+ const val = typeToDsl(m.type, depth + 1) ?? "any()";
107
+ fields.push(optional ? `${key}: ${val}.optional()` : `${key}: ${val}`);
108
+ }
109
+ return fields.length === 0 ? "shape({})" : `shape({ ${fields.join(", ")} })`;
110
+ }
111
+ if (ts.isFunctionTypeNode(node) || ts.isConstructorTypeNode(node)) {
112
+ return fnSignatureDsl(node.parameters, node.type, depth);
113
+ }
114
+ if (ts.isTypeReferenceNode(node)) {
115
+ const name = typeRefName(node);
116
+ const args = node.typeArguments ?? [];
117
+ if (name === "Array" || name === "ReadonlyArray" || name === "readonly") {
118
+ const el = args[0] ? typeToDsl(args[0], depth + 1) : "any()";
119
+ return `array(${el ?? "any()"})`;
120
+ }
121
+ if (name === "Promise") {
122
+ return args[0] ? typeToDsl(args[0], depth + 1) : "any()";
123
+ }
124
+ if (name === "Record") {
125
+ const val = args[1] ? typeToDsl(args[1], depth + 1) : "any()";
126
+ return `shape({}) /* open Record \u2192 ${val ?? "any()"} */`;
127
+ }
128
+ if (name === "Partial" && args[0]) {
129
+ const inner = typeToDsl(args[0], depth + 1);
130
+ return inner ?? "any()";
131
+ }
132
+ return void 0;
133
+ }
134
+ if (ts.isExpressionWithTypeArguments(node)) {
135
+ return void 0;
136
+ }
137
+ return void 0;
138
+ }
139
+ function fnSignatureDsl(params, ret, depth) {
140
+ const fields = [];
141
+ const notes = [];
142
+ params.forEach((p, i) => {
143
+ let name;
144
+ if (p.name && ts.isIdentifier(p.name)) name = p.name.text;
145
+ else name = `p${i}`;
146
+ if (p.dotDotDotToken) {
147
+ notes.push(`${name}: rest \u2014 array()`);
148
+ fields.push(`${name}: array(any())`);
149
+ return;
150
+ }
151
+ const optional = Boolean(p.questionToken) || p.initializer;
152
+ let dsl = typeToDsl(p.type, depth + 1);
153
+ if (dsl === void 0) {
154
+ notes.push(`${name}: unmodeled type \u2192 any()`);
155
+ dsl = "any()";
156
+ }
157
+ fields.push(optional ? `${name}: ${dsl}.optional()` : `${name}: ${dsl}`);
158
+ });
159
+ let retDsl = typeToDsl(ret, depth + 1);
160
+ if (retDsl === void 0) {
161
+ notes.push(`returns: unmodeled \u2192 any()`);
162
+ retDsl = "any()";
163
+ }
164
+ const obj = fields.length === 0 ? "{}" : `{ ${fields.join(", ")} }`;
165
+ return `fn(${obj}, ${retDsl})`;
166
+ }
167
+ function pickName(decl) {
168
+ return decl.name && ts.isIdentifier(decl.name) ? decl.name.text : void 0;
169
+ }
170
+ function collectExports(sourceFile) {
171
+ const out = [];
172
+ const push = (e) => {
173
+ out.push(e);
174
+ };
175
+ for (const stmt of sourceFile.statements) {
176
+ if ((ts.isFunctionDeclaration(stmt) || ts.isMethodSignature(stmt)) && stmt.modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword)) {
177
+ const name = pickName(stmt);
178
+ if (!name) continue;
179
+ const fn = stmt;
180
+ const dsl = fnSignatureDsl(fn.parameters, fn.type, 0);
181
+ push({ name, dsl, kind: "function" });
182
+ continue;
183
+ }
184
+ if (ts.isFunctionDeclaration(stmt) && pickName(stmt)) {
185
+ const isExport = stmt.modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword) ?? false;
186
+ if (!isExport) continue;
187
+ const name = pickName(stmt);
188
+ push({ name, dsl: fnSignatureDsl(stmt.parameters, stmt.type, 0), kind: "function" });
189
+ continue;
190
+ }
191
+ if (ts.isVariableStatement(stmt)) {
192
+ const isExport = stmt.modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword) ?? false;
193
+ if (!isExport) continue;
194
+ for (const d of stmt.declarationList.declarations) {
195
+ const name = pickName(d);
196
+ if (!name) continue;
197
+ const dsl = typeToDsl(d.type, 0);
198
+ if (dsl === void 0) {
199
+ push({ name, note: "unmodeled type", kind: "value" });
200
+ } else if (dsl.startsWith("fn(")) {
201
+ push({ name, dsl, kind: "function" });
202
+ } else {
203
+ push({ name, dsl, kind: "value" });
204
+ }
205
+ }
206
+ continue;
207
+ }
208
+ if (ts.isClassDeclaration(stmt)) {
209
+ const isExport = stmt.modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword) ?? false;
210
+ if (!isExport) continue;
211
+ const name = pickName(stmt);
212
+ if (!name) {
213
+ push({ name: "default", note: "anonymous class", kind: "class" });
214
+ continue;
215
+ }
216
+ let anyMethod = false;
217
+ for (const m of stmt.members) {
218
+ if (ts.isMethodDeclaration(m) || ts.isMethodSignature(m)) {
219
+ const mname = pickName(m);
220
+ if (!mname) continue;
221
+ anyMethod = true;
222
+ push({
223
+ name: `${name}.${mname}`,
224
+ dsl: fnSignatureDsl(m.parameters, m.type, 0),
225
+ kind: "function"
226
+ });
227
+ }
228
+ }
229
+ if (!anyMethod) {
230
+ push({ name, note: "class without public methods projected", kind: "class" });
231
+ }
232
+ continue;
233
+ }
234
+ if (ts.isExportAssignment(stmt)) {
235
+ push({ name: "default", note: "export assignment \u2014 bind via sidecar default", kind: "other" });
236
+ continue;
237
+ }
238
+ if (ts.isInterfaceDeclaration(stmt) || ts.isTypeAliasDeclaration(stmt)) {
239
+ continue;
240
+ }
241
+ }
242
+ return out;
243
+ }
244
+ function draftExportName(name) {
245
+ return name.includes(".") ? name.replace(/\./g, "_") : name;
246
+ }
247
+ function formatDtsContractDraft(label, files, exports) {
248
+ const draftable = exports.filter((e) => e.dsl !== void 0);
249
+ const used = /* @__PURE__ */ new Set(["fn"]);
250
+ for (const e of draftable) {
251
+ for (const b of BUILDERS) {
252
+ if (new RegExp(`\\b${b}\\b`).test(e.dsl)) used.add(b);
253
+ }
254
+ }
255
+ if (draftable.some((e) => /\bany\s*\(/.test(e.dsl))) used.add("any");
256
+ const importList = BUILDERS.filter((b) => used.has(b)).join(", ");
257
+ const lines = [
258
+ "// @nudo:draft",
259
+ `// Generated by \`nudo contract --from-dts\` from ${label}`,
260
+ `// Sources: ${files.map((f) => basename(f)).join(", ")}`,
261
+ "//",
262
+ "// Reverse-engineered from TypeScript declarations \u2014 NOT a sidecar contract.",
263
+ "// This file is never loaded for check. Review each export, then copy it",
264
+ "// into <target>.nudo.js to accept (that is when obligations go live).",
265
+ "//",
266
+ "// After accept: strengthen with Pred builders (number().gt(0) \u2026) \u2014",
267
+ "// dts cannot express algebraic implications (x>0 \u21D2 x+1>1).",
268
+ "",
269
+ `import { ${importList} } from "@nudojs/core";`,
270
+ ""
271
+ ];
272
+ if (draftable.length === 0) {
273
+ lines.push("// (no projectable exports)");
274
+ lines.push("");
275
+ }
276
+ for (const e of exports) {
277
+ if (e.dsl === void 0) {
278
+ lines.push(`// ${e.name} \u2014 skipped (${e.note ?? "unmodeled"})`);
279
+ continue;
280
+ }
281
+ if (e.note) lines.push(`// ${e.name} \u2014 ${e.note}`);
282
+ else lines.push(`// ${e.name} \u2014 from TypeScript`);
283
+ lines.push(`export const ${draftExportName(e.name)} = ${e.dsl};`);
284
+ if (e.name.includes(".")) {
285
+ lines.push(`// sidecar key may also be written as \`${e.name}\``);
286
+ }
287
+ lines.push("");
288
+ }
289
+ return lines.join("\n");
290
+ }
291
+ function readDtsFiles(paths, maxFiles = 16) {
292
+ const out = [];
293
+ const walk = (dir, depth) => {
294
+ if (depth > 6 || out.length >= maxFiles * 3) return;
295
+ let entries;
296
+ try {
297
+ entries = readdirSync(dir);
298
+ } catch {
299
+ return;
300
+ }
301
+ for (const name of entries) {
302
+ if (name === "node_modules" || name === "test" || name === "__tests__") continue;
303
+ const p = join(dir, name);
304
+ let st;
305
+ try {
306
+ st = statSync(p);
307
+ } catch {
308
+ continue;
309
+ }
310
+ if (st.isDirectory()) walk(p, depth + 1);
311
+ else if (name.endsWith(".d.ts") && !name.endsWith(".d.ts.map")) out.push(p);
312
+ }
313
+ };
314
+ for (const p of paths) {
315
+ if (!existsSync(p)) continue;
316
+ const st = statSync(p);
317
+ if (st.isDirectory()) walk(p, 0);
318
+ else if (/\.d\.ts$/.test(p) || /\.(m)?ts$/.test(p)) {
319
+ if (!p.endsWith(".d.ts.map")) out.push(p);
320
+ }
321
+ }
322
+ return out.slice(0, maxFiles);
323
+ }
324
+ function dtsToContractDraft(dtsFiles, label) {
325
+ const files = dtsFiles.filter((f) => existsSync(f));
326
+ const exports = [];
327
+ let skippedFiles = 0;
328
+ for (const file of files) {
329
+ let content;
330
+ try {
331
+ content = readFileSync(file, "utf8");
332
+ } catch {
333
+ skippedFiles++;
334
+ continue;
335
+ }
336
+ const sf = ts.createSourceFile(file, content, ts.ScriptTarget.Latest, true);
337
+ for (const e of collectExports(sf)) {
338
+ if (exports.some((x) => x.name === e.name && x.dsl)) continue;
339
+ exports.push(e);
340
+ }
341
+ }
342
+ const name = label ?? (files[0] ? basename(files[0]).replace(/\.d\.ts$/, "") : "dts");
343
+ const draftSource = formatDtsContractDraft(name, files, exports);
344
+ return {
345
+ label: name,
346
+ files,
347
+ exports,
348
+ draftSource,
349
+ stats: {
350
+ files: files.length - skippedFiles,
351
+ exports: exports.filter((e) => e.dsl !== void 0).length,
352
+ skipped: exports.filter((e) => e.dsl === void 0).length + skippedFiles
353
+ }
354
+ };
355
+ }
356
+ function dtsPathToContractDraft(path, label) {
357
+ const files = readDtsFiles([path]);
358
+ return dtsToContractDraft(files, label ?? basename(path).replace(/\.d\.ts$/, ""));
359
+ }
360
+
361
+ // src/index.ts
13
362
  var MAX_ALIAS_DEPTH = 8;
14
363
  function scopeKey(scope) {
15
364
  return scope.moduleName ?? "\0global";
@@ -46,6 +395,25 @@ function absArr(element) {
46
395
  function absPromise(inner) {
47
396
  return absExact({ k: "eff", eff: "promise", inner });
48
397
  }
398
+ function absMapType(name, key, value) {
399
+ return absExact({
400
+ k: "brand",
401
+ name,
402
+ shape: absObj({
403
+ __key: key,
404
+ __value: value
405
+ })
406
+ });
407
+ }
408
+ function absSetType(name, elem) {
409
+ return absExact({
410
+ k: "brand",
411
+ name,
412
+ shape: absObj({
413
+ __elem: elem
414
+ })
415
+ });
416
+ }
49
417
  function absObj(properties) {
50
418
  const slots = {};
51
419
  for (const [k, v] of Object.entries(properties)) {
@@ -56,14 +424,16 @@ function absObj(properties) {
56
424
  function absBrand(name, properties) {
57
425
  return absExact({ k: "brand", name, shape: absObj(properties) });
58
426
  }
59
- function absFnSig(paramTypes, returnType) {
60
- return absExact({
61
- k: "fn",
427
+ function absFnSig(paramTypes, returnType, extra) {
428
+ return relationFn(paramTypes, returnType, {
62
429
  params: paramTypes.map((_, i) => `_arg${i}`),
63
- paramTypes,
64
- returnType
430
+ conf: "exact",
431
+ ...extra ?? {}
65
432
  });
66
433
  }
434
+ function typeVarAbs(name) {
435
+ return makeAbs({ k: "any" }, termVar(name), void 0, "path");
436
+ }
67
437
  function absUnion(members) {
68
438
  if (members.length === 0) return absNever();
69
439
  return members.reduce((acc, m) => joinAbs(acc, m));
@@ -71,6 +441,19 @@ function absUnion(members) {
71
441
  function isPlainUnknown(a) {
72
442
  return a.shape.k === "unknown" && !(a.term?.op === "lit");
73
443
  }
444
+ function hasTypeVar(a, depth = 0) {
445
+ if (depth > 6) return false;
446
+ if (a.term?.op === "var") return true;
447
+ const s = a.shape;
448
+ if (s.k === "arr") return hasTypeVar(s.element, depth + 1);
449
+ if (s.k === "tuple") return s.elements.some((e) => hasTypeVar(e, depth + 1));
450
+ if (s.k === "sum") return s.members.some((m) => hasTypeVar(m, depth + 1));
451
+ if (s.k === "obj") return Object.values(s.slots).some((sl) => hasTypeVar(sl.value, depth + 1));
452
+ if (s.k === "fn") {
453
+ return (s.paramTypes ?? []).some((p) => hasTypeVar(p, depth + 1)) || (s.returnType ? hasTypeVar(s.returnType, depth + 1) : false);
454
+ }
455
+ return false;
456
+ }
74
457
  function harvestDts(files, opts) {
75
458
  const maxBytes = opts?.maxFileBytes ?? 15e5;
76
459
  const deadline = opts?.maxMs !== void 0 ? Date.now() + opts.maxMs : void 0;
@@ -84,6 +467,7 @@ function harvestDts(files, opts) {
84
467
  classes: /* @__PURE__ */ new Map(),
85
468
  instanceCache: /* @__PURE__ */ new Map(),
86
469
  expanding: /* @__PURE__ */ new Set(),
470
+ typeParams: /* @__PURE__ */ new Set(),
87
471
  moduleAliases: [],
88
472
  importEquals: /* @__PURE__ */ new Map(),
89
473
  declaredModules: /* @__PURE__ */ new Set(),
@@ -99,17 +483,17 @@ function harvestDts(files, opts) {
99
483
  }
100
484
  let content;
101
485
  try {
102
- const st = statSync(file);
486
+ const st = statSync2(file);
103
487
  if (st.size > maxBytes) {
104
488
  ctx.skipped++;
105
489
  continue;
106
490
  }
107
- content = readFileSync(file, "utf8");
491
+ content = readFileSync2(file, "utf8");
108
492
  } catch {
109
493
  ctx.skipped++;
110
494
  continue;
111
495
  }
112
- const sourceFile = ts.createSourceFile(file, content, ts.ScriptTarget.Latest, true);
496
+ const sourceFile = ts2.createSourceFile(file, content, ts2.ScriptTarget.Latest, true);
113
497
  collectStatements(ctx, sourceFile.statements, { moduleName: void 0, ns: [] });
114
498
  parsed++;
115
499
  }
@@ -141,7 +525,7 @@ function emitEnvModule(env, pkgName) {
141
525
  constLines.push("");
142
526
  }
143
527
  const lines = [];
144
- lines.push("// Auto-generated by nudo harvest \u2014 DO NOT EDIT");
528
+ lines.push("// Auto-generated by @nudojs/harvester \u2014 DO NOT EDIT");
145
529
  lines.push(`// Source package: ${pkgName}`);
146
530
  lines.push(
147
531
  'import { num, str, bool, never as absNever, unknown as absUnknown, numLit, strLit, boolLit, objOf, relationFn, abs as makeAbs } from "@nudojs/core";'
@@ -185,13 +569,13 @@ function collectStatements(ctx, statements, scope) {
185
569
  }
186
570
  }
187
571
  function collectStatement(ctx, stmt, scope) {
188
- if (ts.isModuleDeclaration(stmt)) {
572
+ if (ts2.isModuleDeclaration(stmt)) {
189
573
  const body = stmt.body;
190
- if (!body || !ts.isModuleBlock(body)) {
574
+ if (!body || !ts2.isModuleBlock(body)) {
191
575
  ctx.skipped++;
192
576
  return;
193
577
  }
194
- if (ts.isStringLiteral(stmt.name)) {
578
+ if (ts2.isStringLiteral(stmt.name)) {
195
579
  ctx.declaredModules.add(stmt.name.text);
196
580
  collectStatements(ctx, body.statements, { moduleName: stmt.name.text, ns: [] });
197
581
  } else if (stmt.name.text === "global") {
@@ -201,7 +585,7 @@ function collectStatement(ctx, stmt, scope) {
201
585
  }
202
586
  return;
203
587
  }
204
- if (ts.isFunctionDeclaration(stmt)) {
588
+ if (ts2.isFunctionDeclaration(stmt)) {
205
589
  if (!stmt.name) {
206
590
  ctx.skipped++;
207
591
  return;
@@ -216,15 +600,23 @@ function collectStatement(ctx, stmt, scope) {
216
600
  ctx.symbols++;
217
601
  return;
218
602
  }
219
- if (ts.isVariableStatement(stmt)) {
603
+ if (ts2.isVariableStatement(stmt)) {
220
604
  for (const decl of stmt.declarationList.declarations) {
221
- if (!ts.isIdentifier(decl.name)) {
605
+ if (!ts2.isIdentifier(decl.name)) {
222
606
  ctx.skipped++;
223
607
  continue;
224
608
  }
225
- const iface = decl.type ? interfaceDeclOf(ctx, decl.type) : void 0;
226
- if (iface) {
227
- expandInterfaceAsModuleMembers(ctx, scope, iface);
609
+ const ifaceName = decl.type && ts2.isTypeReferenceNode(decl.type) ? typeNameString(decl.type.typeName) : void 0;
610
+ if (ifaceName && (ctx.interfaces.has(ifaceName) || ctx.qualifiedInterfaces.has(ifaceName) || true)) {
611
+ ctx.pending.push({
612
+ t: "iface-expand",
613
+ scope,
614
+ localName: decl.name.text,
615
+ typeRefName: ifaceName
616
+ });
617
+ ctx.symbols++;
618
+ ctx.pending.push({ t: "var", scope, name: decl.name.text, typeNode: decl.type });
619
+ ctx.symbols++;
228
620
  continue;
229
621
  }
230
622
  ctx.pending.push({ t: "var", scope, name: decl.name.text, typeNode: decl.type });
@@ -232,15 +624,15 @@ function collectStatement(ctx, stmt, scope) {
232
624
  }
233
625
  return;
234
626
  }
235
- if (ts.isInterfaceDeclaration(stmt)) {
627
+ if (ts2.isInterfaceDeclaration(stmt)) {
236
628
  registerTypeDecl(ctx, scope, stmt.name.text, stmt);
237
629
  return;
238
630
  }
239
- if (ts.isTypeAliasDeclaration(stmt)) {
631
+ if (ts2.isTypeAliasDeclaration(stmt)) {
240
632
  registerAlias(ctx, scope, stmt);
241
633
  return;
242
634
  }
243
- if (ts.isClassDeclaration(stmt)) {
635
+ if (ts2.isClassDeclaration(stmt)) {
244
636
  if (!stmt.name) {
245
637
  ctx.skipped++;
246
638
  return;
@@ -251,18 +643,18 @@ function collectStatement(ctx, stmt, scope) {
251
643
  ctx.symbols++;
252
644
  return;
253
645
  }
254
- if (ts.isEnumDeclaration(stmt)) {
646
+ if (ts2.isEnumDeclaration(stmt)) {
255
647
  if (stmt.name) {
256
648
  ctx.pending.push({ t: "enum", scope, name: stmt.name.text });
257
649
  ctx.symbols++;
258
650
  }
259
651
  return;
260
652
  }
261
- if (ts.isImportDeclaration(stmt)) {
653
+ if (ts2.isImportDeclaration(stmt)) {
262
654
  return;
263
655
  }
264
- if (ts.isImportEqualsDeclaration(stmt)) {
265
- if (ts.isIdentifier(stmt.name) && stmt.moduleReference && ts.isExternalModuleReference(stmt.moduleReference) && ts.isStringLiteral(stmt.moduleReference.expression)) {
656
+ if (ts2.isImportEqualsDeclaration(stmt)) {
657
+ if (ts2.isIdentifier(stmt.name) && stmt.moduleReference && ts2.isExternalModuleReference(stmt.moduleReference) && ts2.isStringLiteral(stmt.moduleReference.expression)) {
266
658
  const key = scopeKey(scope);
267
659
  let bindings = ctx.importEquals.get(key);
268
660
  if (!bindings) {
@@ -273,10 +665,10 @@ function collectStatement(ctx, stmt, scope) {
273
665
  }
274
666
  return;
275
667
  }
276
- if (ts.isExportDeclaration(stmt)) {
668
+ if (ts2.isExportDeclaration(stmt)) {
277
669
  const specifier = stmt.moduleSpecifier;
278
- if (specifier && ts.isStringLiteral(specifier)) {
279
- if (stmt.exportClause && ts.isNamespaceExport(stmt.exportClause)) {
670
+ if (specifier && ts2.isStringLiteral(specifier)) {
671
+ if (stmt.exportClause && ts2.isNamespaceExport(stmt.exportClause)) {
280
672
  ctx.skipped++;
281
673
  return;
282
674
  }
@@ -289,7 +681,7 @@ function collectStatement(ctx, stmt, scope) {
289
681
  ctx.skipped++;
290
682
  return;
291
683
  }
292
- if (stmt.exportClause && ts.isNamedExports(stmt.exportClause)) {
684
+ if (stmt.exportClause && ts2.isNamedExports(stmt.exportClause)) {
293
685
  for (const el of stmt.exportClause.elements) {
294
686
  const localName = (el.propertyName ?? el.name).text;
295
687
  const exportedName = el.name.text;
@@ -306,8 +698,8 @@ function collectStatement(ctx, stmt, scope) {
306
698
  ctx.skipped++;
307
699
  return;
308
700
  }
309
- if (ts.isExportAssignment(stmt)) {
310
- if (stmt.expression && ts.isIdentifier(stmt.expression) && scope.moduleName !== void 0) {
701
+ if (ts2.isExportAssignment(stmt)) {
702
+ if (stmt.expression && ts2.isIdentifier(stmt.expression) && scope.moduleName !== void 0) {
311
703
  const target = ctx.importEquals.get(scopeKey(scope))?.get(stmt.expression.text);
312
704
  if (target !== void 0) {
313
705
  ctx.moduleAliases.push([scope.moduleName, target]);
@@ -320,66 +712,122 @@ function collectStatement(ctx, stmt, scope) {
320
712
  ctx.skipped++;
321
713
  }
322
714
  function registerTypeDecl(ctx, scope, name, decl) {
323
- if (!ctx.interfaces.has(name)) ctx.interfaces.set(name, decl);
324
- if (ts.isClassDeclaration(decl)) return;
715
+ const push = (map, key) => {
716
+ const list = map.get(key);
717
+ if (list) {
718
+ if (!list.includes(decl)) list.push(decl);
719
+ } else {
720
+ map.set(key, [decl]);
721
+ }
722
+ };
723
+ push(ctx.interfaces, name);
724
+ if (ts2.isClassDeclaration(decl)) return;
325
725
  const qualified = [...scope.ns, name].join(".");
326
- if (qualified !== name && !ctx.qualifiedInterfaces.has(qualified)) {
327
- ctx.qualifiedInterfaces.set(qualified, decl);
328
- }
726
+ if (qualified !== name) push(ctx.qualifiedInterfaces, qualified);
329
727
  }
330
- function interfaceDeclOf(ctx, typeNode) {
331
- if (!ts.isTypeReferenceNode(typeNode)) return void 0;
332
- const name = typeNameString(typeNode.typeName);
333
- const d = ctx.interfaces.get(name) ?? ctx.qualifiedInterfaces.get(name);
334
- return d && ts.isInterfaceDeclaration(d) ? d : void 0;
728
+ function interfaceDeclsOf(ctx, typeNode) {
729
+ const name = typeof typeNode === "string" ? typeNode : ts2.isTypeReferenceNode(typeNode) ? typeNameString(typeNode.typeName) : void 0;
730
+ if (!name) return [];
731
+ const short = name.includes(".") ? name.split(".").pop() : name;
732
+ const keys = name === short ? [name] : [name, short];
733
+ const seen = /* @__PURE__ */ new Set();
734
+ const out = [];
735
+ for (const key of keys) {
736
+ for (const d of [
737
+ ...ctx.interfaces.get(key) ?? [],
738
+ ...ctx.qualifiedInterfaces.get(key) ?? []
739
+ ]) {
740
+ if (ts2.isInterfaceDeclaration(d) && !seen.has(d)) {
741
+ seen.add(d);
742
+ out.push(d);
743
+ }
744
+ }
745
+ }
746
+ return out;
335
747
  }
336
- function expandInterfaceAsModuleMembers(ctx, scope, iface) {
337
- for (const member of iface.members) {
338
- if (ts.isMethodSignature(member)) {
339
- const name = memberName(member);
340
- if (name === void 0) continue;
341
- const existing = ctx.pending.find(
342
- (p) => p.t === "fn" && p.name === name && scopeKey(p.scope) === scopeKey(scope)
343
- );
344
- if (existing && existing.t === "fn") {
345
- existing.decls.push(member);
748
+ function expandInterfaceAsModuleMembers(ctx, scope, ifaces) {
749
+ for (const iface of ifaces) {
750
+ for (const member of iface.members) {
751
+ if (ts2.isMethodSignature(member)) {
752
+ const name = memberName(member);
753
+ if (name === void 0) continue;
754
+ const existing = ctx.pending.find(
755
+ (p) => p.t === "fn" && p.name === name && scopeKey(p.scope) === scopeKey(scope)
756
+ );
757
+ if (existing && existing.t === "fn") {
758
+ existing.decls.push(member);
759
+ continue;
760
+ }
761
+ ctx.pending.push({ t: "fn", scope, name, decls: [member] });
762
+ ctx.symbols++;
346
763
  continue;
347
764
  }
348
- ctx.pending.push({ t: "fn", scope, name, decls: [member] });
349
- ctx.symbols++;
350
- continue;
351
- }
352
- if (ts.isPropertySignature(member)) {
353
- const name = memberName(member);
354
- if (name === void 0) continue;
355
- ctx.pending.push({ t: "var", scope, name, typeNode: member.type });
356
- ctx.symbols++;
765
+ if (ts2.isPropertySignature(member)) {
766
+ const name = memberName(member);
767
+ if (name === void 0) continue;
768
+ if (ctx.pending.some((p) => p.t === "var" && p.name === name && scopeKey(p.scope) === scopeKey(scope))) {
769
+ continue;
770
+ }
771
+ ctx.pending.push({ t: "var", scope, name, typeNode: member.type });
772
+ ctx.symbols++;
773
+ }
357
774
  }
358
775
  }
359
776
  }
360
777
  function registerAlias(ctx, scope, stmt) {
361
778
  const name = stmt.name.text;
362
- if (!ctx.aliases.has(name)) ctx.aliases.set(name, stmt);
779
+ const push = (map, key) => {
780
+ const list = map.get(key);
781
+ if (list) {
782
+ if (!list.includes(stmt)) list.push(stmt);
783
+ } else {
784
+ map.set(key, [stmt]);
785
+ }
786
+ };
787
+ push(ctx.aliases, name);
363
788
  const qualified = [...scope.ns, name].join(".");
364
- if (qualified !== name && !ctx.qualifiedAliases.has(qualified)) {
365
- ctx.qualifiedAliases.set(qualified, stmt);
366
- }
789
+ if (qualified !== name) push(ctx.qualifiedAliases, qualified);
367
790
  }
368
791
  function materialize(ctx) {
792
+ const expanded = [];
793
+ for (const p of ctx.pending) {
794
+ if (p.t !== "iface-expand") {
795
+ expanded.push(p);
796
+ continue;
797
+ }
798
+ const ifaces = interfaceDeclsOf(ctx, p.typeRefName);
799
+ if (ifaces.length === 0) {
800
+ continue;
801
+ }
802
+ const before = ctx.pending.length;
803
+ expandInterfaceAsModuleMembers(ctx, p.scope, ifaces);
804
+ for (let i = before; i < ctx.pending.length; i++) {
805
+ const n = ctx.pending[i];
806
+ if (n.t === "fn" || n.t === "var") expanded.push(n);
807
+ }
808
+ }
809
+ ctx.pending = expanded;
369
810
  for (const p of ctx.pending) {
811
+ if (p.t === "iface-expand") continue;
370
812
  const rec = recordFor(ctx, p.scope);
371
813
  if (p.t === "fn") {
372
814
  if (rec[p.name] !== void 0) continue;
373
- const value = signatureToFnSig(ctx, p.decls[0]);
374
- const sig = value.shape.k === "fn" ? value.shape : void 0;
375
- if (sig) {
815
+ const sigs = p.decls.map((d) => signatureToFnSig(ctx, d));
816
+ const value = sigs[0];
817
+ const shape0 = value.shape.k === "fn" ? value.shape : void 0;
818
+ if (shape0 && sigs.length > 1) {
376
819
  const returns = [];
377
- for (const decl of p.decls) {
378
- if (!decl.type) continue;
379
- const ret = mapType(ctx, decl.type, 0);
380
- if (!isPlainUnknown(ret)) returns.push(ret);
820
+ for (const s of sigs) {
821
+ if (s.shape.k !== "fn") continue;
822
+ const ret = s.shape.returnType;
823
+ if (ret && !isPlainUnknown(ret)) returns.push(ret);
381
824
  }
382
- sig.returnType = returns.length > 0 ? absUnion(returns) : absUnknown();
825
+ const withVars = returns.filter((r) => hasTypeVar(r));
826
+ const pool = withVars.length > 0 ? withVars : returns;
827
+ const mergedRet = pool.length > 0 ? absUnion(pool) : absUnknown();
828
+ shape0.returnType = mergedRet;
829
+ const rel = getFnImpl(value)?.relation;
830
+ if (rel) rel.returnType = mergedRet;
383
831
  }
384
832
  rec[p.name] = value;
385
833
  continue;
@@ -398,7 +846,7 @@ function materialize(ctx) {
398
846
  case "value": {
399
847
  const cls = ctx.classes.get(p.lookup ?? p.name);
400
848
  if (cls) {
401
- const ctor = cls.members.find(ts.isConstructorDeclaration);
849
+ const ctor = cls.members.find(ts2.isConstructorDeclaration);
402
850
  const params = [];
403
851
  if (ctor) {
404
852
  for (const param of ctor.parameters) {
@@ -425,78 +873,111 @@ function materialize(ctx) {
425
873
  }
426
874
  }
427
875
  function typeNameString(name) {
428
- if (ts.isIdentifier(name)) return name.text;
876
+ if (ts2.isIdentifier(name)) return name.text;
429
877
  return `${typeNameString(name.left)}.${name.right.text}`;
430
878
  }
431
879
  function memberName(node) {
432
880
  const n = node.name;
433
881
  if (!n) return void 0;
434
- if (ts.isIdentifier(n) || ts.isStringLiteral(n) || ts.isNumericLiteral(n)) return n.text;
882
+ if (ts2.isIdentifier(n) || ts2.isStringLiteral(n) || ts2.isNumericLiteral(n)) return n.text;
435
883
  return void 0;
436
884
  }
437
- function instanceFor(ctx, key) {
438
- const cached = ctx.instanceCache.get(key);
885
+ function instanceFor(ctx, key, typeArgs) {
886
+ const cacheKey = typeArgs && typeArgs.length > 0 ? `${key}::${typeArgs.length}` : key;
887
+ const cached = ctx.instanceCache.get(cacheKey);
439
888
  if (cached !== void 0) return cached;
440
- const decl = ctx.interfaces.get(key) ?? ctx.qualifiedInterfaces.get(key);
889
+ const decls = interfaceDeclsOf(ctx, key);
890
+ const decl = decls[0];
441
891
  if (!decl) return absUnknown();
442
892
  if (ctx.expanding.has(key)) {
443
893
  return absBrand(decl.name?.text ?? key, {});
444
894
  }
445
895
  ctx.expanding.add(key);
896
+ const savedTp = ctx.typeParams;
897
+ ctx.typeParams = new Set(savedTp);
898
+ for (const d of decls) {
899
+ for (const tp of d.typeParameters ?? []) ctx.typeParams.add(tp.name.text);
900
+ }
446
901
  const properties = {};
447
902
  try {
448
- const members = decl.members;
449
- for (const member of members) {
450
- const modifiers = member.modifiers;
451
- if (modifiers?.some((mod) => mod.kind === ts.SyntaxKind.StaticKeyword)) continue;
452
- if (ts.isPropertySignature(member) || ts.isPropertyDeclaration(member)) {
453
- const name = memberName(member);
454
- if (name === void 0) continue;
455
- let type = member.type ? mapType(ctx, member.type, 0) : absUnknown();
456
- if (member.questionToken) type = absUnion([type, absUndefLit()]);
457
- properties[name] = type;
458
- } else if (ts.isMethodSignature(member) || ts.isMethodDeclaration(member)) {
459
- const name = memberName(member);
460
- if (name === void 0 || properties[name] !== void 0) continue;
461
- properties[name] = signatureToFnSig(ctx, member);
462
- } else if (ts.isGetAccessorDeclaration(member) || ts.isSetAccessorDeclaration(member)) {
463
- const name = memberName(member);
464
- if (name === void 0 || properties[name] !== void 0 || !member.type) continue;
465
- properties[name] = mapType(ctx, member.type, 0);
903
+ for (const d of decls) {
904
+ const members = d.members;
905
+ for (const member of members) {
906
+ const modifiers = member.modifiers;
907
+ if (modifiers?.some((mod) => mod.kind === ts2.SyntaxKind.StaticKeyword)) continue;
908
+ if (ts2.isPropertySignature(member) || ts2.isPropertyDeclaration(member)) {
909
+ const name = memberName(member);
910
+ if (name === void 0) continue;
911
+ let type = member.type ? mapType(ctx, member.type, 0) : absUnknown();
912
+ if (member.questionToken) type = absUnion([type, absUndefLit()]);
913
+ properties[name] = type;
914
+ } else if (ts2.isMethodSignature(member) || ts2.isMethodDeclaration(member)) {
915
+ const name = memberName(member);
916
+ if (name === void 0 || properties[name] !== void 0) continue;
917
+ properties[name] = signatureToFnSig(ctx, member);
918
+ } else if (ts2.isGetAccessorDeclaration(member) || ts2.isSetAccessorDeclaration(member)) {
919
+ const name = memberName(member);
920
+ if (name === void 0 || properties[name] !== void 0 || !member.type) continue;
921
+ properties[name] = mapType(ctx, member.type, 0);
922
+ }
466
923
  }
467
924
  }
468
925
  } finally {
926
+ ctx.typeParams = savedTp;
469
927
  ctx.expanding.delete(key);
470
928
  }
471
- const value = absBrand(decl.name?.text ?? key, properties);
472
- ctx.instanceCache.set(key, value);
929
+ let value = absBrand(decl.name?.text ?? key, properties);
930
+ if (typeArgs && typeArgs.length > 0 && Object.keys(properties).length <= 40) {
931
+ const map = /* @__PURE__ */ new Map();
932
+ const tps = decls[0].typeParameters ?? [];
933
+ tps.forEach((tp, i) => {
934
+ const arg = typeArgs[i];
935
+ if (arg) map.set(tp.name.text, arg);
936
+ });
937
+ if (map.size > 0) value = substAbs(value, map);
938
+ }
939
+ ctx.instanceCache.set(cacheKey, value);
473
940
  return value;
474
941
  }
475
942
  function signatureToFnSig(ctx, sig) {
476
- const paramTypes = sig.parameters.map((param) => {
477
- let type;
478
- if (param.dotDotDotToken) {
479
- const inner = param.type && ts.isArrayTypeNode(param.type) ? param.type.elementType : param.type;
480
- type = inner ? absArr(mapType(ctx, inner, 0)) : absUnknown();
481
- } else {
482
- type = param.type ? mapType(ctx, param.type, 0) : absUnknown();
483
- }
484
- if (param.questionToken && !param.dotDotDotToken) type = absUnion([type, absUndefLit()]);
485
- return type;
486
- });
487
- const returnType = sig.type ? mapType(ctx, sig.type, 0) : absUnknown();
488
- return absFnSig(paramTypes, returnType);
943
+ const savedTp = ctx.typeParams;
944
+ ctx.typeParams = new Set(savedTp);
945
+ for (const tp of sig.typeParameters ?? []) {
946
+ ctx.typeParams.add(tp.name.text);
947
+ }
948
+ try {
949
+ const paramTypes = sig.parameters.map((param) => {
950
+ let type;
951
+ if (param.dotDotDotToken) {
952
+ const inner = param.type && ts2.isArrayTypeNode(param.type) ? param.type.elementType : param.type;
953
+ type = inner ? absArr(mapType(ctx, inner, 0)) : absUnknown();
954
+ } else {
955
+ type = param.type ? mapType(ctx, param.type, 0) : absUnknown();
956
+ }
957
+ if (param.questionToken && !param.dotDotDotToken) type = absUnion([type, absUndefLit()]);
958
+ return type;
959
+ });
960
+ const returnType = sig.type ? mapType(ctx, sig.type, 0) : absUnknown();
961
+ const cond = ctx.pendingCond;
962
+ ctx.pendingCond = void 0;
963
+ return absFnSig(paramTypes, returnType, cond ? {
964
+ inferFrom: { fromVar: cond.fromVar, via: cond.via, inferVar: cond.inferVar },
965
+ condFallback: cond.fallback
966
+ } : void 0);
967
+ } finally {
968
+ ctx.typeParams = savedTp;
969
+ }
489
970
  }
490
971
  function mapTypeMembers(ctx, members, depth) {
491
972
  const properties = {};
492
973
  for (const member of members) {
493
- if (ts.isPropertySignature(member)) {
974
+ if (ts2.isPropertySignature(member)) {
494
975
  const name = memberName(member);
495
976
  if (name === void 0) continue;
496
977
  let type = member.type ? mapType(ctx, member.type, depth) : absUnknown();
497
978
  if (member.questionToken) type = absUnion([type, absUndefLit()]);
498
979
  properties[name] = type;
499
- } else if (ts.isMethodSignature(member)) {
980
+ } else if (ts2.isMethodSignature(member)) {
500
981
  const name = memberName(member);
501
982
  if (name === void 0 || properties[name] !== void 0) continue;
502
983
  properties[name] = signatureToFnSig(ctx, member);
@@ -507,38 +988,184 @@ function mapTypeMembers(ctx, members, depth) {
507
988
  function mapTypeRef(ctx, node, depth) {
508
989
  const name = typeNameString(node.typeName);
509
990
  const args = node.typeArguments ?? [];
991
+ if (ctx.typeParams.has(name) && args.length === 0) {
992
+ return typeVarAbs(name);
993
+ }
510
994
  if ((name === "Array" || name === "ReadonlyArray") && args.length >= 1) {
511
995
  return absArr(mapType(ctx, args[0], depth));
512
996
  }
997
+ if ((name === "List" || name === "ArrayLike" || name === "Collection" || name === "Many" || name === "NumericDictionary" || name === "Dictionary") && args.length >= 1) {
998
+ return absArr(mapType(ctx, args[0], depth));
999
+ }
513
1000
  if (name === "Promise" && args.length >= 1) {
514
1001
  return absPromise(mapType(ctx, args[0], depth));
515
1002
  }
1003
+ if ((name === "Map" || name === "ReadonlyMap" || name === "WeakMap") && args.length >= 2) {
1004
+ return absMapType(name, mapType(ctx, args[0], depth), mapType(ctx, args[1], depth));
1005
+ }
1006
+ if ((name === "Set" || name === "ReadonlySet" || name === "WeakSet") && args.length >= 1) {
1007
+ return absSetType(name, mapType(ctx, args[0], depth));
1008
+ }
516
1009
  if (name === "Record") {
517
1010
  return absObj({});
518
1011
  }
519
1012
  if (ctx.interfaces.has(name) || ctx.qualifiedInterfaces.has(name)) {
520
- return instanceFor(ctx, name);
1013
+ return instanceFor(ctx, name, args.map((a) => mapType(ctx, a, depth)));
521
1014
  }
522
- const alias = ctx.aliases.get(name) ?? ctx.qualifiedAliases.get(name);
1015
+ const aliases = ctx.aliases.get(name) ?? ctx.qualifiedAliases.get(name) ?? [];
1016
+ const alias = aliases[0];
523
1017
  if (alias) return mapType(ctx, alias.type, depth + 1);
524
1018
  return absUnknown();
525
1019
  }
1020
+ function collectInferNames(node, out = /* @__PURE__ */ new Set()) {
1021
+ const visit = (n) => {
1022
+ if (!n) return;
1023
+ if (ts2.isInferTypeNode(n)) {
1024
+ out.add(n.typeParameter.name.text);
1025
+ visit(n.typeParameter.constraint);
1026
+ return;
1027
+ }
1028
+ n.forEachChild(visit);
1029
+ };
1030
+ visit(node);
1031
+ return out;
1032
+ }
1033
+ function absExtends(check, ext) {
1034
+ if (check.term?.op === "var") return "unknown";
1035
+ if (ext.term?.op === "lit" && (ext.term.value === null || ext.term.value === void 0)) {
1036
+ if (check.term?.op === "lit") {
1037
+ return check.term.value === ext.term.value ? "true" : "false";
1038
+ }
1039
+ if (check.shape.k === "prim" || check.shape.k === "arr" || check.shape.k === "obj" || check.shape.k === "fn" || check.shape.k === "brand" || check.shape.k === "eff") {
1040
+ return "false";
1041
+ }
1042
+ return "unknown";
1043
+ }
1044
+ if (ext.shape.k === "unknown") return "true";
1045
+ if (ext.shape.k === "any") return "true";
1046
+ const cs = check.shape;
1047
+ const es = ext.shape;
1048
+ if (es.k === "prim" && cs.k === "prim") {
1049
+ return cs.type === es.type ? "true" : "false";
1050
+ }
1051
+ if (es.k === "sum") {
1052
+ const flags = es.members.map((m) => absExtends(check, m));
1053
+ if (flags.some((f) => f === "true")) return "true";
1054
+ if (flags.every((f) => f === "false")) return "false";
1055
+ return "unknown";
1056
+ }
1057
+ if (cs.k === "sum") {
1058
+ const flags = cs.members.map((m) => absExtends(m, ext));
1059
+ if (flags.every((f) => f === "true")) return "true";
1060
+ if (flags.every((f) => f === "false")) return "false";
1061
+ return "unknown";
1062
+ }
1063
+ if (cs.k === "arr" && es.k === "arr") {
1064
+ return absExtends(cs.element, es.element);
1065
+ }
1066
+ if (cs.k === "eff" && es.k === "eff" && cs.eff === es.eff) {
1067
+ return absExtends(cs.inner, es.inner);
1068
+ }
1069
+ if (cs.k === "brand" && es.k === "brand") {
1070
+ return cs.name === es.name ? "true" : "false";
1071
+ }
1072
+ if (cs.k === "never") return "true";
1073
+ return "unknown";
1074
+ }
1075
+ function matchExtendsInfer(pattern, actual, inferMap, depth = 0) {
1076
+ if (depth > 6) return "unknown";
1077
+ if (ts2.isInferTypeNode(pattern)) {
1078
+ const name = pattern.typeParameter.name.text;
1079
+ if (!inferMap.has(name)) inferMap.set(name, actual);
1080
+ return "true";
1081
+ }
1082
+ if (ts2.isArrayTypeNode(pattern) && actual.shape.k === "arr") {
1083
+ return matchExtendsInfer(pattern.elementType, actual.shape.element, inferMap, depth + 1);
1084
+ }
1085
+ if (ts2.isTypeReferenceNode(pattern)) {
1086
+ const name = typeNameString(pattern.typeName);
1087
+ const args = pattern.typeArguments ?? [];
1088
+ if ((name === "Array" || name === "ReadonlyArray" || name === "List" || name === "ArrayLike") && args[0] && actual.shape.k === "arr") {
1089
+ return matchExtendsInfer(args[0], actual.shape.element, inferMap, depth + 1);
1090
+ }
1091
+ if (name === "Promise" && args[0] && actual.shape.k === "eff" && actual.shape.eff === "promise") {
1092
+ return matchExtendsInfer(args[0], actual.shape.inner, inferMap, depth + 1);
1093
+ }
1094
+ return "unknown";
1095
+ }
1096
+ if (ts2.isUnionTypeNode(pattern)) {
1097
+ const flags = pattern.types.map((t) => matchExtendsInfer(t, actual, inferMap, depth + 1));
1098
+ if (flags.some((f) => f === "true")) return "true";
1099
+ if (flags.every((f) => f === "false")) return "false";
1100
+ return "unknown";
1101
+ }
1102
+ return "unknown";
1103
+ }
1104
+ function mapConditionalType(ctx, node, depth) {
1105
+ const inferMap = /* @__PURE__ */ new Map();
1106
+ const inferNames = collectInferNames(node.extendsType);
1107
+ const check = mapType(ctx, node.checkType, depth + 1);
1108
+ const astMatch = matchExtendsInfer(node.extendsType, check, inferMap);
1109
+ const extAbs = mapType(ctx, node.extendsType, depth + 1);
1110
+ const absMatch = astMatch !== "unknown" ? astMatch : absExtends(check, extAbs);
1111
+ const condMeta = analyzeCondPattern(ctx, node, check);
1112
+ const savedTp = ctx.typeParams;
1113
+ ctx.typeParams = new Set(savedTp);
1114
+ for (const n of inferNames) ctx.typeParams.add(n);
1115
+ try {
1116
+ const trueAbs = inferMap.size > 0 ? substAbs(mapType(ctx, node.trueType, depth + 1), inferMap) : mapType(ctx, node.trueType, depth + 1);
1117
+ const falseAbs = mapType(ctx, node.falseType, depth + 1);
1118
+ if (absMatch === "true") return trueAbs;
1119
+ if (absMatch === "false") return falseAbs;
1120
+ if (condMeta && check.term?.op === "var") {
1121
+ ctx.pendingCond = { ...condMeta, fallback: falseAbs };
1122
+ return typeVarAbs(condMeta.inferVar);
1123
+ }
1124
+ return absUnion([trueAbs, falseAbs]);
1125
+ } finally {
1126
+ ctx.typeParams = savedTp;
1127
+ }
1128
+ }
1129
+ function analyzeCondPattern(_ctx, node, check) {
1130
+ if (check.term?.op !== "var") return void 0;
1131
+ const fromVar = check.term.id;
1132
+ const ext = node.extendsType;
1133
+ const names = collectInferNames(ext);
1134
+ const inferVar = [...names][0];
1135
+ if (!inferVar) return void 0;
1136
+ if (ts2.isArrayTypeNode(ext)) {
1137
+ return { fromVar, via: "arr", inferVar };
1138
+ }
1139
+ if (ts2.isTypeReferenceNode(ext)) {
1140
+ const n = typeNameString(ext.typeName);
1141
+ if (n === "Promise" || n === "PromiseLike") return { fromVar, via: "promise", inferVar };
1142
+ if (n === "Array" || n === "ReadonlyArray" || n === "List" || n === "ArrayLike") {
1143
+ return { fromVar, via: "arr", inferVar };
1144
+ }
1145
+ }
1146
+ return void 0;
1147
+ }
526
1148
  function mapType(ctx, node, depth) {
527
1149
  if (!node || depth > MAX_ALIAS_DEPTH) return absUnknown();
528
- if (ts.isParenthesizedTypeNode(node)) return mapType(ctx, node.type, depth);
529
- if (ts.isTypeReferenceNode(node)) return mapTypeRef(ctx, node, depth);
530
- if (ts.isArrayTypeNode(node)) return absArr(mapType(ctx, node.elementType, depth));
531
- if (ts.isTupleTypeNode(node)) {
1150
+ if (ts2.isParenthesizedTypeNode(node)) return mapType(ctx, node.type, depth);
1151
+ if (ts2.isTypeReferenceNode(node)) return mapTypeRef(ctx, node, depth);
1152
+ if (ts2.isArrayTypeNode(node)) return absArr(mapType(ctx, node.elementType, depth));
1153
+ if (ts2.isTupleTypeNode(node)) {
532
1154
  for (const element of node.elements) {
533
- const inner = ts.isNamedTupleMember(element) ? element.type : element;
1155
+ const inner = ts2.isNamedTupleMember(element) ? element.type : element;
534
1156
  return absArr(mapType(ctx, inner, depth));
535
1157
  }
536
1158
  return absUnknown();
537
1159
  }
538
- if (ts.isUnionTypeNode(node)) {
539
- return absUnion(node.types.map((t) => mapType(ctx, t, depth)));
1160
+ if (ts2.isUnionTypeNode(node)) {
1161
+ const members = node.types.map((t) => mapType(ctx, t, depth));
1162
+ const hasLit = members.some((m) => m.term?.op === "lit");
1163
+ if (hasLit && members.length > 1) {
1164
+ return absExact({ k: "sum", members });
1165
+ }
1166
+ return absUnion(members);
540
1167
  }
541
- if (ts.isIntersectionTypeNode(node)) {
1168
+ if (ts2.isIntersectionTypeNode(node)) {
542
1169
  const members = node.types.map((t) => mapType(ctx, t, depth));
543
1170
  if (members.length > 0 && members.every((m) => m.shape.k === "obj")) {
544
1171
  const properties = {};
@@ -552,20 +1179,21 @@ function mapType(ctx, node, depth) {
552
1179
  }
553
1180
  return members[0] ?? absUnknown();
554
1181
  }
555
- if (ts.isLiteralTypeNode(node)) {
1182
+ if (ts2.isLiteralTypeNode(node)) {
556
1183
  const lit = node.literal;
557
- if (ts.isStringLiteral(lit)) return absLit(lit.text);
558
- if (ts.isNumericLiteral(lit)) return absLit(Number(lit.text));
559
- if (lit.kind === ts.SyntaxKind.TrueKeyword) return absLit(true);
560
- if (lit.kind === ts.SyntaxKind.FalseKeyword) return absLit(false);
561
- if (ts.isPrefixUnaryExpression(lit) && lit.operator === ts.SyntaxKind.MinusToken && ts.isNumericLiteral(lit.operand)) {
1184
+ if (ts2.isStringLiteral(lit)) return absLit(lit.text);
1185
+ if (ts2.isNumericLiteral(lit)) return absLit(Number(lit.text));
1186
+ if (lit.kind === ts2.SyntaxKind.TrueKeyword) return absLit(true);
1187
+ if (lit.kind === ts2.SyntaxKind.FalseKeyword) return absLit(false);
1188
+ if (lit.kind === ts2.SyntaxKind.NullKeyword) return absNullLit();
1189
+ if (ts2.isPrefixUnaryExpression(lit) && lit.operator === ts2.SyntaxKind.MinusToken && ts2.isNumericLiteral(lit.operand)) {
562
1190
  return absLit(-Number(lit.operand.text));
563
1191
  }
564
1192
  return absUnknown();
565
1193
  }
566
- if (ts.isFunctionTypeNode(node)) return signatureToFnSig(ctx, node);
567
- if (ts.isTypeLiteralNode(node)) {
568
- const ctor = node.members.find(ts.isConstructSignatureDeclaration);
1194
+ if (ts2.isFunctionTypeNode(node)) return signatureToFnSig(ctx, node);
1195
+ if (ts2.isTypeLiteralNode(node)) {
1196
+ const ctor = node.members.find(ts2.isConstructSignatureDeclaration);
569
1197
  if (ctor) {
570
1198
  const params = [];
571
1199
  for (const param of ctor.parameters) {
@@ -577,30 +1205,36 @@ function mapType(ctx, node, depth) {
577
1205
  }
578
1206
  return absObj(mapTypeMembers(ctx, node.members, depth));
579
1207
  }
580
- if (ts.isTypeOperatorNode(node)) {
581
- return node.operator === ts.SyntaxKind.ReadonlyKeyword ? mapType(ctx, node.type, depth) : absUnknown();
1208
+ if (ts2.isTypeOperatorNode(node)) {
1209
+ return node.operator === ts2.SyntaxKind.ReadonlyKeyword ? mapType(ctx, node.type, depth) : absUnknown();
1210
+ }
1211
+ if (ts2.isConditionalTypeNode(node)) {
1212
+ return mapConditionalType(ctx, node, depth);
1213
+ }
1214
+ if (ts2.isInferTypeNode(node)) {
1215
+ return typeVarAbs(node.typeParameter.name.text);
582
1216
  }
583
1217
  switch (node.kind) {
584
- case ts.SyntaxKind.StringKeyword:
1218
+ case ts2.SyntaxKind.StringKeyword:
585
1219
  return absPrim("string");
586
- case ts.SyntaxKind.NumberKeyword:
1220
+ case ts2.SyntaxKind.NumberKeyword:
587
1221
  return absPrim("number");
588
- case ts.SyntaxKind.BooleanKeyword:
1222
+ case ts2.SyntaxKind.BooleanKeyword:
589
1223
  return absPrim("boolean");
590
- case ts.SyntaxKind.BigIntKeyword:
1224
+ case ts2.SyntaxKind.BigIntKeyword:
591
1225
  return absPrim("bigint");
592
- case ts.SyntaxKind.SymbolKeyword:
1226
+ case ts2.SyntaxKind.SymbolKeyword:
593
1227
  return absPrim("symbol");
594
- case ts.SyntaxKind.AnyKeyword:
595
- case ts.SyntaxKind.UnknownKeyword:
596
- case ts.SyntaxKind.ObjectKeyword:
1228
+ case ts2.SyntaxKind.AnyKeyword:
1229
+ case ts2.SyntaxKind.UnknownKeyword:
1230
+ case ts2.SyntaxKind.ObjectKeyword:
597
1231
  return absUnknown();
598
- case ts.SyntaxKind.VoidKeyword:
599
- case ts.SyntaxKind.UndefinedKeyword:
1232
+ case ts2.SyntaxKind.VoidKeyword:
1233
+ case ts2.SyntaxKind.UndefinedKeyword:
600
1234
  return absUndefLit();
601
- case ts.SyntaxKind.NeverKeyword:
1235
+ case ts2.SyntaxKind.NeverKeyword:
602
1236
  return absNever();
603
- case ts.SyntaxKind.NullKeyword:
1237
+ case ts2.SyntaxKind.NullKeyword:
604
1238
  return absNullLit();
605
1239
  default:
606
1240
  return absUnknown();
@@ -685,6 +1319,9 @@ function emitAbs(a, expanding) {
685
1319
  }
686
1320
  }
687
1321
  export {
1322
+ dtsPathToContractDraft,
1323
+ dtsToContractDraft,
688
1324
  emitEnvModule,
1325
+ formatDtsContractDraft,
689
1326
  harvestDts
690
1327
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nudojs/harvester",
3
- "version": "0.2.6",
3
+ "version": "0.2.8-beta.0",
4
4
  "engines": {
5
5
  "node": ">=20"
6
6
  },
@@ -29,8 +29,8 @@
29
29
  "registry": "https://registry.npmjs.org"
30
30
  },
31
31
  "dependencies": {
32
- "typescript": "^5.8.0",
33
- "@nudojs/core": "2.0.1"
32
+ "typescript": "^5.9.3",
33
+ "@nudojs/core": "3.0.0-beta.0"
34
34
  },
35
35
  "scripts": {
36
36
  "build": "tsup src/index.ts --format esm --dts"