@nudojs/core 1.2.2 → 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.
package/dist/index.js CHANGED
@@ -9,12 +9,14 @@ import {
9
9
  buildArgsFromAssume,
10
10
  canSkipLiteralCallScan,
11
11
  collectAbsFreeVars,
12
+ constraintAdmitsNullish,
12
13
  constraintToEntryAbs,
13
14
  effectiveInterface,
14
15
  evictGeneralizeMemoForPaths,
15
16
  execNudoModule,
16
17
  extractDeclaredThrows,
17
18
  extractFn,
19
+ extractFnBudget,
18
20
  extractNudoImports,
19
21
  extractRefineReturnFromSource,
20
22
  extractRefinesFromSource,
@@ -30,6 +32,7 @@ import {
30
32
  getTerm,
31
33
  hashSource,
32
34
  instantiateConstraint,
35
+ instantiateOnTerm,
33
36
  interfaceDiagCount,
34
37
  interfaceSourceOf,
35
38
  interfaceTierOf,
@@ -44,6 +47,7 @@ import {
44
47
  loadModuleDepsFingerprint,
45
48
  localNamedExports,
46
49
  normPath,
50
+ nullable,
47
51
  number,
48
52
  omit,
49
53
  partial,
@@ -53,6 +57,7 @@ import {
53
57
  resetGeneralizeMemo,
54
58
  resetHashSourceCache,
55
59
  resetNudoModuleExecCache,
60
+ resetSidecarLoadFailureCache,
56
61
  scanLiteralCalls,
57
62
  setInterfaceDiagCollector,
58
63
  setRefineDiagCollector,
@@ -67,7 +72,7 @@ import {
67
72
  takeRefineDiagsSince,
68
73
  throwConstraintToKinds,
69
74
  union
70
- } from "./chunk-TOURNNEA.js";
75
+ } from "./chunk-5YNR4Q7E.js";
71
76
  import {
72
77
  $add,
73
78
  $arguments,
@@ -189,6 +194,8 @@ import {
189
194
  NudoThrow,
190
195
  NudoUnsupportedError,
191
196
  OBJECT_PROTO_METHOD_NAMES,
197
+ PROMISE_MICRO_ERROR_LABEL,
198
+ PROMISE_MICRO_OVERFLOW_LABEL,
192
199
  RUNTIME_IMPORT_RE,
193
200
  TYPEOF_NAMES,
194
201
  abs,
@@ -206,10 +213,12 @@ import {
206
213
  asAbs,
207
214
  asAbsVal,
208
215
  assignSourceSlots,
216
+ assumeFinite,
209
217
  attachFnImpl,
210
218
  beginCollectionFork,
211
219
  betaOf,
212
220
  bigintLit,
221
+ bindImports,
213
222
  bindingsOf,
214
223
  bitandAbs,
215
224
  bitnotAbs,
@@ -228,6 +237,7 @@ import {
228
237
  clearCollectionTables,
229
238
  clearStaleTermPred,
230
239
  cmp,
240
+ collectAbsExports,
231
241
  collectionElementJoin,
232
242
  collectionExactLen,
233
243
  confJoin,
@@ -275,6 +285,7 @@ import {
275
285
  foldRequireSpecArg,
276
286
  foldStaticStringExpr,
277
287
  formalParamDisplayNames,
288
+ formalParamSignatureNames,
278
289
  formalParamsFromNodes,
279
290
  formatAbs,
280
291
  formatAbsMultiline,
@@ -295,6 +306,7 @@ import {
295
306
  gt,
296
307
  gtNum,
297
308
  hostBuiltinCtorName,
309
+ identBoundaryRegex,
298
310
  implies,
299
311
  instantiateReturn,
300
312
  isArrMutator,
@@ -357,6 +369,7 @@ import {
357
369
  migrateNullProto,
358
370
  mod,
359
371
  mul,
372
+ namespaceAbsOf,
360
373
  namespaceNameOf,
361
374
  ne,
362
375
  negAbs,
@@ -438,6 +451,7 @@ import {
438
451
  strLit,
439
452
  strictEqAbs,
440
453
  stringOfSymbol,
454
+ stripStringsKeepComments,
441
455
  stripTypes,
442
456
  sub,
443
457
  substAbs,
@@ -470,7 +484,7 @@ import {
470
484
  v,
471
485
  withExecPhi,
472
486
  withVar
473
- } from "./chunk-4V3CRVSD.js";
487
+ } from "./chunk-CKH6GQAI.js";
474
488
 
475
489
  // src/environment.ts
476
490
  function createEnvironment(parent, bindings = /* @__PURE__ */ new Map()) {
@@ -703,6 +717,252 @@ function formatDiagnostics(diags) {
703
717
  }).join("\n");
704
718
  }
705
719
 
720
+ // src/algebra/postcondition.ts
721
+ function proved() {
722
+ return { status: "proved" };
723
+ }
724
+ function disproved(reason) {
725
+ return { status: "disproved", reason };
726
+ }
727
+ function unprovable(reason) {
728
+ return { status: "unprovable", reason };
729
+ }
730
+ var RET_ANCHOR = "__nudo_return__";
731
+ function retAnchorTerm(ret) {
732
+ return ret.term ?? v(RET_ANCHOR);
733
+ }
734
+ function isValueSetUnknown(ret) {
735
+ if (ret.conf === "opaque" || ret.conf === "widened" || ret.conf === "mock") {
736
+ return true;
737
+ }
738
+ if (ret.shape.k === "unknown" && !ret.term) return true;
739
+ if (ret.shape.k === "any") return true;
740
+ return false;
741
+ }
742
+ function isBoundedEvidenceMissing(ret, constraint) {
743
+ if (constraint.preds.length === 0 && !constraint.members) return false;
744
+ if (ret.term?.op === "lit") return false;
745
+ if (ret.pred && ret.pred.op !== "true") return false;
746
+ return true;
747
+ }
748
+ function assertImplies(ret, constraint, opts) {
749
+ const phi = opts?.phi ?? pTrue;
750
+ if (ret.shape.k === "never") return proved();
751
+ if (isAnyConstraint(constraint)) return proved();
752
+ const nullishOk = constraintAdmitsNullish(constraint);
753
+ if (isNullishLitAbs(ret)) {
754
+ return nullishOk ? proved() : disproved(`nullish return \u22AD ${formatConstraint(constraint)}`);
755
+ }
756
+ let sumMembers;
757
+ if (ret.shape.k === "sum") {
758
+ const members = ret.shape.members;
759
+ const nullishMembers = members.filter((m) => isNullishLitAbs(m));
760
+ if (nullishMembers.length > 0 && !nullishOk) {
761
+ return disproved(
762
+ `nullish return arm \u22AD ${formatConstraint(constraint)} (contract does not admit nullish)`
763
+ );
764
+ }
765
+ sumMembers = members.filter((m) => !isNullishLitAbs(m));
766
+ }
767
+ if (isValueSetUnknown(ret)) {
768
+ return unprovable(
769
+ ret.shape.k === "any" ? "any does not establish the return contract" : "inference failure (unknown/opaque) \u2014 cannot prove the return contract"
770
+ );
771
+ }
772
+ if (constraint.members && constraint.members.length > 0) {
773
+ let sawUnprovable = false;
774
+ for (const m of constraint.members) {
775
+ const r = assertImplies(ret, m, opts);
776
+ if (r.status === "proved") return proved();
777
+ if (r.status === "unprovable") sawUnprovable = true;
778
+ }
779
+ return sawUnprovable ? unprovable("union: no member proved the return contract") : disproved(`return \u22AD union(${constraint.members.map((m) => formatConstraint(m)).join(", ")})`);
780
+ }
781
+ if (sumMembers && constraint.fields) {
782
+ let sawUnprovable = false;
783
+ for (const m of sumMembers) {
784
+ if (isValueSetUnknown(m)) continue;
785
+ const r = assertImplies(m, constraint, opts);
786
+ if (r.status === "disproved") return r;
787
+ if (r.status === "unprovable") sawUnprovable = true;
788
+ }
789
+ return sawUnprovable ? unprovable("sum: some arms unprovable") : proved();
790
+ }
791
+ if (constraint.fields) {
792
+ return assertShapeFields(ret, constraint, opts);
793
+ }
794
+ if (constraint.element) {
795
+ if (ret.shape.k !== "arr" && ret.shape.k !== "tuple") {
796
+ return disproved(`return shape ${ret.shape.k} \u22AD array(...)`);
797
+ }
798
+ return proved();
799
+ }
800
+ if (constraint.fn) return proved();
801
+ return assertScalar(ret, constraint, phi);
802
+ }
803
+ function isAnyConstraint(c) {
804
+ return !c.prim && c.preds.length === 0 && !c.fields && !c.element && !c.members && !c.fn && !isIntFlag(c);
805
+ }
806
+ function assertShapeFields(ret, constraint, opts) {
807
+ let slots;
808
+ if (ret.shape.k === "obj") {
809
+ slots = ret.shape.slots;
810
+ } else if (ret.shape.k === "brand") {
811
+ const inner = ret.shape.shape;
812
+ if (inner.shape.k === "obj") slots = inner.shape.slots;
813
+ }
814
+ if (!slots) {
815
+ return disproved(`return shape ${ret.shape.k} \u22AD object shape`);
816
+ }
817
+ for (const [key, field] of Object.entries(constraint.fields)) {
818
+ const slot = getSlot(slots, key);
819
+ if (!slot) {
820
+ if (!field.optional && !field.constraint.isOptional) {
821
+ return disproved(`missing field ${key}`);
822
+ }
823
+ continue;
824
+ }
825
+ const r = assertImplies(slot.value, field.constraint, opts);
826
+ if (r.status === "disproved") {
827
+ return disproved(`field ${key}: ${r.reason}`);
828
+ }
829
+ if (r.status === "unprovable") {
830
+ return unprovable(`field ${key}: ${r.reason}`);
831
+ }
832
+ }
833
+ return proved();
834
+ }
835
+ function assertScalar(ret, constraint, phi) {
836
+ if (constraint.prim) {
837
+ const retPrim = primOfAbs(ret);
838
+ if (retPrim && retPrim !== constraint.prim) {
839
+ return disproved(`typeof return = "${retPrim}" \u22AD "${constraint.prim}"`);
840
+ }
841
+ if (!retPrim && ret.shape.k !== "sum" && ret.shape.k !== "never") {
842
+ }
843
+ }
844
+ if (isIntFlag(constraint) && constraint.prim === "number") {
845
+ const lv2 = litValue(ret);
846
+ if (typeof lv2 === "number" && !Number.isInteger(lv2)) {
847
+ return disproved(`return ${lv2} \u22AD int`);
848
+ }
849
+ }
850
+ const t = retAnchorTerm(ret);
851
+ const constraintPred = instantiateOnTerm(constraint, t);
852
+ const retPred = ret.pred && ret.pred.op !== "true" ? phi.op === "true" ? ret.pred : and(phi, ret.pred) : phi;
853
+ if (constraint.preds.length === 0 && constraintPred.op === "true") {
854
+ return proved();
855
+ }
856
+ if (constraint.preds.length === 0 && constraintPred.op === "typeof" && constraint.prim) {
857
+ return proved();
858
+ }
859
+ if (constraintPred.op !== "true") {
860
+ if (implies(retPred, constraintPred)) return proved();
861
+ if (ret.pred && ret.pred.op !== "true" && numericBoundsImplyLocal(ret.pred, constraintPred)) {
862
+ return proved();
863
+ }
864
+ if (isBoundedEvidenceMissing(ret, constraint)) {
865
+ return unprovable(
866
+ `cannot prove return satisfies ${formatConstraint(constraint)} (no bound evidence on the return value)`
867
+ );
868
+ }
869
+ }
870
+ const lv = litValue(ret);
871
+ if (lv !== void 0 && (typeof lv === "number" || typeof lv === "string" || typeof lv === "boolean" || lv === null)) {
872
+ if (literalMeetsConstraint(lv, constraint)) {
873
+ return proved();
874
+ }
875
+ const boundDesc = describeBoundViolation(lv, constraint);
876
+ return disproved(
877
+ boundDesc ?? `return ${JSON.stringify(lv)} \u22AD ${formatConstraint(constraint)}`
878
+ );
879
+ }
880
+ if (ret.term?.op === "lit" && ret.term.value === void 0) {
881
+ return proved();
882
+ }
883
+ if (constraintPred.op !== "true" || constraint.preds.length > 0) {
884
+ if (isBoundedEvidenceMissing(ret, constraint)) {
885
+ return unprovable(
886
+ `cannot prove return satisfies ${formatConstraint(constraint)} (no bound evidence on the return value)`
887
+ );
888
+ }
889
+ return disproved(
890
+ `return (${formatAbs(ret)}) \u22AD ${formatConstraint(constraint)} (postcondition not implied)`
891
+ );
892
+ }
893
+ return proved();
894
+ }
895
+ function numericBoundsImplyLocal(src, tgt) {
896
+ const one = (p) => p.op === "gt" || p.op === "ge" || p.op === "lt" || p.op === "le";
897
+ if (!one(src) || !one(tgt)) return false;
898
+ if (src.op !== tgt.op) {
899
+ if (src.op === "gt" && tgt.op === "ge") {
900
+ return sameTermSide(src, tgt) && litGE(src.b, tgt.b);
901
+ }
902
+ if (src.op === "lt" && tgt.op === "le") {
903
+ return sameTermSide(src, tgt) && litLE(src.b, tgt.b);
904
+ }
905
+ return false;
906
+ }
907
+ if (!sameTermSide(src, tgt)) return false;
908
+ const sb = src.b.op === "lit" ? src.b.value : void 0;
909
+ const tb = tgt.b.op === "lit" ? tgt.b.value : void 0;
910
+ if (typeof sb !== "number" || typeof tb !== "number") return false;
911
+ if (src.op === "gt" || src.op === "ge") return sb >= tb;
912
+ return sb <= tb;
913
+ }
914
+ function sameTermSide(a, b) {
915
+ return a.a.op === b.a.op && termEquals(a.a, b.a);
916
+ }
917
+ function litGE(a, b) {
918
+ if (a.op !== "lit" || b.op !== "lit") return false;
919
+ return typeof a.value === "number" && typeof b.value === "number" && a.value >= b.value;
920
+ }
921
+ function litLE(a, b) {
922
+ if (a.op !== "lit" || b.op !== "lit") return false;
923
+ return typeof a.value === "number" && typeof b.value === "number" && a.value <= b.value;
924
+ }
925
+ function primOfAbs(a) {
926
+ if (a.shape.k === "prim") return a.shape.type;
927
+ const lv = litValue(a);
928
+ if (typeof lv === "number") return "number";
929
+ if (typeof lv === "string") return "string";
930
+ if (typeof lv === "boolean") return "boolean";
931
+ if (typeof lv === "bigint") return "bigint";
932
+ return void 0;
933
+ }
934
+ function describeBoundViolation(lv, constraint) {
935
+ const opSym = { gt: ">", ge: "\u2265", lt: "<", le: "\u2264" };
936
+ for (const p of constraint.preds) {
937
+ for (const atom of p.op === "and" ? p.args : [p]) {
938
+ if (atom.op !== "gt" && atom.op !== "ge" && atom.op !== "lt" && atom.op !== "le") continue;
939
+ if (atom.b.op !== "lit" || typeof atom.b.value !== "number") continue;
940
+ const n = atom.b.value;
941
+ const sym = opSym[atom.op];
942
+ if (typeof lv === "number") {
943
+ let ok = true;
944
+ if (atom.op === "gt") ok = lv > n;
945
+ if (atom.op === "ge") ok = lv >= n;
946
+ if (atom.op === "lt") ok = lv < n;
947
+ if (atom.op === "le") ok = lv <= n;
948
+ if (!ok) return `return ${sym} ${n}`;
949
+ }
950
+ if (typeof lv === "string" && isLengthSelfTerm(atom.a)) {
951
+ let ok = true;
952
+ if (atom.op === "gt") ok = lv.length > n;
953
+ if (atom.op === "ge") ok = lv.length >= n;
954
+ if (atom.op === "lt") ok = lv.length < n;
955
+ if (atom.op === "le") ok = lv.length <= n;
956
+ if (!ok) return `length(self) ${sym} ${n}`;
957
+ }
958
+ }
959
+ }
960
+ return void 0;
961
+ }
962
+ function isLengthSelfTerm(t) {
963
+ return t.op === "app" && t.fn === "length" && t.args.length === 1 && t.args[0].op === "var" && t.args[0].id === SELF;
964
+ }
965
+
706
966
  // src/algebra/projection.ts
707
967
  var PROJECTABLE_CONF = /* @__PURE__ */ new Set(["exact", "path"]);
708
968
  var ARR_FALLBACK_CONF = /* @__PURE__ */ new Set([
@@ -1137,8 +1397,9 @@ function estimateEntryParamCount(source, fnName, file) {
1137
1397
  }
1138
1398
  } catch {
1139
1399
  }
1400
+ const name = identBoundaryRegex(fnName);
1140
1401
  const re = new RegExp(
1141
- `(?:function\\s+${fnName}\\s*\\(([^)]*)\\)|${fnName}\\s*=\\s*(?:async\\s*)?function(?:\\s+${fnName})?\\s*\\(([^)]*)\\)|${fnName}\\s*=\\s*(?:async\\s*)?\\(([^)]*)\\)\\s*=>)`
1402
+ `(?:function\\s+${name}\\s*\\(([^)]*)\\)|${name}\\s*=\\s*(?:async\\s*)?function(?:\\s+${name})?\\s*\\(([^)]*)\\)|${name}\\s*=\\s*(?:async\\s*)?\\(([^)]*)\\)\\s*=>)`
1142
1403
  );
1143
1404
  const m = re.exec(source);
1144
1405
  if (m) {
@@ -1149,13 +1410,15 @@ function estimateEntryParamCount(source, fnName, file) {
1149
1410
  return 1;
1150
1411
  }
1151
1412
  function isDefaultExportName(source, fnName) {
1413
+ const name = identBoundaryRegex(fnName);
1152
1414
  const re = new RegExp(
1153
- `export\\s+default\\s+(?:async\\s+)?(?:function\\s+${fnName}\\b|${fnName}\\b)`
1415
+ `export\\s+default\\s+(?:async\\s+)?(?:function\\s+${name}|${name})`
1154
1416
  );
1155
1417
  return re.test(source);
1156
1418
  }
1157
1419
  function formatEntrySigLine(name, g, throws) {
1158
- const ps = g.params.map((p, i) => {
1420
+ const names = g.formals && g.formals.length > 0 ? formalParamSignatureNames(g.formals) : g.params;
1421
+ const ps = names.map((p, i) => {
1159
1422
  const t = g.typeParams[i]?.value;
1160
1423
  const shown = !t ? "any" : t.shape.k === "unknown" ? "unknown" : formatShape(t);
1161
1424
  return `${p}: ${shown}`;
@@ -1219,6 +1482,7 @@ function resetCheckSourceMemo() {
1219
1482
  checkKeyDeps.clear();
1220
1483
  checkDepIndex.clear();
1221
1484
  resetHashSourceCache();
1485
+ resetSidecarLoadFailureCache();
1222
1486
  }
1223
1487
  function loadModuleId(fn2) {
1224
1488
  if (!fn2) return 0;
@@ -1525,8 +1789,9 @@ function projectCaseArgField(arg, field) {
1525
1789
  }
1526
1790
  function scanCaseInconsistency(source, knownFns, opts) {
1527
1791
  const out = [];
1528
- if (!source.includes("@nudo:case")) return out;
1529
- const hasContractOrigin = source.includes("@nudo:contract") || source.includes("@nudo:contract") || opts.sidecarPresent === true;
1792
+ const directiveSrc = stripStringsKeepComments(source);
1793
+ if (!directiveSrc.includes("@nudo:case")) return out;
1794
+ const hasContractOrigin = directiveSrc.includes("@nudo:contract") || opts.sidecarPresent === true;
1530
1795
  if (!hasContractOrigin) return out;
1531
1796
  const file = opts.file ?? parseSource(source);
1532
1797
  const parseLitArg = (s) => {
@@ -1749,6 +2014,7 @@ function checkSource(filePath, source, phi = pTrue, opts = {}) {
1749
2014
  }
1750
2015
  setRefineDiagCollector(null);
1751
2016
  setInterfaceDiagCollector(null);
2017
+ resetSidecarLoadFailureCache();
1752
2018
  const refineSince = refineDiagCount();
1753
2019
  const ifaceSince = interfaceDiagCount();
1754
2020
  const useMemo = phi.op === "true";
@@ -1824,7 +2090,7 @@ function sidecarDiagIssues(diags) {
1824
2090
  return out;
1825
2091
  }
1826
2092
  function checkSourceInner(filePath, source, file, phi, opts, issues, signatures, names, truncated, identityOpts = opts, depsFp, sidecarFp) {
1827
- const hasRefineDirective = source.includes("@nudo:contract") || source.includes("@nudo:contract");
2093
+ const hasRefineDirective = stripStringsKeepComments(source).includes("@nudo:contract");
1828
2094
  const exportedNames = sidecarFp !== void 0 ? localNamedExports(source) : void 0;
1829
2095
  const entryNames = localNamedExports(source);
1830
2096
  const entryThrowsMode = opts.entryThrows ?? "error";
@@ -1889,7 +2155,7 @@ function checkSourceInner(filePath, source, file, phi, opts, issues, signatures,
1889
2155
  eff.returns.constraint,
1890
2156
  declared
1891
2157
  );
1892
- if (retIssues.length > 0) returnViolated.add(name);
2158
+ if (retIssues.some((i) => i.severity === "error")) returnViolated.add(name);
1893
2159
  issues.push(...retIssues);
1894
2160
  }
1895
2161
  continue;
@@ -1935,7 +2201,7 @@ function checkSourceInner(filePath, source, file, phi, opts, issues, signatures,
1935
2201
  message: `${name} (export): may throw ${gateDisplay}`,
1936
2202
  actual: `${name}(\u2026) throws ${gateDisplay}`,
1937
2203
  expected: "entry total, or @nudo:throws / try-catch",
1938
- suggestion: first.cause ? `${first.cause} \u2192 ${first.kind === "ReferenceError" ? "re-export `export { x } from` \u4E0D\u662F\u5C40\u90E8\u7ED1\u5B9A\u2014\u2014\u6539 `import { x }` / @nudo:throws ReferenceError" : `@nudo:throws ${first.kind} / refine / guard / try-catch`}` : first.kind === "ReferenceError" ? "re-export `export { x } from` \u4E0D\u662F\u5C40\u90E8\u7ED1\u5B9A\u2014\u2014\u6539 `import { x }` / @nudo:throws ReferenceError" : `@nudo:throws ${first.kind} / refine / guard / try-catch`,
2204
+ suggestion: first.cause ? `${first.cause} \u2192 ${first.kind === "ReferenceError" ? "re-export `export { x } from` \u4E0D\u662F\u5C40\u90E8\u7ED1\u5B9A\u2014\u2014\u6539 `import { x }` / @nudo:throws ReferenceError" : `@nudo:throws ${first.kind} / sidecar fn({ \u2026 }) contract / refine / guard / try-catch`}` : first.kind === "ReferenceError" ? "re-export `export { x } from` \u4E0D\u662F\u5C40\u90E8\u7ED1\u5B9A\u2014\u2014\u6539 `import { x }` / @nudo:throws ReferenceError" : `@nudo:throws ${first.kind} / sidecar fn({ \u2026 }) contract / refine / guard / try-catch`,
1939
2205
  fn: name,
1940
2206
  ...loc.line !== void 0 ? { line: loc.line } : {},
1941
2207
  ...loc.column !== void 0 ? { column: loc.column } : {}
@@ -1980,7 +2246,7 @@ function checkSourceInner(filePath, source, file, phi, opts, issues, signatures,
1980
2246
  message: `${name} (export): may throw ${gateDisplay}`,
1981
2247
  actual: `${formatEntrySigLine(name, g, gateDisplay)}`,
1982
2248
  expected: "entry total, or @nudo:throws / try-catch",
1983
- suggestion: first.cause ? `${first.cause} \u2192 ${first.kind === "ReferenceError" ? "re-export `export { x } from` \u4E0D\u662F\u5C40\u90E8\u7ED1\u5B9A\u2014\u2014\u6539 `import { x }` / @nudo:throws ReferenceError" : `@nudo:throws ${first.kind} / refine / guard / try-catch`}` : first.kind === "ReferenceError" ? "re-export `export { x } from` \u4E0D\u662F\u5C40\u90E8\u7ED1\u5B9A\u2014\u2014\u6539 `import { x }` / @nudo:throws ReferenceError" : `@nudo:throws ${first.kind} / refine / guard / try-catch`,
2249
+ suggestion: first.cause ? `${first.cause} \u2192 ${first.kind === "ReferenceError" ? "re-export `export { x } from` \u4E0D\u662F\u5C40\u90E8\u7ED1\u5B9A\u2014\u2014\u6539 `import { x }` / @nudo:throws ReferenceError" : `@nudo:throws ${first.kind} / sidecar fn({ \u2026 }) contract / refine / guard / try-catch`}` : first.kind === "ReferenceError" ? "re-export `export { x } from` \u4E0D\u662F\u5C40\u90E8\u7ED1\u5B9A\u2014\u2014\u6539 `import { x }` / @nudo:throws ReferenceError" : `@nudo:throws ${first.kind} / sidecar fn({ \u2026 }) contract / refine / guard / try-catch`,
1984
2250
  fn: name,
1985
2251
  ...loc.line !== void 0 ? { line: loc.line } : {},
1986
2252
  ...loc.column !== void 0 ? { column: loc.column } : {}
@@ -1998,7 +2264,8 @@ function checkSourceInner(filePath, source, file, phi, opts, issues, signatures,
1998
2264
  });
1999
2265
  signatures.push({
2000
2266
  name,
2001
- params: g.params,
2267
+ // 解构形参展示 `{ grade, findings }`,不落 `_p0`(#64:契约要上屏)
2268
+ params: g.formals && g.formals.length > 0 ? formalParamSignatureNames(g.formals) : g.params,
2002
2269
  paramTypes,
2003
2270
  abs: g.symbolic,
2004
2271
  display: throwsDisplay ? `${fmt.display} throws ${throwsDisplay}` : fmt.display,
@@ -2078,7 +2345,7 @@ function checkSourceInner(filePath, source, file, phi, opts, issues, signatures,
2078
2345
  eff.returns.constraint,
2079
2346
  g.symbolic
2080
2347
  );
2081
- if (retIssues.length > 0) returnViolated.add(name);
2348
+ if (retIssues.some((i) => i.severity === "error")) returnViolated.add(name);
2082
2349
  issues.push(...retIssues);
2083
2350
  }
2084
2351
  if (eff && eff.source === "generated") {
@@ -2105,7 +2372,25 @@ function checkSourceInner(filePath, source, file, phi, opts, issues, signatures,
2105
2372
  severity: "info",
2106
2373
  code: "nudo:fork-truncated",
2107
2374
  message: `Branch expansion was truncated (fork budget); affected results widened to unknown#opaque (budget)`,
2108
- suggestion: "optional: simplify branching or raise nudo.analysis.maxForks \u2014 non-blocking"
2375
+ suggestion: "optional: raise nudo.analysis.maxForks, or @nudo:budget forks=\u2026 on the function \u2014 non-blocking"
2376
+ });
2377
+ continue;
2378
+ }
2379
+ if (label === PROMISE_MICRO_OVERFLOW_LABEL) {
2380
+ issues.push({
2381
+ severity: "info",
2382
+ code: "nudo:promise-micro-truncated",
2383
+ message: `Promise microtask queue hit its hard cap; further then/catch callbacks were dropped (results stay fail-closed)`,
2384
+ suggestion: "optional: reduce top-level Promise.then fan-out \u2014 non-blocking"
2385
+ });
2386
+ continue;
2387
+ }
2388
+ if (label === PROMISE_MICRO_ERROR_LABEL) {
2389
+ issues.push({
2390
+ severity: "info",
2391
+ code: "nudo:promise-micro-error",
2392
+ message: `A promise microtask threw while draining; the error was recorded and did not rewrite the synchronous return value`,
2393
+ suggestion: "optional: inspect the then/catch callback for a throw path \u2014 non-blocking"
2109
2394
  });
2110
2395
  continue;
2111
2396
  }
@@ -2124,7 +2409,7 @@ function checkSourceInner(filePath, source, file, phi, opts, issues, signatures,
2124
2409
  severity: "info",
2125
2410
  code: "nudo:recursion-truncated",
2126
2411
  message: `Recursive evaluation of '${label}' was truncated (depth/size budget); result widened to unknown#opaque (budget \u2014 not inference debt)`,
2127
- suggestion: "narrow the recursion base case or declare an explicit @nudo:contract return contract",
2412
+ suggestion: "raise the function budget with @nudo:budget calls=\u2026 depth=\u2026 (or narrow the recursion base case / declare @nudo:contract return)",
2128
2413
  fn: label
2129
2414
  });
2130
2415
  }
@@ -2187,7 +2472,7 @@ function checkSourceInner(filePath, source, file, phi, opts, issues, signatures,
2187
2472
  rc.constraint,
2188
2473
  rec.result
2189
2474
  );
2190
- if (retIssues.length > 0) returnViolated.add(rec.fnName);
2475
+ if (retIssues.some((i) => i.severity === "error")) returnViolated.add(rec.fnName);
2191
2476
  issues.push(...retIssues);
2192
2477
  }
2193
2478
  }
@@ -2246,118 +2531,33 @@ function checkSourceInner(filePath, source, file, phi, opts, issues, signatures,
2246
2531
  };
2247
2532
  }
2248
2533
  function checkReturnConstraint(fnName, cName, constraint, ret) {
2249
- const out = [];
2250
- if (ret.shape.k === "unknown" && !ret.term) return out;
2251
- if (ret.shape.k === "any") return out;
2252
- if (isNullishLitAbs(ret)) return out;
2253
- if (ret.shape.k === "sum" && constraint.fields) {
2254
- const seen = /* @__PURE__ */ new Set();
2255
- for (const m of ret.shape.members) {
2256
- for (const issue of checkReturnConstraint(fnName, cName, constraint, m)) {
2257
- const key = `${issue.message}\0${issue.actual ?? ""}\0${issue.expected ?? ""}`;
2258
- if (seen.has(key)) continue;
2259
- seen.add(key);
2260
- out.push(issue);
2261
- }
2262
- }
2263
- return out;
2264
- }
2265
- const push = (actual, expected, suggestion) => {
2266
- out.push({
2267
- severity: "error",
2268
- code: "nudo:constraint-violated",
2534
+ const proof = assertImplies(ret, constraint);
2535
+ if (proof.status === "proved") return [];
2536
+ const push = (severity, actual, expected, suggestion) => [
2537
+ {
2538
+ severity,
2539
+ code: severity === "error" ? "nudo:constraint-violated" : "nudo:unproven-return",
2269
2540
  message: `${fnName}: return value \u22AD @nudo:contract return ${cName}`,
2270
2541
  actual,
2271
2542
  expected,
2272
2543
  suggestion,
2273
2544
  fn: fnName
2274
- });
2275
- };
2276
- if (constraint.fields) {
2277
- const slots = ret.shape.k === "obj" ? ret.shape.slots : void 0;
2278
- if (!slots) {
2279
- if (ret.shape.k !== "never") {
2280
- push(formatAbs(ret), `object shape (${cName})`, `return an object satisfying the ${cName} shape`);
2281
- }
2282
- return out;
2283
2545
  }
2284
- for (const [key, field] of Object.entries(constraint.fields)) {
2285
- const slot = getSlot(slots, key);
2286
- if (!slot) {
2287
- if (!field.optional && !field.constraint.isOptional) {
2288
- push(formatAbs(ret), `missing field ${key}`, `add the missing field ${key} to the return value`);
2289
- }
2290
- continue;
2291
- }
2292
- if (field.constraint.prim && slot.value.shape.k === "prim") {
2293
- const actualPrim = slot.value.shape.type;
2294
- if (actualPrim !== field.constraint.prim) {
2295
- push(
2296
- formatAbs(slot.value),
2297
- `typeof ${key} = "${field.constraint.prim}"`,
2298
- `change the return value's ${key} to ${field.constraint.prim}`
2299
- );
2300
- continue;
2301
- }
2302
- }
2303
- const lv2 = litValue(slot.value);
2304
- if (lv2 !== void 0 && typeof lv2 === "number") {
2305
- for (const p of field.constraint.preds) {
2306
- const flat = p.op === "and" ? p.args : [p];
2307
- for (const atom of flat) {
2308
- if ((atom.op === "gt" || atom.op === "ge" || atom.op === "lt" || atom.op === "le") && atom.b.op === "lit" && typeof atom.b.value === "number") {
2309
- const n = atom.b.value;
2310
- let ok = true;
2311
- if (atom.op === "gt") ok = lv2 > n;
2312
- if (atom.op === "ge") ok = lv2 >= n;
2313
- if (atom.op === "lt") ok = lv2 < n;
2314
- if (atom.op === "le") ok = lv2 <= n;
2315
- if (!ok) {
2316
- const opSym = { gt: ">", ge: "\u2265", lt: "<", le: "\u2264" }[atom.op];
2317
- push(
2318
- formatAbs(slot.value),
2319
- `${key} ${opSym} ${n}`,
2320
- `the return value's ${key} should satisfy ${key} ${opSym} ${n}`
2321
- );
2322
- }
2323
- }
2324
- }
2325
- }
2326
- }
2327
- }
2328
- return out;
2329
- }
2330
- if (constraint.prim && ret.shape.k === "prim") {
2331
- const actualPrim = ret.shape.type;
2332
- if (actualPrim !== constraint.prim) {
2333
- push(formatAbs(ret), `typeof return = "${constraint.prim}"`, `return ${constraint.prim}`);
2334
- return out;
2335
- }
2336
- }
2337
- const lv = litValue(ret);
2338
- if (lv !== void 0 && typeof lv === "number") {
2339
- for (const p of constraint.preds) {
2340
- const flat = p.op === "and" ? p.args : [p];
2341
- for (const atom of flat) {
2342
- if ((atom.op === "gt" || atom.op === "ge" || atom.op === "lt" || atom.op === "le") && atom.b.op === "lit" && typeof atom.b.value === "number") {
2343
- const n = atom.b.value;
2344
- let ok = true;
2345
- if (atom.op === "gt") ok = lv > n;
2346
- if (atom.op === "ge") ok = lv >= n;
2347
- if (atom.op === "lt") ok = lv < n;
2348
- if (atom.op === "le") ok = lv <= n;
2349
- if (!ok) {
2350
- const opSym = { gt: ">", ge: "\u2265", lt: "<", le: "\u2264" }[atom.op];
2351
- push(formatAbs(ret), `return ${opSym} ${n}`, `return a value satisfying ${opSym} ${n}`);
2352
- }
2353
- }
2354
- }
2355
- }
2356
- }
2357
- 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") || typeof lv === "string" && constraint.preds.length > 0) && !literalMeetsConstraint(lv, constraint)) {
2358
- push(formatAbs(ret), formatConstraint(constraint), `return a value satisfying ${formatConstraint(constraint)}`);
2546
+ ];
2547
+ if (proof.status === "disproved") {
2548
+ return push(
2549
+ "error",
2550
+ formatAbs(ret),
2551
+ proof.reason,
2552
+ proof.reason.includes("nullish") ? `use nullable(${formatConstraint(constraint)}) or union(..., lit(null)) to allow nullish returns` : `return a value satisfying ${formatConstraint(constraint)}`
2553
+ );
2359
2554
  }
2360
- return out;
2555
+ return push(
2556
+ "warning",
2557
+ formatAbs(ret),
2558
+ formatConstraint(constraint),
2559
+ `${proof.reason}; add a precondition, narrow the return expression, or relax the return contract`
2560
+ );
2361
2561
  }
2362
2562
 
2363
2563
  // src/algebra/check-report.ts
@@ -2393,6 +2593,12 @@ function actionsForIssue(i) {
2393
2593
  ];
2394
2594
  case "nudo:entry-may-throw":
2395
2595
  return [
2596
+ {
2597
+ kind: "draft",
2598
+ command: "nudo contract --draft",
2599
+ label: "narrow the entry with a sidecar fn({ \u2026 }) param contract \u2014 most honest fix for helpers (also tightens the signature)",
2600
+ ...i.fn ? { hint: i.fn } : {}
2601
+ },
2396
2602
  {
2397
2603
  kind: "relax",
2398
2604
  label: "declare intentional fail-fast: @nudo:throws Error (or case `!! throws`)",
@@ -2406,8 +2612,7 @@ function actionsForIssue(i) {
2406
2612
  kind: "ignore-throws",
2407
2613
  command: "nudo check --ignore-throws TypeError",
2408
2614
  label: "migration switch: ignore this throw class on L2"
2409
- },
2410
- draft
2615
+ }
2411
2616
  ];
2412
2617
  case "nudo:unknown-inference":
2413
2618
  case "nudo:opaque-result":
@@ -2424,10 +2629,12 @@ function actionsForIssue(i) {
2424
2629
  ];
2425
2630
  case "nudo:fork-truncated":
2426
2631
  case "nudo:recursion-truncated":
2632
+ case "nudo:promise-micro-truncated":
2633
+ case "nudo:promise-micro-error":
2427
2634
  return [
2428
2635
  {
2429
2636
  kind: "info",
2430
- label: "results widened to unknown \u2014 raise budget or narrow control flow"
2637
+ label: "results widened to unknown \u2014 raise budget (@nudo:budget forks/calls/depth on the function) or narrow control flow"
2431
2638
  }
2432
2639
  ];
2433
2640
  case "nudo:host-effect-blocked":
@@ -2661,107 +2868,6 @@ function formatGitlabCodeQuality(r, opts = {}) {
2661
2868
  };
2662
2869
  });
2663
2870
  }
2664
-
2665
- // src/algebra/abs-modules.ts
2666
- function bindImports(node, env, modules) {
2667
- const mod2 = modules[node.source.value];
2668
- if (!mod2) {
2669
- for (const s of node.specifiers) {
2670
- env.vars.set(s.local.name, unknown);
2671
- }
2672
- return;
2673
- }
2674
- for (const s of node.specifiers) {
2675
- if (s.type === "ImportDefaultSpecifier") {
2676
- env.vars.set(s.local.name, mod2.default ?? unknown);
2677
- } else if (s.type === "ImportSpecifier") {
2678
- const imported = s.imported.type === "Identifier" ? s.imported.name : String(s.imported);
2679
- env.vars.set(s.local.name, mod2.named[imported] ?? unknown);
2680
- } else if (s.type === "ImportNamespaceSpecifier") {
2681
- const slots = {};
2682
- for (const [k, v2] of Object.entries(mod2.named)) slots[k] = { value: v2 };
2683
- if (mod2.default) slots["default"] = { value: mod2.default };
2684
- env.vars.set(s.local.name, obj(slots));
2685
- }
2686
- }
2687
- }
2688
- function fnToAbs(env, name) {
2689
- const impl = env.fns.get(name);
2690
- if (!impl) return void 0;
2691
- return absFunction(impl.params, {
2692
- body: impl.body,
2693
- async: impl.async,
2694
- env
2695
- });
2696
- }
2697
- function lookupExport(env, name) {
2698
- if (env.vars.has(name)) return env.vars.get(name);
2699
- return fnToAbs(env, name);
2700
- }
2701
- function collectAbsExports(file, env, modules) {
2702
- const named = {};
2703
- let defaultExport;
2704
- for (const stmt of file.program.body) {
2705
- if (stmt.type === "ExportNamedDeclaration") {
2706
- if (stmt.source && modules) {
2707
- const mod2 = modules[stmt.source.value];
2708
- if (mod2) {
2709
- for (const spec of stmt.specifiers) {
2710
- if (spec.type !== "ExportSpecifier") continue;
2711
- const local = spec.local.type === "Identifier" ? spec.local.name : spec.local.value;
2712
- const exported = spec.exported.type === "Identifier" ? spec.exported.name : spec.exported.value;
2713
- const v2 = local === "default" ? mod2.default : mod2.named[local];
2714
- if (v2 === void 0) continue;
2715
- if (exported === "default") defaultExport = v2;
2716
- else named[exported] = v2;
2717
- }
2718
- }
2719
- continue;
2720
- }
2721
- const decl = stmt.declaration;
2722
- if (decl) {
2723
- if (decl.type === "FunctionDeclaration" && decl.id) {
2724
- const v2 = lookupExport(env, decl.id.name);
2725
- if (v2) named[decl.id.name] = v2;
2726
- } else if (decl.type === "ClassDeclaration" && decl.id) {
2727
- const v2 = lookupExport(env, decl.id.name);
2728
- if (v2) named[decl.id.name] = v2;
2729
- } else if (decl.type === "VariableDeclaration") {
2730
- for (const d of decl.declarations) {
2731
- if (d.id.type === "Identifier") {
2732
- const v2 = lookupExport(env, d.id.name);
2733
- if (v2) named[d.id.name] = v2;
2734
- }
2735
- }
2736
- }
2737
- }
2738
- for (const spec of stmt.specifiers) {
2739
- if (spec.type !== "ExportSpecifier") continue;
2740
- const local = spec.local.type === "Identifier" ? spec.local.name : spec.local.value;
2741
- const exported = spec.exported.type === "Identifier" ? spec.exported.name : spec.exported.value;
2742
- const v2 = lookupExport(env, local);
2743
- if (v2) named[exported] = v2;
2744
- }
2745
- } else if (stmt.type === "ExportAllDeclaration" && stmt.source && modules) {
2746
- const mod2 = modules[stmt.source.value];
2747
- if (mod2?.named) {
2748
- for (const [k, v2] of Object.entries(mod2.named)) named[k] = v2;
2749
- }
2750
- } else if (stmt.type === "ExportDefaultDeclaration") {
2751
- const d = stmt.declaration;
2752
- if (d.type === "FunctionDeclaration") {
2753
- defaultExport = lookupExport(env, d.id ? d.id.name : "default");
2754
- } else if (d.type === "ClassDeclaration" && d.id) {
2755
- defaultExport = lookupExport(env, d.id.name);
2756
- } else if (d.type === "Identifier") {
2757
- defaultExport = lookupExport(env, d.name);
2758
- }
2759
- }
2760
- }
2761
- const result = { named };
2762
- if (defaultExport) result.default = defaultExport;
2763
- return result;
2764
- }
2765
2871
  export {
2766
2872
  $add,
2767
2873
  $arguments,
@@ -2905,6 +3011,7 @@ export {
2905
3011
  asAbs,
2906
3012
  asAbsVal,
2907
3013
  assignSourceSlots,
3014
+ assumeFinite,
2908
3015
  attachFnImpl,
2909
3016
  beginCollectionFork,
2910
3017
  betaOf,
@@ -2938,6 +3045,7 @@ export {
2938
3045
  collectionElementJoin,
2939
3046
  collectionExactLen,
2940
3047
  confJoin,
3048
+ constraintAdmitsNullish,
2941
3049
  constraintToEntryAbs,
2942
3050
  contractParamNameSet,
2943
3051
  createEnvironment,
@@ -2984,6 +3092,7 @@ export {
2984
3092
  extStateOf,
2985
3093
  extractDeclaredThrows,
2986
3094
  extractFn,
3095
+ extractFnBudget,
2987
3096
  extractNudoImports,
2988
3097
  extractRefineReturnFromSource,
2989
3098
  extractRefinesFromSource,
@@ -2995,6 +3104,7 @@ export {
2995
3104
  foldRequireSpecArg,
2996
3105
  foldStaticStringExpr,
2997
3106
  formalParamDisplayNames,
3107
+ formalParamSignatureNames,
2998
3108
  formalParamsFromNodes,
2999
3109
  formatAbs,
3000
3110
  formatAbsMultiline,
@@ -3100,6 +3210,7 @@ export {
3100
3210
  mock,
3101
3211
  mod,
3102
3212
  mul,
3213
+ namespaceAbsOf,
3103
3214
  namespaceNameOf,
3104
3215
  ne,
3105
3216
  negAbs,
@@ -3111,6 +3222,7 @@ export {
3111
3222
  noteEvalCallRecord,
3112
3223
  noteEvalFallback,
3113
3224
  notePromiseExecutorFork,
3225
+ nullable,
3114
3226
  num,
3115
3227
  numLit,
3116
3228
  numVar,
@@ -3160,6 +3272,7 @@ export {
3160
3272
  resetNudoModuleExecCache,
3161
3273
  resetParseSourceCache,
3162
3274
  resetPhi,
3275
+ resetSidecarLoadFailureCache,
3163
3276
  runTranspiled,
3164
3277
  runTranspiledOptionsMemoKey,
3165
3278
  runWithLoopExits,