@nudojs/core 1.2.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -21,6 +21,7 @@ import {
21
21
  getFnImpl,
22
22
  getSlot,
23
23
  gt,
24
+ identBoundaryRegex,
24
25
  joinAbs,
25
26
  le,
26
27
  lit,
@@ -36,12 +37,69 @@ import {
36
37
  ptypeof,
37
38
  runTranspiledOptionsMemoKey,
38
39
  snapshotAbs,
40
+ stripStringsKeepComments,
39
41
  termToString,
40
42
  tryRunTranspiled,
41
43
  unknown,
42
44
  v,
43
- withExecPhi
44
- } from "./chunk-WB565PQK.js";
45
+ withEvalBudgetOverride,
46
+ withExecPhi,
47
+ withFnBudgetOverride
48
+ } from "./chunk-CKH6GQAI.js";
49
+
50
+ // src/algebra/hash-source.ts
51
+ var lastHashSource;
52
+ var lastHashOut;
53
+ function hashSource(s) {
54
+ if (s === lastHashSource && lastHashOut !== void 0) return lastHashOut;
55
+ let h1 = 2166136261;
56
+ let h2 = 2654435769;
57
+ for (let i = 0; i < s.length; i++) {
58
+ const c = s.charCodeAt(i);
59
+ h1 ^= c;
60
+ h1 = Math.imul(h1, 16777619);
61
+ h2 = Math.imul(h2 ^ c, 16777619);
62
+ }
63
+ lastHashSource = s;
64
+ lastHashOut = `${(h1 >>> 0).toString(36)}-${(h2 >>> 0).toString(36)}_${s.length.toString(36)}`;
65
+ return lastHashOut;
66
+ }
67
+ function resetHashSourceCache() {
68
+ lastHashSource = void 0;
69
+ lastHashOut = void 0;
70
+ }
71
+
72
+ // src/algebra/sidecar-path.ts
73
+ function normPath(p) {
74
+ return p.replace(/\\/g, "/");
75
+ }
76
+ function resolveDepPath(fromFile, spec) {
77
+ const s = normPath(spec);
78
+ if (!s.startsWith(".")) return s;
79
+ const from = normPath(fromFile);
80
+ const i = from.lastIndexOf("/");
81
+ const base = i >= 0 ? from.slice(0, i) : "";
82
+ const parts = base ? base.split("/") : [];
83
+ for (const seg of s.split("/")) {
84
+ if (seg === "" || seg === ".") continue;
85
+ if (seg === "..") parts.pop();
86
+ else parts.push(seg);
87
+ }
88
+ return parts.join("/");
89
+ }
90
+ function isNodeModulesPath(p) {
91
+ const f = normPath(p);
92
+ if (f === "node_modules" || f.startsWith("node_modules/")) return true;
93
+ return f.includes("/node_modules/") || f.endsWith("/node_modules");
94
+ }
95
+ function sidecarPathOf(file) {
96
+ const f = normPath(file);
97
+ if (f.endsWith(".mjs")) return f.slice(0, -4) + ".nudo.js";
98
+ if (f.endsWith(".js")) return f.slice(0, -3) + ".nudo.js";
99
+ if (f.endsWith(".mts")) return f.slice(0, -4) + ".nudo.ts";
100
+ if (f.endsWith(".ts")) return f.slice(0, -3) + ".nudo.ts";
101
+ return `${f}.nudo.js`;
102
+ }
45
103
 
46
104
  // src/algebra/constraint.ts
47
105
  var SELF = "__nudo_self__";
@@ -190,6 +248,28 @@ function union(...cs) {
190
248
  members: cs.map((c) => asNestedConstraint(c, "union member"))
191
249
  });
192
250
  }
