@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.
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-GD5XIQQK.js";
75
+ } from "./chunk-5YNR4Q7E.js";
71
76
  import {
72
77
  $add,
73
78
  $arguments,
@@ -160,6 +165,7 @@ import {
160
165
  $thisSet,
161
166
  $throw,
162
167
  $toNumber,
168
+ $toNumeric,
163
169
  $tryCurrentMark,
164
170
  $tryDetachSoftCatch,
165
171
  $tryDigestSoftCatch,
@@ -172,6 +178,8 @@ import {
172
178
  $tryTakeSince,
173
179
  $typeof,
174
180
  $unknown,
181
+ $updateAdd,
182
+ $updateSub,
175
183
  $ushr,
176
184
  $while,
177
185
  $whileSeq,
@@ -186,6 +194,8 @@ import {
186
194
  NudoThrow,
187
195
  NudoUnsupportedError,
188
196
  OBJECT_PROTO_METHOD_NAMES,
197
+ PROMISE_MICRO_ERROR_LABEL,
198
+ PROMISE_MICRO_OVERFLOW_LABEL,
189
199
  RUNTIME_IMPORT_RE,
190
200
  TYPEOF_NAMES,
191
201
  abs,
@@ -203,10 +213,12 @@ import {
203
213
  asAbs,
204
214
  asAbsVal,
205
215
  assignSourceSlots,
216
+ assumeFinite,
206
217
  attachFnImpl,
207
218
  beginCollectionFork,
208
219
  betaOf,
209
220
  bigintLit,
221
+ bindImports,
210
222
  bindingsOf,
211
223
  bitandAbs,
212
224
  bitnotAbs,
@@ -225,6 +237,7 @@ import {
225
237
  clearCollectionTables,
226
238
  clearStaleTermPred,
227
239
  cmp,
240
+ collectAbsExports,
228
241
  collectionElementJoin,
229
242
  collectionExactLen,
230
243
  confJoin,
@@ -272,6 +285,7 @@ import {
272
285
  foldRequireSpecArg,
273
286
  foldStaticStringExpr,
274
287
  formalParamDisplayNames,
288
+ formalParamSignatureNames,
275
289
  formalParamsFromNodes,
276
290
  formatAbs,
277
291
  formatAbsMultiline,
@@ -292,6 +306,7 @@ import {
292
306
  gt,
293
307
  gtNum,
294
308
  hostBuiltinCtorName,
309
+ identBoundaryRegex,
295
310
  implies,
296
311
  instantiateReturn,
297
312
  isArrMutator,
@@ -354,6 +369,7 @@ import {
354
369
  migrateNullProto,
355
370
  mod,
356
371
  mul,
372
+ namespaceAbsOf,
357
373
  namespaceNameOf,
358
374
  ne,
359
375
  negAbs,
@@ -385,6 +401,7 @@ import {
385
401
  primToTypeof,
386
402
  projectFlatMapResult,
387
403
  promoteParamShape,
404
+ propertyKeyOf,
388
405
  protoBrandAbs,
389
406
  protoOfRecv,
390
407
  ptypeof,
@@ -434,6 +451,7 @@ import {
434
451
  strLit,
435
452
  strictEqAbs,
436
453
  stringOfSymbol,
454
+ stripStringsKeepComments,
437
455
  stripTypes,
438
456
  sub,
439
457
  substAbs,
@@ -466,7 +484,7 @@ import {
466
484
  v,
467
485
  withExecPhi,
468
486
  withVar
469
- } from "./chunk-WB565PQK.js";
487
+ } from "./chunk-CKH6GQAI.js";
470
488
 
471
489
  // src/environment.ts
472
490
  function createEnvironment(parent, bindings = /* @__PURE__ */ new Map()) {
@@ -699,6 +717,252 @@ function formatDiagnostics(diags) {
699
717
  }).join("\n");
700
718
  }
701
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
+
702
966
  // src/algebra/projection.ts
703
967
  var PROJECTABLE_CONF = /* @__PURE__ */ new Set(["exact", "path"]);
704
968
  var ARR_FALLBACK_CONF = /* @__PURE__ */ new Set([
@@ -1133,8 +1397,9 @@ function estimateEntryParamCount(source, fnName, file) {
1133
1397
  }
1134
1398
  } catch {
1135
1399
  }
1400
+ const name = identBoundaryRegex(fnName);
1136
1401
  const re = new RegExp(
1137
- `(?: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*=>)`
1138
1403
  );
1139
1404
  const m = re.exec(source);
1140
1405
  if (m) {
@@ -1145,13 +1410,15 @@ function estimateEntryParamCount(source, fnName, file) {
1145
1410
  return 1;
1146
1411
  }
1147
1412
  function isDefaultExportName(source, fnName) {
1413
+ const name = identBoundaryRegex(fnName);
1148
1414
  const re = new RegExp(
1149
- `export\\s+default\\s+(?:async\\s+)?(?:function\\s+${fnName}\\b|${fnName}\\b)`
1415
+ `export\\s+default\\s+(?:async\\s+)?(?:function\\s+${name}|${name})`
1150
1416
  );
1151
1417
  return re.test(source);
1152
1418
  }
1153
1419
  function formatEntrySigLine(name, g, throws) {
1154
- 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) => {
1155
1422
  const t = g.typeParams[i]?.value;
1156
1423
  const shown = !t ? "any" : t.shape.k === "unknown" ? "unknown" : formatShape(t);
1157
1424
  return `${p}: ${shown}`;
@@ -1215,6 +1482,7 @@ function resetCheckSourceMemo() {
1215
1482
  checkKeyDeps.clear();
1216
1483
  checkDepIndex.clear();
1217
1484
  resetHashSourceCache();
1485
+ resetSidecarLoadFailureCache();
1218
1486
  }
1219
1487
  function loadModuleId(fn2) {
1220
1488
  if (!fn2) return 0;
@@ -1465,8 +1733,11 @@ function widenForAssign(a) {
1465
1733
  a.conf
1466
1734
  );
1467
1735
  }
1468
- const lv = litValue(a);
1469
- if (lv !== void 0 && a.term?.op === "lit") {
1736
+ if (a.term?.op === "lit") {
1737
+ const lv = a.term.value;
1738
+ if (lv === null || lv === void 0) {
1739
+ return abs({ k: "unknown" }, void 0, void 0, a.conf);
1740
+ }
1470
1741
  if (typeof lv === "number") {
1471
1742
  return abs({ k: "prim", type: "number" }, void 0, void 0, a.conf);
1472
1743
  }
@@ -1518,8 +1789,9 @@ function projectCaseArgField(arg, field) {
1518
1789
  }
1519
1790
  function scanCaseInconsistency(source, knownFns, opts) {
1520
1791
  const out = [];
1521
- if (!source.includes("@nudo:case")) return out;
1522
- 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;
1523
1795
  if (!hasContractOrigin) return out;
1524
1796
  const file = opts.file ?? parseSource(source);
1525
1797
  const parseLitArg = (s) => {
@@ -1742,6 +2014,7 @@ function checkSource(filePath, source, phi = pTrue, opts = {}) {
1742
2014
  }
1743
2015
  setRefineDiagCollector(null);
1744
2016
  setInterfaceDiagCollector(null);
2017
+ resetSidecarLoadFailureCache();
1745
2018
  const refineSince = refineDiagCount();
1746
2019
  const ifaceSince = interfaceDiagCount();
1747
2020
  const useMemo = phi.op === "true";
@@ -1817,7 +2090,7 @@ function sidecarDiagIssues(diags) {
1817
2090
  return out;
1818
2091
  }
1819
2092
  function checkSourceInner(filePath, source, file, phi, opts, issues, signatures, names, truncated, identityOpts = opts, depsFp, sidecarFp) {
1820
- const hasRefineDirective = source.includes("@nudo:contract") || source.includes("@nudo:contract");
2093
+ const hasRefineDirective = stripStringsKeepComments(source).includes("@nudo:contract");
1821
2094
  const exportedNames = sidecarFp !== void 0 ? localNamedExports(source) : void 0;
1822
2095
  const entryNames = localNamedExports(source);
1823
2096
  const entryThrowsMode = opts.entryThrows ?? "error";
@@ -1882,7 +2155,7 @@ function checkSourceInner(filePath, source, file, phi, opts, issues, signatures,
1882
2155
  eff.returns.constraint,
1883
2156
  declared
1884
2157
  );
1885
- if (retIssues.length > 0) returnViolated.add(name);
2158
+ if (retIssues.some((i) => i.severity === "error")) returnViolated.add(name);
1886
2159
  issues.push(...retIssues);
1887
2160
  }
1888
2161
  continue;
@@ -1928,7 +2201,7 @@ function checkSourceInner(filePath, source, file, phi, opts, issues, signatures,
1928
2201
  message: `${name} (export): may throw ${gateDisplay}`,
1929
2202
  actual: `${name}(\u2026) throws ${gateDisplay}`,
1930
2203
  expected: "entry total, or @nudo:throws / try-catch",
1931
- 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`,
1932
2205
  fn: name,
1933
2206
  ...loc.line !== void 0 ? { line: loc.line } : {},
1934
2207
  ...loc.column !== void 0 ? { column: loc.column } : {}
@@ -1973,7 +2246,7 @@ function checkSourceInner(filePath, source, file, phi, opts, issues, signatures,
1973
2246
  message: `${name} (export): may throw ${gateDisplay}`,
1974
2247
  actual: `${formatEntrySigLine(name, g, gateDisplay)}`,
1975
2248
  expected: "entry total, or @nudo:throws / try-catch",
1976
- 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`,
1977
2250
  fn: name,
1978
2251
  ...loc.line !== void 0 ? { line: loc.line } : {},
1979
2252
  ...loc.column !== void 0 ? { column: loc.column } : {}
@@ -1991,7 +2264,8 @@ function checkSourceInner(filePath, source, file, phi, opts, issues, signatures,
1991
2264
  });
1992
2265
  signatures.push({
1993
2266
  name,
1994
- params: g.params,
2267
+ // 解构形参展示 `{ grade, findings }`,不落 `_p0`(#64:契约要上屏)
2268
+ params: g.formals && g.formals.length > 0 ? formalParamSignatureNames(g.formals) : g.params,
1995
2269
  paramTypes,
1996
2270
  abs: g.symbolic,
1997
2271
  display: throwsDisplay ? `${fmt.display} throws ${throwsDisplay}` : fmt.display,
@@ -2071,7 +2345,7 @@ function checkSourceInner(filePath, source, file, phi, opts, issues, signatures,
2071
2345
  eff.returns.constraint,
2072
2346
  g.symbolic
2073
2347
  );
2074
- if (retIssues.length > 0) returnViolated.add(name);
2348
+ if (retIssues.some((i) => i.severity === "error")) returnViolated.add(name);
2075
2349
  issues.push(...retIssues);
2076
2350
  }
2077
2351
  if (eff && eff.source === "generated") {
@@ -2098,7 +2372,25 @@ function checkSourceInner(filePath, source, file, phi, opts, issues, signatures,
2098
2372
  severity: "info",
2099
2373
  code: "nudo:fork-truncated",
2100
2374
  message: `Branch expansion was truncated (fork budget); affected results widened to unknown#opaque (budget)`,
2101
- 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"
2102
2394
  });
2103
2395
  continue;
2104
2396
  }
@@ -2117,7 +2409,7 @@ function checkSourceInner(filePath, source, file, phi, opts, issues, signatures,
2117
2409
  severity: "info",
2118
2410
  code: "nudo:recursion-truncated",
2119
2411
  message: `Recursive evaluation of '${label}' was truncated (depth/size budget); result widened to unknown#opaque (budget \u2014 not inference debt)`,
2120
- 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)",
2121
2413
  fn: label
2122
2414
  });
2123
2415
  }
@@ -2180,7 +2472,7 @@ function checkSourceInner(filePath, source, file, phi, opts, issues, signatures,
2180
2472
  rc.constraint,
2181
2473
  rec.result
2182
2474
  );
2183
- if (retIssues.length > 0) returnViolated.add(rec.fnName);
2475
+ if (retIssues.some((i) => i.severity === "error")) returnViolated.add(rec.fnName);
2184
2476
  issues.push(...retIssues);
2185
2477
  }
2186
2478
  }
@@ -2239,118 +2531,33 @@ function checkSourceInner(filePath, source, file, phi, opts, issues, signatures,
2239
2531
  };
2240
2532
  }
2241
2533
  function checkReturnConstraint(fnName, cName, constraint, ret) {
2242
- const out = [];
2243
- if (ret.shape.k === "unknown" && !ret.term) return out;
2244
- if (ret.shape.k === "any") return out;
2245
- if (isNullishLitAbs(ret)) return out;
2246
- if (ret.shape.k === "sum" && constraint.fields) {
2247
- const seen = /* @__PURE__ */ new Set();
2248
- for (const m of ret.shape.members) {
2249
- for (const issue of checkReturnConstraint(fnName, cName, constraint, m)) {
2250
- const key = `${issue.message}\0${issue.actual ?? ""}\0${issue.expected ?? ""}`;
2251
- if (seen.has(key)) continue;
2252
- seen.add(key);
2253
- out.push(issue);
2254
- }
2255
- }
2256
- return out;
2257
- }
2258
- const push = (actual, expected, suggestion) => {
2259
- out.push({
2260
- severity: "error",
2261
- 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",
2262
2540
  message: `${fnName}: return value \u22AD @nudo:contract return ${cName}`,
2263
2541
  actual,
2264
2542
  expected,
2265
2543
  suggestion,
2266
2544
  fn: fnName
2267
- });
2268
- };
2269
- if (constraint.fields) {
2270
- const slots = ret.shape.k === "obj" ? ret.shape.slots : void 0;
2271
- if (!slots) {
2272
- if (ret.shape.k !== "never") {
2273
- push(formatAbs(ret), `object shape (${cName})`, `return an object satisfying the ${cName} shape`);
2274
- }
2275
- return out;
2276
- }
2277
- for (const [key, field] of Object.entries(constraint.fields)) {
2278
- const slot = getSlot(slots, key);
2279
- if (!slot) {
2280
- if (!field.optional && !field.constraint.isOptional) {
2281
- push(formatAbs(ret), `missing field ${key}`, `add the missing field ${key} to the return value`);
2282
- }
2283
- continue;
2284
- }
2285
- if (field.constraint.prim && slot.value.shape.k === "prim") {
2286
- const actualPrim = slot.value.shape.type;
2287
- if (actualPrim !== field.constraint.prim) {
2288
- push(
2289
- formatAbs(slot.value),
2290
- `typeof ${key} = "${field.constraint.prim}"`,
2291
- `change the return value's ${key} to ${field.constraint.prim}`
2292
- );
2293
- continue;
2294
- }
2295
- }
2296
- const lv2 = litValue(slot.value);
2297
- if (lv2 !== void 0 && typeof lv2 === "number") {
2298
- for (const p of field.constraint.preds) {
2299
- const flat = p.op === "and" ? p.args : [p];
2300
- for (const atom of flat) {
2301
- if ((atom.op === "gt" || atom.op === "ge" || atom.op === "lt" || atom.op === "le") && atom.b.op === "lit" && typeof atom.b.value === "number") {
2302
- const n = atom.b.value;
2303
- let ok = true;
2304
- if (atom.op === "gt") ok = lv2 > n;
2305
- if (atom.op === "ge") ok = lv2 >= n;
2306
- if (atom.op === "lt") ok = lv2 < n;
2307
- if (atom.op === "le") ok = lv2 <= n;
2308
- if (!ok) {
2309
- const opSym = { gt: ">", ge: "\u2265", lt: "<", le: "\u2264" }[atom.op];
2310
- push(
2311
- formatAbs(slot.value),
2312
- `${key} ${opSym} ${n}`,
2313
- `the return value's ${key} should satisfy ${key} ${opSym} ${n}`
2314
- );
2315
- }
2316
- }
2317
- }
2318
- }
2319
- }
2320
2545
  }
2321
- return out;
2322
- }
2323
- if (constraint.prim && ret.shape.k === "prim") {
2324
- const actualPrim = ret.shape.type;
2325
- if (actualPrim !== constraint.prim) {
2326
- push(formatAbs(ret), `typeof return = "${constraint.prim}"`, `return ${constraint.prim}`);
2327
- return out;
2328
- }
2329
- }
2330
- const lv = litValue(ret);
2331
- if (lv !== void 0 && typeof lv === "number") {
2332
- for (const p of constraint.preds) {
2333
- const flat = p.op === "and" ? p.args : [p];
2334
- for (const atom of flat) {
2335
- if ((atom.op === "gt" || atom.op === "ge" || atom.op === "lt" || atom.op === "le") && atom.b.op === "lit" && typeof atom.b.value === "number") {
2336
- const n = atom.b.value;
2337
- let ok = true;
2338
- if (atom.op === "gt") ok = lv > n;
2339
- if (atom.op === "ge") ok = lv >= n;
2340
- if (atom.op === "lt") ok = lv < n;
2341
- if (atom.op === "le") ok = lv <= n;
2342
- if (!ok) {
2343
- const opSym = { gt: ">", ge: "\u2265", lt: "<", le: "\u2264" }[atom.op];
2344
- push(formatAbs(ret), `return ${opSym} ${n}`, `return a value satisfying ${opSym} ${n}`);
2345
- }
2346
- }
2347
- }
2348
- }
2349
- }
2350
- 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)) {
2351
- 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
+ );
2352
2554
  }
2353
- 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
+ );
2354
2561
  }
2355
2562
 
2356
2563
  // src/algebra/check-report.ts
@@ -2386,6 +2593,12 @@ function actionsForIssue(i) {
2386
2593
  ];
2387
2594
  case "nudo:entry-may-throw":
2388
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
+ },
2389
2602
  {
2390
2603
  kind: "relax",
2391
2604
  label: "declare intentional fail-fast: @nudo:throws Error (or case `!! throws`)",
@@ -2399,8 +2612,7 @@ function actionsForIssue(i) {
2399
2612
  kind: "ignore-throws",
2400
2613
  command: "nudo check --ignore-throws TypeError",
2401
2614
  label: "migration switch: ignore this throw class on L2"
2402
- },
2403
- draft
2615
+ }
2404
2616
  ];
2405
2617
  case "nudo:unknown-inference":
2406
2618
  case "nudo:opaque-result":
@@ -2417,10 +2629,12 @@ function actionsForIssue(i) {
2417
2629
  ];
2418
2630
  case "nudo:fork-truncated":
2419
2631
  case "nudo:recursion-truncated":
2632
+ case "nudo:promise-micro-truncated":
2633
+ case "nudo:promise-micro-error":
2420
2634
  return [
2421
2635
  {
2422
2636
  kind: "info",
2423
- 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"
2424
2638
  }
2425
2639
  ];
2426
2640
  case "nudo:host-effect-blocked":
@@ -2654,107 +2868,6 @@ function formatGitlabCodeQuality(r, opts = {}) {
2654
2868
  };
2655
2869
  });
2656
2870
  }
2657
-
2658
- // src/algebra/abs-modules.ts
2659
- function bindImports(node, env, modules) {
2660
- const mod2 = modules[node.source.value];
2661
- if (!mod2) {
2662
- for (const s of node.specifiers) {
2663
- env.vars.set(s.local.name, unknown);
2664
- }
2665
- return;
2666
- }
2667
- for (const s of node.specifiers) {
2668
- if (s.type === "ImportDefaultSpecifier") {
2669
- env.vars.set(s.local.name, mod2.default ?? unknown);
2670
- } else if (s.type === "ImportSpecifier") {
2671
- const imported = s.imported.type === "Identifier" ? s.imported.name : String(s.imported);
2672
- env.vars.set(s.local.name, mod2.named[imported] ?? unknown);
2673
- } else if (s.type === "ImportNamespaceSpecifier") {
2674
- const slots = {};
2675
- for (const [k, v2] of Object.entries(mod2.named)) slots[k] = { value: v2 };
2676
- if (mod2.default) slots["default"] = { value: mod2.default };
2677
- env.vars.set(s.local.name, obj(slots));
2678
- }
2679
- }
2680
- }
2681
- function fnToAbs(env, name) {
2682
- const impl = env.fns.get(name);
2683
- if (!impl) return void 0;
2684
- return absFunction(impl.params, {
2685
- body: impl.body,
2686
- async: impl.async,
2687
- env
2688
- });
2689
- }
2690
- function lookupExport(env, name) {
2691
- if (env.vars.has(name)) return env.vars.get(name);
2692
- return fnToAbs(env, name);
2693
- }
2694
- function collectAbsExports(file, env, modules) {
2695
- const named = {};
2696
- let defaultExport;
2697
- for (const stmt of file.program.body) {
2698
- if (stmt.type === "ExportNamedDeclaration") {
2699
- if (stmt.source && modules) {
2700
- const mod2 = modules[stmt.source.value];
2701
- if (mod2) {
2702
- for (const spec of stmt.specifiers) {
2703
- if (spec.type !== "ExportSpecifier") continue;
2704
- const local = spec.local.type === "Identifier" ? spec.local.name : spec.local.value;
2705
- const exported = spec.exported.type === "Identifier" ? spec.exported.name : spec.exported.value;
2706
- const v2 = local === "default" ? mod2.default : mod2.named[local];
2707
- if (v2 === void 0) continue;
2708
- if (exported === "default") defaultExport = v2;
2709
- else named[exported] = v2;
2710
- }
2711
- }
2712
- continue;
2713
- }
2714
- const decl = stmt.declaration;
2715
- if (decl) {
2716
- if (decl.type === "FunctionDeclaration" && decl.id) {
2717
- const v2 = lookupExport(env, decl.id.name);
2718
- if (v2) named[decl.id.name] = v2;
2719
- } else if (decl.type === "ClassDeclaration" && decl.id) {
2720
- const v2 = lookupExport(env, decl.id.name);
2721
- if (v2) named[decl.id.name] = v2;
2722
- } else if (decl.type === "VariableDeclaration") {
2723
- for (const d of decl.declarations) {
2724
- if (d.id.type === "Identifier") {
2725
- const v2 = lookupExport(env, d.id.name);
2726
- if (v2) named[d.id.name] = v2;
2727
- }
2728
- }
2729
- }
2730
- }
2731
- for (const spec of stmt.specifiers) {
2732
- if (spec.type !== "ExportSpecifier") continue;
2733
- const local = spec.local.type === "Identifier" ? spec.local.name : spec.local.value;
2734
- const exported = spec.exported.type === "Identifier" ? spec.exported.name : spec.exported.value;
2735
- const v2 = lookupExport(env, local);
2736
- if (v2) named[exported] = v2;
2737
- }
2738
- } else if (stmt.type === "ExportAllDeclaration" && stmt.source && modules) {
2739
- const mod2 = modules[stmt.source.value];
2740
- if (mod2?.named) {
2741
- for (const [k, v2] of Object.entries(mod2.named)) named[k] = v2;
2742
- }
2743
- } else if (stmt.type === "ExportDefaultDeclaration") {
2744
- const d = stmt.declaration;
2745
- if (d.type === "FunctionDeclaration") {
2746
- defaultExport = lookupExport(env, d.id ? d.id.name : "default");
2747
- } else if (d.type === "ClassDeclaration" && d.id) {
2748
- defaultExport = lookupExport(env, d.id.name);
2749
- } else if (d.type === "Identifier") {
2750
- defaultExport = lookupExport(env, d.name);
2751
- }
2752
- }
2753
- }
2754
- const result = { named };
2755
- if (defaultExport) result.default = defaultExport;
2756
- return result;
2757
- }
2758
2871
  export {
2759
2872
  $add,
2760
2873
  $arguments,
@@ -2847,6 +2960,7 @@ export {
2847
2960
  $thisSet,
2848
2961
  $throw,
2849
2962
  $toNumber,
2963
+ $toNumeric,
2850
2964
  $tryCurrentMark,
2851
2965
  $tryDetachSoftCatch,
2852
2966
  $tryDigestSoftCatch,
@@ -2859,6 +2973,8 @@ export {
2859
2973
  $tryTakeSince,
2860
2974
  $typeof,
2861
2975
  $unknown,
2976
+ $updateAdd,
2977
+ $updateSub,
2862
2978
  $ushr,
2863
2979
  $while,
2864
2980
  $whileSeq,
@@ -2895,6 +3011,7 @@ export {
2895
3011
  asAbs,
2896
3012
  asAbsVal,
2897
3013
  assignSourceSlots,
3014
+ assumeFinite,
2898
3015
  attachFnImpl,
2899
3016
  beginCollectionFork,
2900
3017
  betaOf,
@@ -2928,6 +3045,7 @@ export {
2928
3045
  collectionElementJoin,
2929
3046
  collectionExactLen,
2930
3047
  confJoin,
3048
+ constraintAdmitsNullish,
2931
3049
  constraintToEntryAbs,
2932
3050
  contractParamNameSet,
2933
3051
  createEnvironment,
@@ -2974,6 +3092,7 @@ export {
2974
3092
  extStateOf,
2975
3093
  extractDeclaredThrows,
2976
3094
  extractFn,
3095
+ extractFnBudget,
2977
3096
  extractNudoImports,
2978
3097
  extractRefineReturnFromSource,
2979
3098
  extractRefinesFromSource,
@@ -2985,6 +3104,7 @@ export {
2985
3104
  foldRequireSpecArg,
2986
3105
  foldStaticStringExpr,
2987
3106
  formalParamDisplayNames,
3107
+ formalParamSignatureNames,
2988
3108
  formalParamsFromNodes,
2989
3109
  formatAbs,
2990
3110
  formatAbsMultiline,
@@ -3090,6 +3210,7 @@ export {
3090
3210
  mock,
3091
3211
  mod,
3092
3212
  mul,
3213
+ namespaceAbsOf,
3093
3214
  namespaceNameOf,
3094
3215
  ne,
3095
3216
  negAbs,
@@ -3101,6 +3222,7 @@ export {
3101
3222
  noteEvalCallRecord,
3102
3223
  noteEvalFallback,
3103
3224
  notePromiseExecutorFork,
3225
+ nullable,
3104
3226
  num,
3105
3227
  numLit,
3106
3228
  numVar,
@@ -3126,6 +3248,7 @@ export {
3126
3248
  primToTypeof,
3127
3249
  projectFlatMapResult,
3128
3250
  promoteParamShape,
3251
+ propertyKeyOf,
3129
3252
  protoBrandAbs,
3130
3253
  protoOfRecv,
3131
3254
  ptypeof,
@@ -3149,6 +3272,7 @@ export {
3149
3272
  resetNudoModuleExecCache,
3150
3273
  resetParseSourceCache,
3151
3274
  resetPhi,
3275
+ resetSidecarLoadFailureCache,
3152
3276
  runTranspiled,
3153
3277
  runTranspiledOptionsMemoKey,
3154
3278
  runWithLoopExits,