251
+ function nullable(c) {
252
+ return makeBuilder(void 0, [], {
253
+ members: [
254
+ asNestedConstraint(c, "nullable()"),
255
+ toPlainConstraint(litC(null)),
256
+ toPlainConstraint(litC(void 0))
257
+ ]
258
+ });
259
+ }
260
+ function constraintAdmitsNullish(c) {
261
+ if (c.members) return c.members.some((m) => constraintAdmitsNullish(m));
262
+ if (!c.prim && c.preds.length === 0 && !c.fields && !c.element && !c.fn) return true;
263
+ for (const p of c.preds) {
264
+ for (const flat of p.op === "and" ? p.args : [p]) {
265
+ if (flat.op !== "eq") continue;
266
+ for (const side of [flat.a, flat.b]) {
267
+ if (side.op === "lit" && (side.value === null || side.value === void 0)) return true;
268
+ }
269
+ }
270
+ }
271
+ return false;
272
+ }
193
273
  function fn(params, returns, opts) {
194
274
  const normalized = {};
195
275
  for (const [k, v2] of Object.entries(params)) {
@@ -388,21 +468,26 @@ function memberLitValue(m) {
388
468
  leaves.push(p2);
389
469
  };
390
470
  m.preds.forEach(visit2);
391
- if (leaves.length !== 1) return void 0;
471
+ if (leaves.length !== 1) return { ok: false };
392
472
  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;
473
+ if (p.op !== "eq") return { ok: false };
474
+ if (p.a.op === "var" && p.b.op === "lit") {
475
+ return { ok: true, v: p.b.value };
476
+ }
477
+ if (p.b.op === "var" && p.a.op === "lit") {
478
+ return { ok: true, v: p.a.value };
479
+ }
480
+ return { ok: false };
397
481
  }
398
482
  function samePrimLiteralUnionAbs(members, t) {
399
483
  if (members.length === 0) return void 0;
400
484
  const values = [];
401
485
  let prim;
402
486
  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;
487
+ const mk = memberLitValue(m);
488
+ if (!mk.ok) return void 0;
489
+ const v2 = mk.v;
490
+ if (v2 === void 0 || v2 === null) return void 0;
406
491
  const mp = m.prim ?? (typeof v2 === "number" ? "number" : typeof v2 === "string" ? "string" : "boolean");
407
492
  if (prim === void 0) prim = mp;
408
493
  else if (prim !== mp) return void 0;
@@ -461,10 +546,10 @@ function constraintOnTermAbs(c, t) {
461
546
  if (c.prim) {
462
547
  return abs({ k: "prim", type: c.prim }, t, predOut, "path");
463
548
  }
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)
549
+ const allEqNullish = c.preds.length > 0 && c.preds.every(
550
+ (p) => p.op === "eq" && (p.b.op === "lit" && (p.b.value === null || p.b.value === void 0) || p.a.op === "lit" && (p.a.value === null || p.a.value === void 0))
466
551
  );
467
- if (allEqNull) {
552
+ if (allEqNullish) {
468
553
  return abs({ k: "unknown" }, t, predOut, "path");
469
554
  }
470
555
  if (c.preds.length > 0) {
@@ -505,60 +590,6 @@ function throwConstraintToKinds(c) {
505
590
  return void 0;
506
591
  }
507
592
 
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
593
  // src/algebra/refine.ts
563
594
  function extractNudoImports(source) {
564
595
  const out = [];
@@ -624,7 +655,8 @@ var sidecarInjects = {
624
655
  and: andC,
625
656
  partial,
626
657
  pick,
627
- omit
658
+ omit,
659
+ nullable
628
660
  };
629
661
  function isSidecarSpec(spec) {
630
662
  return spec.endsWith(".nudo.js") || spec.endsWith(".nudo.ts");
@@ -974,6 +1006,7 @@ function execNudoModule(src, opts) {
974
1006
  return execNudoModuleCached(src, opts ?? {});
975
1007
  }
976
1008
  var nudoModuleExecCache = /* @__PURE__ */ new Map();
1009
+ var nudoModuleFailCache = /* @__PURE__ */ new Map();
977
1010
  var MAX_NUDO_MODULE_EXEC = 64;
978
1011
  var MAX_SIDECAR_CLOSURE = 64;
979
1012
  function sidecarDepClosure(src, fromFile, loadModule, out) {
@@ -1020,19 +1053,34 @@ function execNudoModuleCached(src, opts) {
1020
1053
  nudoModuleExecCache.set(key, hit);
1021
1054
  return hit;
1022
1055
  }
1056
+ if (nudoModuleFailCache.has(key)) {
1057
+ throw nudoModuleFailCache.get(key);
1058
+ }
1023
1059
  }
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);
1060
+ try {
1061
+ const out = execSidecar(src, opts, opts.fromFile ? [opts.fromFile] : [], preloaded);
1062
+ if (key !== void 0) {
1063
+ if (nudoModuleExecCache.size >= MAX_NUDO_MODULE_EXEC) {
1064
+ const oldest = nudoModuleExecCache.keys().next().value;
1065
+ if (oldest !== void 0) nudoModuleExecCache.delete(oldest);
1066
+ }
1067
+ nudoModuleExecCache.set(key, out);
1029
1068
  }
1030
- nudoModuleExecCache.set(key, out);
1069
+ return out;
1070
+ } catch (e) {
1071
+ if (key !== void 0) {
1072
+ if (nudoModuleFailCache.size >= MAX_NUDO_MODULE_EXEC) {
1073
+ const oldest = nudoModuleFailCache.keys().next().value;
1074
+ if (oldest !== void 0) nudoModuleFailCache.delete(oldest);
1075
+ }
1076
+ nudoModuleFailCache.set(key, e);
1077
+ }
1078
+ throw e;
1031
1079
  }
1032
- return out;
1033
1080
  }
1034
1081
  function resetNudoModuleExecCache() {
1035
1082
  nudoModuleExecCache.clear();
1083
+ nudoModuleFailCache.clear();
1036
1084
  }
1037
1085
  function collectConstraints(source, opts) {
1038
1086
  const map = /* @__PURE__ */ new Map();
@@ -1102,9 +1150,9 @@ function collectConstraints(source, opts) {
1102
1150
  return map;
1103
1151
  }
1104
1152
  function extractRefineLines(source, fnName) {
1105
- const escaped = fnName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1153
+ const escaped = identBoundaryRegex(fnName);
1106
1154
  const fnRe = new RegExp(
1107
- `(?:export\\s+(?:default\\s+)?)?(?:async\\s+)?(?:function\\s+${escaped}\\b|const\\s+${escaped}\\s*=)`
1155
+ `(?:export\\s+(?:default\\s+)?)?(?:async\\s+)?(?:function\\s+${escaped}|const\\s+${escaped}\\s*=)`
1108
1156
  );
1109
1157
  const m = source.match(fnRe);
1110
1158
  if (!m || m.index === void 0) return [];
@@ -1124,7 +1172,7 @@ function extractRefineLines(source, fnName) {
1124
1172
  return reqs;
1125
1173
  }
1126
1174
  function extractRefinesFromSource(source, fnName, opts = {}) {
1127
- if (!source.includes("@nudo:contract")) return [];
1175
+ if (!stripStringsKeepComments(source).includes("@nudo:contract")) return [];
1128
1176
  const constraints = collectConstraints(source, opts);
1129
1177
  const out = [];
1130
1178
  for (const line of extractRefineLines(source, fnName)) {
@@ -1155,7 +1203,7 @@ function refineToIndexedFull(source, fnName, paramNames, opts = {}) {
1155
1203
  return out;
1156
1204
  }
1157
1205
  function extractRefineReturnFromSource(source, fnName, opts = {}) {
1158
- if (!source.includes("@nudo:contract")) return void 0;
1206
+ if (!stripStringsKeepComments(source).includes("@nudo:contract")) return void 0;
1159
1207
  const constraints = collectConstraints(source, opts);
1160
1208
  for (const line of extractRefineLines(source, fnName)) {
1161
1209
  const parts = line.split(/&&|,/).map((s) => s.trim()).filter(Boolean);
@@ -1171,14 +1219,15 @@ function extractRefineReturnFromSource(source, fnName, opts = {}) {
1171
1219
  return void 0;
1172
1220
  }
1173
1221
  function extractDeclaredThrows(source, fnName) {
1174
- if (!source.includes("@nudo:throws") && !source.includes("!! throws")) {
1222
+ const directiveSrc = stripStringsKeepComments(source);
1223
+ if (!directiveSrc.includes("@nudo:throws") && !directiveSrc.includes("!! throws")) {
1175
1224
  return void 0;
1176
1225
  }
1177
1226
  const kinds = /* @__PURE__ */ new Set();
1178
1227
  let any2 = false;
1179
- const escaped = fnName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1228
+ const escaped = identBoundaryRegex(fnName);
1180
1229
  const fnRe = new RegExp(
1181
- `(?:export\\s+(?:default\\s+)?)?(?:async\\s+)?(?:function\\s+${escaped}\\b|const\\s+${escaped}\\s*=)`
1230
+ `(?:export\\s+(?:default\\s+)?)?(?:async\\s+)?(?:function\\s+${escaped}|const\\s+${escaped}\\s*=)`
1182
1231
  );
1183
1232
  const m = source.match(fnRe);
1184
1233
  if (!m || m.index === void 0) return void 0;
@@ -1225,6 +1274,48 @@ function extractDeclaredThrows(source, fnName) {
1225
1274
  if (kinds.size > 0) return [...kinds];
1226
1275
  return void 0;
1227
1276
  }
1277
+ function extractFnBudget(source, fnName) {
1278
+ const directiveSrc = stripStringsKeepComments(source);
1279
+ if (!directiveSrc.includes("@nudo:budget")) return void 0;
1280
+ const escaped = identBoundaryRegex(fnName);
1281
+ const fnRe = new RegExp(
1282
+ `(?:export\\s+(?:default\\s+)?)?(?:async\\s+)?(?:function\\s+${escaped}|const\\s+${escaped}\\s*=)`
1283
+ );
1284
+ const m = source.match(fnRe);
1285
+ if (!m || m.index === void 0) return void 0;
1286
+ const before = source.slice(0, m.index);
1287
+ const lines = before.split("\n");
1288
+ let seenComment = false;
1289
+ const out = {};
1290
+ for (let i = lines.length - 1; i >= 0; i--) {
1291
+ const line = lines[i].trim();
1292
+ if (line === "") {
1293
+ if (seenComment) break;
1294
+ continue;
1295
+ }
1296
+ if (line === "*/") continue;
1297
+ if (line.startsWith("*") || line.startsWith("/*") || line.startsWith("//")) {
1298
+ seenComment = true;
1299
+ const body = line.replace(/^[*/\s]+/, "").replace(/\*\/$/, "").trim();
1300
+ const bd = body.match(/@nudo:budget\s+(.+)$/i);
1301
+ if (bd) {
1302
+ for (const part of bd[1].split(/[,\s|]+/).map((s) => s.trim()).filter(Boolean)) {
1303
+ const kv = part.match(/^(forks|calls|depth)\s*=\s*(\d+)$/i);
1304
+ if (kv) {
1305
+ const n = Number(kv[2]);
1306
+ if (Number.isFinite(n) && n >= 1) {
1307
+ const key = kv[1].toLowerCase();
1308
+ out[key] = Math.floor(n);
1309
+ }
1310
+ }
1311
+ }
1312
+ }
1313
+ continue;
1314
+ }
1315
+ break;
1316
+ }
1317
+ return Object.keys(out).length > 0 ? out : void 0;
1318
+ }
1228
1319
 
1229
1320
  // src/algebra/interface.ts
1230
1321
  var interfaceDiagCollector = null;
@@ -1321,11 +1412,24 @@ function localNamedExports(source) {
1321
1412
  for (const stmt of ast.program.body) {
1322
1413
  const s = stmt;
1323
1414
  if (s.type === "ExportNamedDeclaration") {
1324
- if (s.source) continue;
1415
+ if (s.source) {
1416
+ const specs = s.specifiers ?? [];
1417
+ for (const spec of specs) {
1418
+ if (spec.type !== "ExportNamespaceSpecifier") continue;
1419
+ const name = identName(spec.exported);
1420
+ if (name) out.add(name);
1421
+ }
1422
+ continue;
1423
+ }
1325
1424
  const d = s.declaration;
1326
1425
  if (!d) {
1327
1426
  const specs = s.specifiers ?? [];
1328
1427
  for (const spec of specs) {
1428
+ if (spec.type === "ExportNamespaceSpecifier") {
1429
+ const name2 = identName(spec.exported);
1430
+ if (name2) out.add(name2);
1431
+ continue;
1432
+ }
1329
1433
  if (spec.type !== "ExportSpecifier") continue;
1330
1434
  const name = identName(spec.exported);
1331
1435
  if (!name) continue;
@@ -1473,17 +1577,53 @@ function sidecarAutoBindAllowed(sidecarPath, autoBind, projectDir) {
1473
1577
  return false;
1474
1578
  }
1475
1579
  function isDefaultExportLocal(source, localName) {
1580
+ const name = identBoundaryRegex(localName);
1476
1581
  const reList = new RegExp(
1477
- `export\\s*\\{[^}]*\\b${localName}\\s+as\\s+default\\b[^}]*\\}`,
1582
+ `export\\s*\\{[^}]*${name}\\s+as\\s+default(?![\\w$])[^}]*\\}`,
1478
1583
  "m"
1479
1584
  );
1480
1585
  const reDefaultFn = new RegExp(
1481
- `export\\s+default\\s+(?:async\\s+)?function\\s+${localName}\\b`,
1586
+ `export\\s+default\\s+(?:async\\s+)?function\\s+${name}`,
1587
+ "m"
1588
+ );
1589
+ const reDefaultId = new RegExp(
1590
+ `export\\s+default\\s+${name}`,
1482
1591
  "m"
1483
1592
  );
1484
- const reDefaultId = new RegExp(`export\\s+default\\s+${localName}\\b`, "m");
1485
1593
  return reList.test(source) || reDefaultFn.test(source) || reDefaultId.test(source);
1486
1594
  }
1595
+ function sidecarExportNames(sidecarSrc) {
1596
+ try {
1597
+ return localNamedExports(sidecarSrc);
1598
+ } catch {
1599
+ return void 0;
1600
+ }
1601
+ }
1602
+ function sidecarMayBind(sidecarNames, fnName, source) {
1603
+ if (!sidecarNames) return true;
1604
+ if (sidecarNames.size === 0) return true;
1605
+ if (sidecarNames.has(fnName)) return true;
1606
+ if (sidecarNames.has("default") && isDefaultExportLocal(source, fnName)) return true;
1607
+ if (fnName.includes(".")) {
1608
+ const [cls, method] = fnName.split(".", 2);
1609
+ if (cls && method) {
1610
+ if (sidecarNames.has(`${cls}_${method}`)) return true;
1611
+ if (sidecarNames.has(cls)) return true;
1612
+ if (sidecarNames.has(method)) return true;
1613
+ }
1614
+ }
1615
+ return false;
1616
+ }
1617
+ var sidecarLoadFailures = /* @__PURE__ */ new Map();
1618
+ function noteSidecarLoadFailure(sidecarPath, reason) {
1619
+ const key = `${sidecarPath}\0${reason}`;
1620
+ if (sidecarLoadFailures.has(key)) return false;
1621
+ sidecarLoadFailures.set(key, reason);
1622
+ return true;
1623
+ }
1624
+ function resetSidecarLoadFailureCache() {
1625
+ sidecarLoadFailures.clear();
1626
+ }
1487
1627
  function loadSidecarBinding(source, fnName, opts) {
1488
1628
  const { loadModule, fromFile, autoBind, projectDir } = opts;
1489
1629
  if (!loadModule || !fromFile) return { ok: false };
@@ -1494,15 +1634,30 @@ function loadSidecarBinding(source, fnName, opts) {
1494
1634
  const sidecarSrc = loadModule(spec, fromFile);
1495
1635
  if (sidecarSrc === void 0) return { ok: false };
1496
1636
  if (sidecarSrc === source) return { ok: false };
1637
+ const scNames = sidecarExportNames(sidecarSrc);
1638
+ if (!sidecarMayBind(scNames, fnName, source)) return { ok: false };
1497
1639
  let exports;
1498
1640
  try {
1499
1641
  exports = execNudoModule(sidecarSrc, { loadModule, fromFile: sidecarPath });
1500
1642
  } 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
- });
1643
+ const reason = e instanceof Error ? e.message : String(e);
1644
+ const code = e instanceof NudoSidecarError ? e.code : "nudo:interface-load";
1645
+ if (noteSidecarLoadFailure(sidecarPath, reason)) {
1646
+ let affected = 1;
1647
+ if (scNames && scNames.size > 0) {
1648
+ const srcExports = localNamedExports(source);
1649
+ let n = 0;
1650
+ for (const name of srcExports) {
1651
+ if (sidecarMayBind(scNames, name, source)) n++;
1652
+ }
1653
+ if (n > 0) affected = n;
1654
+ }
1655
+ collectDiag2({
1656
+ code,
1657
+ message: affected > 1 ? `sidecar '${spec}' failed to load: ${reason} (affects ${affected} bindings)` : `sidecar '${spec}' failed to load: ${reason}`,
1658
+ file: sidecarPath
1659
+ });
1660
+ }
1506
1661
  return { ok: false };
1507
1662
  }
1508
1663
  let binding = exports[fnName];
@@ -1585,14 +1740,17 @@ function eqLitsOf(c) {
1585
1740
  if (p.op !== "eq") return;
1586
1741
  let term;
1587
1742
  let value;
1743
+ let hasLit = false;
1588
1744
  if (p.a.op === "var" && p.b.op === "lit") {
1589
1745
  term = p.a;
1590
1746
  value = p.b.value;
1747
+ hasLit = true;
1591
1748
  } else if (p.b.op === "var" && p.a.op === "lit") {
1592
1749
  term = p.b;
1593
1750
  value = p.a.value;
1751
+ hasLit = true;
1594
1752
  }
1595
- if (term !== void 0 && value !== void 0) {
1753
+ if (term !== void 0 && hasLit) {
1596
1754
  out.push({ term: termToString(term), value });
1597
1755
  }
1598
1756
  };
@@ -2055,7 +2213,7 @@ function hasModuleSyntax(file) {
2055
2213
  return false;
2056
2214
  }
2057
2215
  function canSkipLiteralCallScan(source, file) {
2058
- if (source.includes("@nudo:contract")) return false;
2216
+ if (stripStringsKeepComments(source).includes("@nudo:contract")) return false;
2059
2217
  if (hasModuleSyntax(file)) return false;
2060
2218
  if (hasAnyCallLike(file)) return false;
2061
2219
  const fps = fnFingerprints(source, file);
@@ -2209,6 +2367,8 @@ function predKey(p, rename) {
2209
2367
  return `not(${predKey(p.arg, rename)})`;
2210
2368
  case "typeof":
2211
2369
  return `typeof(${termKey(p.t, rename)},${p.type})`;
2370
+ case "assumeFinite":
2371
+ return `assumeFinite(${termKey(p.t, rename)})`;
2212
2372
  }
2213
2373
  }
2214
2374
  function shapeKey(s, seen, rename) {
@@ -2359,6 +2519,8 @@ function renamePred(p, map) {
2359
2519
  return { op: "not", arg: renamePred(p.arg, map) };
2360
2520
  case "typeof":
2361
2521
  return { op: "typeof", t: renameTerm(p.t, map), type: p.type };
2522
+ case "assumeFinite":
2523
+ return { op: "assumeFinite", t: renameTerm(p.t, map) };
2362
2524
  }
2363
2525
  }
2364
2526
  function renameAbs(a, map) {
@@ -2993,6 +3155,23 @@ function extractFn(source, fnName, fileAst) {
2993
3155
  registerFnNode(propName, right);
2994
3156
  }
2995
3157
  }
3158
+ for (const stmt of file.program.body) {
3159
+ if (stmt.type !== "ExportNamedDeclaration" || stmt.declaration) continue;
3160
+ if (stmt.source) continue;
3161
+ const specs = stmt.specifiers ?? [];
3162
+ for (const spec of specs) {
3163
+ if (spec.type !== "ExportSpecifier") continue;
3164
+ const localName = spec.local?.name;
3165
+ const exportedName = spec.exported?.name ?? spec.exported?.value;
3166
+ if (!localName || exportedName == null) continue;
3167
+ const exp = String(exportedName);
3168
+ if (exp === localName || env.fns.has(exp)) continue;
3169
+ const target = env.fns.get(localName);
3170
+ if (!target) continue;
3171
+ env.fns.set(exp, target);
3172
+ formalsByName.set(exp, formalsByName.get(localName) ?? []);
3173
+ }
3174
+ }
2996
3175
  const fn2 = env.fns.get(fnName);
2997
3176
  if (!fn2) return void 0;
2998
3177
  return { params: fn2.params, body: fn2.body, env, formals: formalsByName.get(fnName) ?? [] };
@@ -3027,7 +3206,7 @@ function generalizeFromAstUncached(fnName, source, opts = {}) {
3027
3206
  if (opts.refine) {
3028
3207
  try {
3029
3208
  const r = opts.refine;
3030
- const hasDirective = source.includes("@nudo:contract") || source.includes("@nudo:contract");
3209
+ const hasDirective = stripStringsKeepComments(source).includes("@nudo:contract");
3031
3210
  const sc = opts.sidecarFp ?? (r.loadModule && r.fromFile ? sidecarClosureFingerprint(r.fromFile, r) : void 0);
3032
3211
  const eff = hasDirective || sc !== void 0 ? effectiveInterface(source, fnName, r) : void 0;
3033
3212
  const reqs = eff?.params ?? [];
@@ -3036,6 +3215,7 @@ function generalizeFromAstUncached(fnName, source, opts = {}) {
3036
3215
  param: e.param,
3037
3216
  pred: instantiateConstraint(e.constraint, e.param)
3038
3217
  }));
3218
+ const patternFields = /* @__PURE__ */ new Map();
3039
3219
  for (const e of reqs) {
3040
3220
  const idx = params.indexOf(e.param);
3041
3221
  if (idx >= 0) {
@@ -3048,8 +3228,55 @@ function generalizeFromAstUncached(fnName, source, opts = {}) {
3048
3228
  abs: snapshotAbs(entryAbs),
3049
3229
  source: "refine"
3050
3230
  });
3231
+ continue;
3232
+ }
3233
+ const hit = formals.length > 0 ? locateContractParam(formals, e.param) : void 0;
3234
+ if (hit && hit.field !== void 0) {
3235
+ let bag = patternFields.get(hit.index);
3236
+ if (!bag) {
3237
+ bag = {};
3238
+ patternFields.set(hit.index, bag);
3239
+ }
3240
+ const fieldAbs = constraintToEntryAbs(e.constraint, e.param);
3241
+ bag[hit.field] = {
3242
+ abs: fieldAbs,
3243
+ ...e.constraint.isOptional ? { optional: true } : {}
3244
+ };
3245
+ } else if (hit) {
3246
+ const entryAbs = constraintToEntryAbs(e.constraint, e.param);
3247
+ typeParams[hit.index] = {
3248
+ id: typeParams[hit.index].id,
3249
+ value: entryAbs
3250
+ };
3251
+ refineEntryShapes.set(e.param, {
3252
+ abs: snapshotAbs(entryAbs),
3253
+ source: "refine"
3254
+ });
3051
3255
  }
3052
3256
  }
3257
+ for (const [pIdx, bag] of patternFields) {
3258
+ const slots = {};
3259
+ for (const [key, rec] of Object.entries(bag)) {
3260
+ slots[key] = {
3261
+ value: rec.abs,
3262
+ ...rec.optional ? { optional: true } : {}
3263
+ };
3264
+ }
3265
+ const objAbs = abs(
3266
+ { k: "obj", slots },
3267
+ v(params[pIdx] ?? `_p${pIdx}`),
3268
+ void 0,
3269
+ "path"
3270
+ );
3271
+ typeParams[pIdx] = {
3272
+ id: typeParams[pIdx].id,
3273
+ value: objAbs
3274
+ };
3275
+ refineEntryShapes.set(params[pIdx] ?? `_p${pIdx}`, {
3276
+ abs: snapshotAbs(objAbs),
3277
+ source: "refine"
3278
+ });
3279
+ }
3053
3280
  }
3054
3281
  } catch {
3055
3282
  }
@@ -3080,17 +3307,49 @@ function generalizeFromAstUncached(fnName, source, opts = {}) {
3080
3307
  const hasMocks = inject && Object.keys(inject.mocks ?? {}).length > 0;
3081
3308
  const hasEnv = inject && Object.keys(inject.envGlobals ?? {}).length > 0;
3082
3309
  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);
3310
+ const directiveSrc = stripStringsKeepComments(source);
3311
+ const hasFnMockDirective = /@nudo:mock\s+\w+\s*(?:=|from\b)/.test(directiveSrc);
3312
+ const hasModMockDirective = /@nudo:mock-module\b/.test(directiveSrc);
3085
3313
  const hasModInject = inject && inject.modules && Object.keys(inject.modules).length > 0;
3086
3314
  const mockGated = hasFnMockDirective && !hasMocks || hasModMockDirective && !hasModInject;
3087
- const envGatedSource = /@nudo:env\b/.test(source) && !hasEnv;
3315
+ const envGatedSource = /@nudo:env\b/.test(directiveSrc) && !hasEnv;
3088
3316
  const boundNames = formals.flatMap(
3089
3317
  (f) => f.kind === "pattern" ? f.bound : [f.name]
3090
3318
  );
3091
3319
  const envGated = envGatedSource && freeIdentifiers(body, boundNames).size > 0;
3092
- const replaceGated = /@nudo:replace\b/.test(source) && !hasReps;
3320
+ const replaceGated = /@nudo:replace\b/.test(directiveSrc) && !hasReps;
3093
3321
  const evalEligible = !unresolvableImports && !mockGated && !envGated && !replaceGated;
3322
+ const fnBudget = extractFnBudget(source, fnName);
3323
+ const runCore = (args, phi) => {
3324
+ if (!evalEligible) return void 0;
3325
+ try {
3326
+ const evalRun = evalRunOf(source, opts.modules, opts.inject);
3327
+ if (!evalRun) {
3328
+ return void 0;
3329
+ } else if (fnName.includes(".")) {
3330
+ const [clsName, methodName] = fnName.split(".", 2);
3331
+ const clsAbs = evalRun[clsName ?? ""];
3332
+ if (clsAbs && typeof clsAbs === "object" && "shape" in clsAbs) {
3333
+ const nCtor = ctorParamCountOf(fileAst, clsName ?? "");
3334
+ const inst = $new(clsAbs, Array.from({ length: nCtor }, () => abs({ k: "any" }, void 0, pTrue, "path")));
3335
+ return withExecPhi(phi, () => $invoke(inst, methodName ?? "", args));
3336
+ }
3337
+ return void 0;
3338
+ } else if (fnName in evalRun) {
3339
+ const evalArgs = args.map((a, i) => {
3340
+ const shape2 = promoteScan.promotedShapes.get(params[i]);
3341
+ if (shape2 && (a.shape.k === "any" || a.shape.k === "unknown")) {
3342
+ return { shape: shape2, term: a.term, pred: a.pred, conf: "path" };
3343
+ }
3344
+ return a;
3345
+ });
3346
+ return callTranspiledExportFull(evalRun, fnName, evalArgs, { phi }).result;
3347
+ }
3348
+ return void 0;
3349
+ } catch {
3350
+ return void 0;
3351
+ }
3352
+ };
3094
3353
  const run = (args, phi = pTrue) => {
3095
3354
  const { key, varOrder } = instantiateMemoKey(args, phi);
3096
3355
  const hit = instMemo.get(key);
@@ -3098,30 +3357,14 @@ function generalizeFromAstUncached(fnName, source, opts = {}) {
3098
3357
  return alphaRenameResult(hit.result, hit.varOrder, varOrder);
3099
3358
  }
3100
3359
  let result;
3101
- if (evalEligible) {
3102
- try {
3103
- const evalRun = evalRunOf(source, opts.modules, opts.inject);
3104
- if (!evalRun) {
3105
- } else if (fnName.includes(".")) {
3106
- const [clsName, methodName] = fnName.split(".", 2);
3107
- const clsAbs = evalRun[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 evalRun) {
3114
- const evalArgs = 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(evalRun, fnName, evalArgs, { phi }).result;
3122
- }
3123
- } catch {
3124
- }
3360
+ if (fnBudget) {
3361
+ withFnBudgetOverride(fnBudget, () => {
3362
+ withEvalBudgetOverride(fnBudget, () => {
3363
+ result = runCore(args, phi);
3364
+ });
3365
+ });
3366
+ } else {
3367
+ result = runCore(args, phi);
3125
3368
  }
3126
3369
  if (result === void 0) {
3127
3370
  result = abs({ k: "unknown" }, void 0, void 0, "opaque");
@@ -3689,6 +3932,7 @@ function resolveExportSource(modSrc, fnName, loadSpecFrom, depth = 0, baseFromFi
3689
3932
  }
3690
3933
  const file = parseSource(modSrc);
3691
3934
  let nextSpec;
3935
+ let nextFnName = fnName;
3692
3936
  for (const stmt of file.program.body) {
3693
3937
  if (stmt.type !== "ExportNamedDeclaration" && stmt.type !== "ExportAllDeclaration") continue;
3694
3938
  const src = stmt.source;
@@ -3701,8 +3945,11 @@ function resolveExportSource(modSrc, fnName, loadSpecFrom, depth = 0, baseFromFi
3701
3945
  for (const sp of clause) {
3702
3946
  if (sp.type !== "ExportSpecifier") continue;
3703
3947
  const local = sp.local;
3704
- if (local?.type === "Identifier" && local.name === fnName) {
3948
+ const exported = sp.exported ?? sp.local;
3949
+ const exportedName = exported?.type === "Identifier" ? exported.name : exported && "value" in exported ? String(exported.value ?? "") : void 0;
3950
+ if (exportedName === fnName) {
3705
3951
  nextSpec = String(src.value);
3952
+ nextFnName = local?.type === "Identifier" && local.name ? local.name : fnName;
3706
3953
  break;
3707
3954
  }
3708
3955
  }
@@ -3715,7 +3962,7 @@ function resolveExportSource(modSrc, fnName, loadSpecFrom, depth = 0, baseFromFi
3715
3962
  const nextFromFile = hopFrom ? resolveDepPath(hopFrom, nextSpec) : "";
3716
3963
  return resolveExportSource(
3717
3964
  next,
3718
- fnName,
3965
+ nextFnName,
3719
3966
  loadSpecFrom,
3720
3967
  depth + 1,
3721
3968
  baseFromFile,
@@ -4845,6 +5092,12 @@ function scanLiteralCalls(source, knownFns, phi, opts) {
4845
5092
  }
4846
5093
 
4847
5094
  export {
5095
+ hashSource,
5096
+ resetHashSourceCache,
5097
+ normPath,
5098
+ resolveDepPath,
5099
+ isNodeModulesPath,
5100
+ sidecarPathOf,
4848
5101
  SELF,
4849
5102
  getTerm,
4850
5103
  lenTerm,
@@ -4858,21 +5111,18 @@ export {
4858
5111
  shape,
4859
5112
  litC,
4860
5113
  union,
5114
+ nullable,
5115
+ constraintAdmitsNullish,
4861
5116
  fn,
4862
5117
  andC,
4863
5118
  partial,
4864
5119
  pick,
4865
5120
  omit,
4866
5121
  instantiateConstraint,
5122
+ instantiateOnTerm,
4867
5123
  constraintToEntryAbs,
4868
5124
  fnConstraintToEntryReqs,
4869
5125
  throwConstraintToKinds,
4870
- hashSource,
4871
- resetHashSourceCache,
4872
- normPath,
4873
- resolveDepPath,
4874
- isNodeModulesPath,
4875
- sidecarPathOf,
4876
5126
  extractNudoImports,
4877
5127
  setRefineDiagCollector,
4878
5128
  refineDiagCount,
@@ -4885,12 +5135,14 @@ export {
4885
5135
  refineToIndexedFull,
4886
5136
  extractRefineReturnFromSource,
4887
5137
  extractDeclaredThrows,
5138
+ extractFnBudget,
4888
5139
  setInterfaceDiagCollector,
4889
5140
  interfaceDiagCount,
4890
5141
  takeInterfaceDiags,
4891
5142
  takeInterfaceDiagsSince,
4892
5143
  localNamedExports,
4893
5144
  generatedExportNames,
5145
+ resetSidecarLoadFailureCache,
4894
5146
  effectiveInterface,
4895
5147
  sidecarClosureFingerprint,
4896
5148
  formatConstraint,