@nudojs/core 1.12.0 → 1.14.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/{chunk-JI2XDARI.js → chunk-N7AGFMSD.js} +39 -1
- package/dist/{chunk-56GO5HYS.js → chunk-W3Z77Y2I.js} +298 -79
- package/dist/exec.js +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +77 -17
- package/dist/internal.js +2 -2
- package/package.json +1 -1
|
@@ -47,7 +47,7 @@ import {
|
|
|
47
47
|
withEvalBudgetOverride,
|
|
48
48
|
withExecPhi,
|
|
49
49
|
withFnBudgetOverride
|
|
50
|
-
} from "./chunk-
|
|
50
|
+
} from "./chunk-W3Z77Y2I.js";
|
|
51
51
|
|
|
52
52
|
// src/algebra/directive-scan.ts
|
|
53
53
|
function cleanDirectiveLine(raw, kind) {
|
|
@@ -622,6 +622,14 @@ function scanAssumeSegments(lines) {
|
|
|
622
622
|
}
|
|
623
623
|
return out;
|
|
624
624
|
}
|
|
625
|
+
function scanInvariantSegments(lines) {
|
|
626
|
+
const out = [];
|
|
627
|
+
for (const line of lines) {
|
|
628
|
+
const m = line.match(/^@nudo:invariant\s+(.+)$/);
|
|
629
|
+
if (m) out.push(m[1].trim().replace(/\*\/$/, "").trim());
|
|
630
|
+
}
|
|
631
|
+
return out;
|
|
632
|
+
}
|
|
625
633
|
function scanThrowsDecl(lines) {
|
|
626
634
|
const kinds = /* @__PURE__ */ new Set();
|
|
627
635
|
let any = false;
|
|
@@ -1516,6 +1524,35 @@ function extractAssumeSegs(source, fnName) {
|
|
|
1516
1524
|
}
|
|
1517
1525
|
return out;
|
|
1518
1526
|
}
|
|
1527
|
+
var INVARIANT_SEG_REGEX = /^([A-Za-z_$][\w$]*)((?:\[\*\]|\.[A-Za-z_$][\w$]*)*)\s*(>=|<=|>|<)\s*(-?\d+(?:\.\d+)?)$/;
|
|
1528
|
+
function extractInvariantSegs(source, fnName) {
|
|
1529
|
+
if (!stripStringsKeepComments(source).includes("@nudo:invariant")) return [];
|
|
1530
|
+
const out = [];
|
|
1531
|
+
for (const seg of scanInvariantSegments(fnDirectiveCommentLines(source, fnName))) {
|
|
1532
|
+
const m = seg.match(INVARIANT_SEG_REGEX);
|
|
1533
|
+
if (!m) {
|
|
1534
|
+
out.push({ kind: "bad", raw: seg });
|
|
1535
|
+
continue;
|
|
1536
|
+
}
|
|
1537
|
+
const pathStr = m[2] ?? "";
|
|
1538
|
+
const path = [];
|
|
1539
|
+
for (const pm of pathStr.matchAll(/\[\*\]|\.[A-Za-z_$][\w$]*/g)) {
|
|
1540
|
+
const t = pm[0];
|
|
1541
|
+
if (t === "[*]") path.push({ k: "idx" });
|
|
1542
|
+
else path.push({ k: "prop", name: t.slice(1) });
|
|
1543
|
+
}
|
|
1544
|
+
const relTxt = m[3];
|
|
1545
|
+
out.push({
|
|
1546
|
+
kind: "ok",
|
|
1547
|
+
name: m[1],
|
|
1548
|
+
path,
|
|
1549
|
+
rel: relTxt === ">=" ? "ge" : relTxt === "<=" ? "le" : relTxt === ">" ? "gt" : "lt",
|
|
1550
|
+
value: Number(m[4]),
|
|
1551
|
+
display: `${m[1]}${pathStr} ${relTxt} ${m[4]}`
|
|
1552
|
+
});
|
|
1553
|
+
}
|
|
1554
|
+
return out;
|
|
1555
|
+
}
|
|
1519
1556
|
function fnTopLevelLocalNames(body) {
|
|
1520
1557
|
const block = body;
|
|
1521
1558
|
if (!block || block.type !== "BlockStatement" || !Array.isArray(block.body)) return [];
|
|
@@ -5568,6 +5605,7 @@ export {
|
|
|
5568
5605
|
execNudoModule,
|
|
5569
5606
|
resetNudoModuleExecCache,
|
|
5570
5607
|
extractAssumeSegs,
|
|
5608
|
+
extractInvariantSegs,
|
|
5571
5609
|
fnTopLevelLocalNames,
|
|
5572
5610
|
extractRefinesFromSource,
|
|
5573
5611
|
refineToIndexedFull,
|
|
@@ -28,6 +28,27 @@ function termEquals(a, b) {
|
|
|
28
28
|
}
|
|
29
29
|
return false;
|
|
30
30
|
}
|
|
31
|
+
var DETERMINISTIC_APP_FNS = /* @__PURE__ */ new Set([
|
|
32
|
+
"Math.max",
|
|
33
|
+
"Math.min",
|
|
34
|
+
"Math.abs",
|
|
35
|
+
"Math.round",
|
|
36
|
+
"Math.floor",
|
|
37
|
+
"Math.ceil",
|
|
38
|
+
"Math.trunc",
|
|
39
|
+
"Math.sign",
|
|
40
|
+
"+",
|
|
41
|
+
"-",
|
|
42
|
+
"*",
|
|
43
|
+
"/",
|
|
44
|
+
"%"
|
|
45
|
+
]);
|
|
46
|
+
var LEN_TERM_ID_RE = /^len#\d+$/;
|
|
47
|
+
function isDeterministicTerm(t) {
|
|
48
|
+
if (t.op === "lit") return true;
|
|
49
|
+
if (t.op === "var") return LEN_TERM_ID_RE.test(t.id);
|
|
50
|
+
return DETERMINISTIC_APP_FNS.has(t.fn) && t.args.every(isDeterministicTerm);
|
|
51
|
+
}
|
|
31
52
|
function litKeyString(v2) {
|
|
32
53
|
if (typeof v2 === "number" && Object.is(v2, -0)) return "-0";
|
|
33
54
|
return String(v2);
|
|
@@ -4471,6 +4492,19 @@ function withAssumePremises(p, body) {
|
|
|
4471
4492
|
assumePremises = prev;
|
|
4472
4493
|
}
|
|
4473
4494
|
}
|
|
4495
|
+
var invariantSpecs = [];
|
|
4496
|
+
function currentInvariantSpecs() {
|
|
4497
|
+
return invariantSpecs;
|
|
4498
|
+
}
|
|
4499
|
+
function withInvariantSpecs(specs, body) {
|
|
4500
|
+
const prev = invariantSpecs;
|
|
4501
|
+
invariantSpecs = specs;
|
|
4502
|
+
try {
|
|
4503
|
+
return body();
|
|
4504
|
+
} finally {
|
|
4505
|
+
invariantSpecs = prev;
|
|
4506
|
+
}
|
|
4507
|
+
}
|
|
4474
4508
|
var noBody2 = { type: "BlockStatement", body: [], directives: [] };
|
|
4475
4509
|
var hostGlobalFnCall;
|
|
4476
4510
|
function setHostGlobalFnCall(bridge) {
|
|
@@ -4511,8 +4545,11 @@ function $absVal(v2) {
|
|
|
4511
4545
|
return asAbsVal(v2);
|
|
4512
4546
|
}
|
|
4513
4547
|
function callAtFunctionBoundary(body) {
|
|
4514
|
-
if (assumePremises !== pTrue) {
|
|
4515
|
-
return withAssumePremises(
|
|
4548
|
+
if (assumePremises !== pTrue || invariantSpecs.length > 0) {
|
|
4549
|
+
return withAssumePremises(
|
|
4550
|
+
pTrue,
|
|
4551
|
+
() => withInvariantSpecs([], () => boundaryBody(body))
|
|
4552
|
+
);
|
|
4516
4553
|
}
|
|
4517
4554
|
return boundaryBody(body);
|
|
4518
4555
|
}
|
|
@@ -5043,6 +5080,16 @@ function bumpEvalForkBudget() {
|
|
|
5043
5080
|
return false;
|
|
5044
5081
|
}
|
|
5045
5082
|
|
|
5083
|
+
// src/algebra/len-sharing.ts
|
|
5084
|
+
var domainShared = /* @__PURE__ */ new WeakSet();
|
|
5085
|
+
function markDomainShared(a) {
|
|
5086
|
+
domainShared.add(a);
|
|
5087
|
+
return a;
|
|
5088
|
+
}
|
|
5089
|
+
function isDomainShared(a) {
|
|
5090
|
+
return domainShared.has(a);
|
|
5091
|
+
}
|
|
5092
|
+
|
|
5046
5093
|
// src/algebra/exec/match-iter.ts
|
|
5047
5094
|
var tables = /* @__PURE__ */ new WeakMap();
|
|
5048
5095
|
function registerMatchIter(val, elements) {
|
|
@@ -6585,6 +6632,12 @@ function makeArrayCtorAbs(args) {
|
|
|
6585
6632
|
}
|
|
6586
6633
|
return abs({ k: "tuple", elements: [a0] }, void 0, void 0, "exact");
|
|
6587
6634
|
}
|
|
6635
|
+
var fromIdxSeq = 0;
|
|
6636
|
+
function fromIdxAbs(lenTerm2, lenNumeric = false) {
|
|
6637
|
+
const t = v(`fromIdx#${fromIdxSeq++}`);
|
|
6638
|
+
const pred = lenNumeric && lenTerm2 ? and(ge(t, lit(0)), lt(t, lenTerm2)) : ge(t, lit(0));
|
|
6639
|
+
return abs({ k: "prim", type: "number" }, t, pred, "path");
|
|
6640
|
+
}
|
|
6588
6641
|
function evalArrayFrom(args) {
|
|
6589
6642
|
if (args[0] && typeof args[0] === "object" && args[0].shape.k === "brand" && (args[0].shape.name === "Arguments" || args[0].shape.name === "Proxy")) {
|
|
6590
6643
|
args = [args[0].shape.shape, ...args.slice(1)];
|
|
@@ -6609,6 +6662,7 @@ function evalArrayFrom(args) {
|
|
|
6609
6662
|
if (!hasMapFn) return el;
|
|
6610
6663
|
return applyCallbackValue(mapFn, [el, i], emptyEnv(), pTrue, defaultLeakBudget);
|
|
6611
6664
|
};
|
|
6665
|
+
const idxOrDrop = (lenTerm2, lenNumeric = false) => hasMapFn ? fromIdxAbs(lenTerm2, lenNumeric) : unknown;
|
|
6612
6666
|
if (isSetAbs(a0) || isMapAbs(a0)) {
|
|
6613
6667
|
const els = isSetAbs(a0) ? setElementsAbs(a0) : mapEntriesAbs(a0);
|
|
6614
6668
|
if (els.length === 0) {
|
|
@@ -6643,7 +6697,7 @@ function evalArrayFrom(args) {
|
|
|
6643
6697
|
}
|
|
6644
6698
|
const k = a0.shape.k;
|
|
6645
6699
|
if (k === "arr") {
|
|
6646
|
-
const el = mapOne(a0.shape.element,
|
|
6700
|
+
const el = mapOne(markDomainShared(a0.shape.element), idxOrDrop());
|
|
6647
6701
|
return abs({ k: "arr", element: el }, void 0, void 0, "path");
|
|
6648
6702
|
}
|
|
6649
6703
|
if (k === "tuple") {
|
|
@@ -6717,16 +6771,19 @@ function evalArrayFrom(args) {
|
|
|
6717
6771
|
}
|
|
6718
6772
|
return abs({ k: "tuple", elements: els }, void 0, void 0, "exact");
|
|
6719
6773
|
}
|
|
6720
|
-
|
|
6721
|
-
|
|
6722
|
-
|
|
6774
|
+
const iAbs = idxOrDrop(lit(nLit), true);
|
|
6775
|
+
let el2 = mapOne(undefAbs(), iAbs);
|
|
6776
|
+
for (const vv of allIdxSlotValues()) el2 = joinAbs(el2, mapOne(vv, iAbs));
|
|
6777
|
+
if (open) el2 = joinAbs(el2, mapOne(unknown, iAbs));
|
|
6723
6778
|
return abs({ k: "arr", element: el2 }, void 0, void 0, "path");
|
|
6724
6779
|
}
|
|
6780
|
+
const lenT = lenAbs?.term;
|
|
6781
|
+
const lenNum = !!lenT && lenAbs.shape.k === "prim" && lenAbs.shape.type === "number";
|
|
6725
6782
|
const idxValues = allIdxSlotValues();
|
|
6726
6783
|
let el = idxValues.length > 0 ? idxValues.reduce((x, y) => joinAbs(x, y)) : undefAbs();
|
|
6727
6784
|
el = joinAbs(el, undefAbs());
|
|
6728
6785
|
if (open) el = joinAbs(el, unknown);
|
|
6729
|
-
el = mapOne(el,
|
|
6786
|
+
el = mapOne(markDomainShared(el), idxOrDrop(lenT, lenNum));
|
|
6730
6787
|
return abs({ k: "arr", element: el }, void 0, void 0, "path");
|
|
6731
6788
|
}
|
|
6732
6789
|
return unknown;
|
|
@@ -13700,7 +13757,7 @@ function $add(a, b) {
|
|
|
13700
13757
|
return add(a, b, currentExecPhi());
|
|
13701
13758
|
}
|
|
13702
13759
|
function $sub(a, b) {
|
|
13703
|
-
return sub(a, b, currentExecPhi());
|
|
13760
|
+
return sub(a, b, currentExecPhi(), currentAssumePremises());
|
|
13704
13761
|
}
|
|
13705
13762
|
function $mul(a, b) {
|
|
13706
13763
|
return mul(a, b, currentExecPhi());
|
|
@@ -13729,6 +13786,16 @@ function $assumeAnchor(v2, name) {
|
|
|
13729
13786
|
if (!mentions) return v2;
|
|
13730
13787
|
const a = v2;
|
|
13731
13788
|
if (typeof a !== "object" || a === null || a.term === void 0 || a.term.op === "lit" || a.term.op === "var" && a.term.id === name) {
|
|
13789
|
+
if (a && typeof a === "object" && a.shape?.k === "sum") {
|
|
13790
|
+
const members = a.shape.members.map((m) => {
|
|
13791
|
+
if (m.term === void 0 || m.term.op === "lit" || m.term.op === "var" && m.term.id === name) {
|
|
13792
|
+
return m;
|
|
13793
|
+
}
|
|
13794
|
+
const nt2 = { op: "var", id: name };
|
|
13795
|
+
return { ...m, term: nt2, pred: rewritePred(m.pred, m.term, nt2) };
|
|
13796
|
+
});
|
|
13797
|
+
return { ...a, shape: { ...a.shape, members } };
|
|
13798
|
+
}
|
|
13732
13799
|
return v2;
|
|
13733
13800
|
}
|
|
13734
13801
|
const nt = { op: "var", id: name };
|
|
@@ -13808,11 +13875,13 @@ function $not(a) {
|
|
|
13808
13875
|
}
|
|
13809
13876
|
function $eq(a, b) {
|
|
13810
13877
|
const r = strictEqAbs(a, b);
|
|
13811
|
-
|
|
13878
|
+
if (r !== void 0) return boolLit(r);
|
|
13879
|
+
return cmp("eq", a, b, currentExecPhi());
|
|
13812
13880
|
}
|
|
13813
13881
|
function $ne(a, b) {
|
|
13814
13882
|
const r = strictEqAbs(a, b);
|
|
13815
|
-
|
|
13883
|
+
if (r !== void 0) return boolLit(!r);
|
|
13884
|
+
return cmp("ne", a, b, currentExecPhi());
|
|
13816
13885
|
}
|
|
13817
13886
|
function $eqLoose(a, b) {
|
|
13818
13887
|
const r = looseEqAbs(a, b);
|
|
@@ -15433,6 +15502,80 @@ function primOf(a) {
|
|
|
15433
15502
|
return void 0;
|
|
15434
15503
|
}
|
|
15435
15504
|
|
|
15505
|
+
// src/algebra/exec/runtime/invariant-apply.ts
|
|
15506
|
+
var mintSeq = 0;
|
|
15507
|
+
function strengthenLeaf(a, spec) {
|
|
15508
|
+
const s = a.shape;
|
|
15509
|
+
if (s.k !== "prim" || s.type !== "number") return a;
|
|
15510
|
+
if (a.term?.op === "lit") return a;
|
|
15511
|
+
const term = a.term ?? v(`inv#${mintSeq++}`);
|
|
15512
|
+
const bound = lit(spec.value);
|
|
15513
|
+
const rel = spec.rel === "ge" ? ge(term, bound) : spec.rel === "le" ? le(term, bound) : spec.rel === "gt" ? gt(term, bound) : lt(term, bound);
|
|
15514
|
+
return abs(s, term, a.pred ? and(a.pred, rel) : rel, "path");
|
|
15515
|
+
}
|
|
15516
|
+
function drill(v2, path, spec) {
|
|
15517
|
+
if (path.length === 0) return strengthenLeaf(v2, spec);
|
|
15518
|
+
const [step, ...rest] = path;
|
|
15519
|
+
const s = v2.shape;
|
|
15520
|
+
if (step.k === "idx") {
|
|
15521
|
+
if (s.k === "arr") {
|
|
15522
|
+
const element = drill(s.element, rest, spec);
|
|
15523
|
+
return element === s.element ? v2 : abs({ k: "arr", element }, void 0, void 0, v2.conf);
|
|
15524
|
+
}
|
|
15525
|
+
if (s.k === "tuple") {
|
|
15526
|
+
let changed = false;
|
|
15527
|
+
const elements = s.elements.map((e) => {
|
|
15528
|
+
const w = drill(e, rest, spec);
|
|
15529
|
+
if (w !== e) changed = true;
|
|
15530
|
+
return w;
|
|
15531
|
+
});
|
|
15532
|
+
return changed ? abs({ ...s, elements }, void 0, void 0, v2.conf) : v2;
|
|
15533
|
+
}
|
|
15534
|
+
if (s.k === "sum") {
|
|
15535
|
+
let changed = false;
|
|
15536
|
+
const members = s.members.map((m) => {
|
|
15537
|
+
const w = drill(m, rest, spec);
|
|
15538
|
+
if (w !== m) changed = true;
|
|
15539
|
+
return w;
|
|
15540
|
+
});
|
|
15541
|
+
return changed ? abs({ k: "sum", members }, void 0, void 0, v2.conf) : v2;
|
|
15542
|
+
}
|
|
15543
|
+
return v2;
|
|
15544
|
+
}
|
|
15545
|
+
if (s.k === "obj") {
|
|
15546
|
+
const slot = s.slots[step.name];
|
|
15547
|
+
if (!slot) return v2;
|
|
15548
|
+
const w = drill(slot.value, rest, spec);
|
|
15549
|
+
if (w === slot.value) return v2;
|
|
15550
|
+
return abs(
|
|
15551
|
+
{ ...s, slots: { ...s.slots, [step.name]: { ...slot, value: w } } },
|
|
15552
|
+
void 0,
|
|
15553
|
+
void 0,
|
|
15554
|
+
v2.conf
|
|
15555
|
+
);
|
|
15556
|
+
}
|
|
15557
|
+
return v2;
|
|
15558
|
+
}
|
|
15559
|
+
function applyInvariantsToPack(pack) {
|
|
15560
|
+
const specs = currentInvariantSpecs();
|
|
15561
|
+
if (specs.length === 0) return pack;
|
|
15562
|
+
const s = pack.shape;
|
|
15563
|
+
if (s.k !== "obj") return pack;
|
|
15564
|
+
let slots;
|
|
15565
|
+
let changed = false;
|
|
15566
|
+
for (const spec of specs) {
|
|
15567
|
+
const cur = (slots ?? s.slots)[spec.name];
|
|
15568
|
+
if (!cur) continue;
|
|
15569
|
+
const w = drill(cur.value, spec.path, spec);
|
|
15570
|
+
if (w === cur.value) continue;
|
|
15571
|
+
if (!slots) slots = { ...s.slots };
|
|
15572
|
+
slots[spec.name] = { ...cur, value: w };
|
|
15573
|
+
changed = true;
|
|
15574
|
+
}
|
|
15575
|
+
if (!changed) return pack;
|
|
15576
|
+
return abs({ ...s, slots }, void 0, void 0, pack.conf);
|
|
15577
|
+
}
|
|
15578
|
+
|
|
15436
15579
|
// src/algebra/exec/runtime/loop-widen.ts
|
|
15437
15580
|
function mergeGrowth(a, b) {
|
|
15438
15581
|
if (a === true || b === true) return true;
|
|
@@ -15718,9 +15861,9 @@ function makeLoopWidener() {
|
|
|
15718
15861
|
const base = acc ?? final;
|
|
15719
15862
|
const g = interrupted ? true : growth;
|
|
15720
15863
|
if (g === null) {
|
|
15721
|
-
return collapseArrSums(base);
|
|
15864
|
+
return applyInvariantsToPack(collapseArrSums(base));
|
|
15722
15865
|
}
|
|
15723
|
-
return widenWithPath(normalizeForWiden(base, g), g);
|
|
15866
|
+
return applyInvariantsToPack(widenWithPath(normalizeForWiden(base, g), g));
|
|
15724
15867
|
}
|
|
15725
15868
|
};
|
|
15726
15869
|
}
|
|
@@ -16352,7 +16495,7 @@ function $idx(a, i, opts) {
|
|
|
16352
16495
|
if (idx === void 0) {
|
|
16353
16496
|
return joinAbs(a.shape.element, oobUndef());
|
|
16354
16497
|
}
|
|
16355
|
-
return a.shape.element;
|
|
16498
|
+
return markDomainShared(a.shape.element);
|
|
16356
16499
|
}
|
|
16357
16500
|
if (a.shape.k === "brand") {
|
|
16358
16501
|
const elem = TYPED_ARRAY_ELEMENT[a.shape.name ?? ""];
|
|
@@ -16516,7 +16659,7 @@ function abstractLen(a) {
|
|
|
16516
16659
|
let t = lenTermMemo.get(a);
|
|
16517
16660
|
if (!t) {
|
|
16518
16661
|
t = { op: "var", id: `len#${lenVarSeq++}` };
|
|
16519
|
-
lenTermMemo.set(a, t);
|
|
16662
|
+
if (!isDomainShared(a)) lenTermMemo.set(a, t);
|
|
16520
16663
|
}
|
|
16521
16664
|
return abs({ k: "prim", type: "number" }, t, ge(t, lit(0)), "path");
|
|
16522
16665
|
}
|
|
@@ -16948,7 +17091,7 @@ function $tpl(cooked, raw) {
|
|
|
16948
17091
|
}
|
|
16949
17092
|
function elemsOf(a) {
|
|
16950
17093
|
if (a.shape.k === "tuple") return [...a.shape.elements];
|
|
16951
|
-
if (a.shape.k === "arr") return [a.shape.element];
|
|
17094
|
+
if (a.shape.k === "arr") return [markDomainShared(a.shape.element)];
|
|
16952
17095
|
if (isSetAbs(a)) return setElementsAbs(a);
|
|
16953
17096
|
if (isMapAbs(a)) return mapEntriesAbs(a);
|
|
16954
17097
|
const drained = drainGenElements(a);
|
|
@@ -18070,6 +18213,9 @@ function $for(init, test, step, body, maxIters = DEFAULT_MAX_LOOP_ITERS, opts) {
|
|
|
18070
18213
|
return exitJoin !== void 0 ? counterWiden.widenFinal(exitJoin) : state;
|
|
18071
18214
|
}
|
|
18072
18215
|
function widenLoopJoin(a) {
|
|
18216
|
+
return applyInvariantsToPack(widenLoopJoinInner(a));
|
|
18217
|
+
}
|
|
18218
|
+
function widenLoopJoinInner(a) {
|
|
18073
18219
|
const widenValue = (v2) => {
|
|
18074
18220
|
if (v2.shape.k !== "sum") return v2;
|
|
18075
18221
|
const members = v2.shape.members;
|
|
@@ -22018,7 +22164,7 @@ function invokeArrMethod(arr, method, args) {
|
|
|
22018
22164
|
"path"
|
|
22019
22165
|
);
|
|
22020
22166
|
}
|
|
22021
|
-
const out = callFn(args[0], args[1], shape2.element,
|
|
22167
|
+
const out = callFn(args[0], args[1], markDomainShared(shape2.element), fromIdxAbs(), arr);
|
|
22022
22168
|
const el = mapElementFallback(asAbs(args[0]), shape2.element, out);
|
|
22023
22169
|
const conf = el === out ? confJoin(arr.conf, out.conf) : "partial";
|
|
22024
22170
|
return abs({ k: "arr", element: el }, void 0, void 0, conf);
|
|
@@ -22055,7 +22201,7 @@ function invokeArrMethod(arr, method, args) {
|
|
|
22055
22201
|
}
|
|
22056
22202
|
return acc;
|
|
22057
22203
|
}
|
|
22058
|
-
return callFn(fn2, void 0, acc, shape2.element,
|
|
22204
|
+
return callFn(fn2, void 0, acc, markDomainShared(shape2.element), fromIdxAbs(), arr);
|
|
22059
22205
|
}
|
|
22060
22206
|
if (method === "reduceRight") {
|
|
22061
22207
|
validateCallableArg(args[0], "reduceRight callback may not be callable");
|
|
@@ -22091,7 +22237,7 @@ function invokeArrMethod(arr, method, args) {
|
|
|
22091
22237
|
cause: "reduceRight of possibly-empty array with no initial value"
|
|
22092
22238
|
});
|
|
22093
22239
|
}
|
|
22094
|
-
return callFn(fn2, void 0, acc, shape2.element,
|
|
22240
|
+
return callFn(fn2, void 0, acc, markDomainShared(shape2.element), fromIdxAbs(), arr);
|
|
22095
22241
|
}
|
|
22096
22242
|
if (method === "filter") {
|
|
22097
22243
|
validateCallableArg(args[0], "filter callback may not be callable");
|
|
@@ -22136,7 +22282,7 @@ function invokeArrMethod(arr, method, args) {
|
|
|
22136
22282
|
);
|
|
22137
22283
|
return projectFlatMapResult(arr.conf, mapped);
|
|
22138
22284
|
}
|
|
22139
|
-
const out = callFn(args[0], args[1], shape2.element,
|
|
22285
|
+
const out = callFn(args[0], args[1], markDomainShared(shape2.element), fromIdxAbs(), arr);
|
|
22140
22286
|
return projectFlatMapResult(arr.conf, [out]);
|
|
22141
22287
|
}
|
|
22142
22288
|
if (method === "forEach") {
|
|
@@ -22146,7 +22292,7 @@ function invokeArrMethod(arr, method, args) {
|
|
|
22146
22292
|
if (!isHole(i)) callFn(args[0], args[1], el, $lit(i), arr);
|
|
22147
22293
|
});
|
|
22148
22294
|
} else {
|
|
22149
|
-
callFn(args[0], args[1], shape2.element,
|
|
22295
|
+
callFn(args[0], args[1], markDomainShared(shape2.element), fromIdxAbs(), arr);
|
|
22150
22296
|
}
|
|
22151
22297
|
return undefAbs();
|
|
22152
22298
|
}
|
|
@@ -22165,7 +22311,7 @@ function invokeArrMethod(arr, method, args) {
|
|
|
22165
22311
|
if (method === "every" && !t) return boolLit(false);
|
|
22166
22312
|
}
|
|
22167
22313
|
} else {
|
|
22168
|
-
const t = callbackTruth(callFn(args[0], args[1], shape2.element,
|
|
22314
|
+
const t = callbackTruth(callFn(args[0], args[1], markDomainShared(shape2.element), fromIdxAbs(), arr));
|
|
22169
22315
|
if (t === void 0) return bool();
|
|
22170
22316
|
if (method === "some" && t) return boolLit(true);
|
|
22171
22317
|
if (method === "every" && !t) return boolLit(false);
|
|
@@ -22190,8 +22336,8 @@ function invokeArrMethod(arr, method, args) {
|
|
|
22190
22336
|
}
|
|
22191
22337
|
return undefAbs();
|
|
22192
22338
|
}
|
|
22193
|
-
const el = shape2.element;
|
|
22194
|
-
const t = callbackTruth(callFn(args[0], args[1], el,
|
|
22339
|
+
const el = markDomainShared(shape2.element);
|
|
22340
|
+
const t = callbackTruth(callFn(args[0], args[1], el, fromIdxAbs(), arr));
|
|
22195
22341
|
if (t === true) return el;
|
|
22196
22342
|
if (t === false) return undefAbs();
|
|
22197
22343
|
return joinAbs(el, undefAbs());
|
|
@@ -22208,7 +22354,7 @@ function invokeArrMethod(arr, method, args) {
|
|
|
22208
22354
|
if (undecided) return joinAbs(unknownIdx(), $lit(-1));
|
|
22209
22355
|
return $lit(-1);
|
|
22210
22356
|
}
|
|
22211
|
-
const t = callbackTruth(callFn(args[0], args[1], shape2.element,
|
|
22357
|
+
const t = callbackTruth(callFn(args[0], args[1], markDomainShared(shape2.element), fromIdxAbs(), arr));
|
|
22212
22358
|
if (t === true) return unknownIdx();
|
|
22213
22359
|
if (t === false) return $lit(-1);
|
|
22214
22360
|
return joinAbs(unknownIdx(), $lit(-1));
|
|
@@ -22229,7 +22375,7 @@ function invokeArrMethod(arr, method, args) {
|
|
|
22229
22375
|
}
|
|
22230
22376
|
return wantIndex ? $lit(-1) : undefAbs();
|
|
22231
22377
|
}
|
|
22232
|
-
const t = callbackTruth(callFn(args[0], args[1], shape2.element,
|
|
22378
|
+
const t = callbackTruth(callFn(args[0], args[1], markDomainShared(shape2.element), fromIdxAbs(), arr));
|
|
22233
22379
|
if (wantIndex) {
|
|
22234
22380
|
if (t === true) return unknownIdx();
|
|
22235
22381
|
if (t === false) return $lit(-1);
|
|
@@ -22279,7 +22425,7 @@ function invokeArrMethod(arr, method, args) {
|
|
|
22279
22425
|
}
|
|
22280
22426
|
return abstractSeqIteratorAbs(
|
|
22281
22427
|
abs(
|
|
22282
|
-
{ k: "tuple", elements: [
|
|
22428
|
+
{ k: "tuple", elements: [fromIdxAbs(), markDomainShared(shape2.element)] },
|
|
22283
22429
|
void 0,
|
|
22284
22430
|
void 0,
|
|
22285
22431
|
"partial"
|
|
@@ -27909,6 +28055,12 @@ function numericBounds(a, phi2 = pTrue) {
|
|
|
27909
28055
|
if (a.term?.op === "var") {
|
|
27910
28056
|
collectBoundsFromPhi(phi2, a.term.id, result);
|
|
27911
28057
|
}
|
|
28058
|
+
if (a.pred && a.term) {
|
|
28059
|
+
collectExclusionsFromPred(a.pred, a.term, result);
|
|
28060
|
+
}
|
|
28061
|
+
if (a.term?.op === "var") {
|
|
28062
|
+
collectExclusionsFromPhi(phi2, a.term.id, result);
|
|
28063
|
+
}
|
|
27912
28064
|
if (a.term?.op === "lit" && typeof a.term.value === "number" && !Number.isNaN(a.term.value)) {
|
|
27913
28065
|
result.lo = { value: a.term.value, strict: false };
|
|
27914
28066
|
result.hi = { value: a.term.value, strict: false };
|
|
@@ -27962,11 +28114,113 @@ function collectBoundsFromPhi(phi2, id, acc) {
|
|
|
27962
28114
|
noteBoundFromRel(p, isTarget, acc);
|
|
27963
28115
|
}
|
|
27964
28116
|
}
|
|
27965
|
-
function
|
|
28117
|
+
function noteStrictExclusion(p, isTarget, acc) {
|
|
28118
|
+
if (p.op !== "ne") return;
|
|
28119
|
+
const num2 = exclusionLitOf(p, isTarget);
|
|
28120
|
+
if (num2 === void 0) return;
|
|
28121
|
+
if (acc.lo !== void 0 && acc.lo.value === num2 && !acc.lo.strict) {
|
|
28122
|
+
acc.lo = { value: num2, strict: true };
|
|
28123
|
+
}
|
|
28124
|
+
if (acc.hi !== void 0 && acc.hi.value === num2 && !acc.hi.strict) {
|
|
28125
|
+
acc.hi = { value: num2, strict: true };
|
|
28126
|
+
}
|
|
28127
|
+
}
|
|
28128
|
+
function exclusionLitOf(p, isTarget) {
|
|
28129
|
+
if (isTarget(p.a) && p.b.op === "lit" && typeof p.b.value === "number" && !Number.isNaN(p.b.value)) {
|
|
28130
|
+
return p.b.value;
|
|
28131
|
+
}
|
|
28132
|
+
if (isTarget(p.b) && p.a.op === "lit" && typeof p.a.value === "number" && !Number.isNaN(p.a.value)) {
|
|
28133
|
+
return p.a.value;
|
|
28134
|
+
}
|
|
28135
|
+
return void 0;
|
|
28136
|
+
}
|
|
28137
|
+
function collectExclusionsFromPred(pred, term, acc) {
|
|
28138
|
+
const isTarget = (t) => termToString(t) === termToString(term);
|
|
28139
|
+
const apply = (p) => {
|
|
28140
|
+
if (p.op === "and") {
|
|
28141
|
+
p.args.forEach(apply);
|
|
28142
|
+
return;
|
|
28143
|
+
}
|
|
28144
|
+
noteStrictExclusion(p, isTarget, acc);
|
|
28145
|
+
};
|
|
28146
|
+
apply(pred);
|
|
28147
|
+
}
|
|
28148
|
+
function collectExclusionsFromPhi(phi2, id, acc) {
|
|
28149
|
+
const isTarget = (t) => t.op === "var" && t.id === id;
|
|
28150
|
+
const conjs = phi2.op === "and" ? phi2.args : [phi2];
|
|
28151
|
+
for (const p of conjs) {
|
|
28152
|
+
noteStrictExclusion(p, isTarget, acc);
|
|
28153
|
+
}
|
|
28154
|
+
}
|
|
28155
|
+
function isNonNaN(a, phi2) {
|
|
28156
|
+
if (a.term?.op === "lit") {
|
|
28157
|
+
return typeof a.term.value === "number" && !Number.isNaN(a.term.value);
|
|
28158
|
+
}
|
|
28159
|
+
if (a.shape.k === "sum") {
|
|
28160
|
+
return a.shape.members.every(
|
|
28161
|
+
(m) => isNonNaN(m, phi2)
|
|
28162
|
+
);
|
|
28163
|
+
}
|
|
28164
|
+
const hasNonNaNFact = (p) => {
|
|
28165
|
+
if (!p) return false;
|
|
28166
|
+
if (p.op === "and") return p.args.some(hasNonNaNFact);
|
|
28167
|
+
if (p.op === "assumeFinite") {
|
|
28168
|
+
return !!a.term && termEquals(p.t, a.term);
|
|
28169
|
+
}
|
|
28170
|
+
if (p.op === "ne") {
|
|
28171
|
+
if (p.b.op === "lit" && typeof p.b.value === "number" && Number.isNaN(p.b.value) && a.term && termEquals(p.a, a.term)) {
|
|
28172
|
+
return true;
|
|
28173
|
+
}
|
|
28174
|
+
}
|
|
28175
|
+
if (p.op === "gt" || p.op === "ge" || p.op === "lt" || p.op === "le") {
|
|
28176
|
+
if (a.term && (termEquals(p.a, a.term) || termEquals(p.b, a.term))) return true;
|
|
28177
|
+
}
|
|
28178
|
+
return false;
|
|
28179
|
+
};
|
|
28180
|
+
if (hasNonNaNFact(a.pred)) return true;
|
|
28181
|
+
if (a.term?.op === "var") {
|
|
28182
|
+
const id = a.term.id;
|
|
28183
|
+
const conjs = phi2.op === "and" ? phi2.args : [phi2];
|
|
28184
|
+
for (const p of conjs) {
|
|
28185
|
+
if (p.op === "assumeFinite" && p.t.op === "var" && p.t.id === id) {
|
|
28186
|
+
return true;
|
|
28187
|
+
}
|
|
28188
|
+
if (p.op === "ne") {
|
|
28189
|
+
const b = p.b;
|
|
28190
|
+
if (b.op === "lit" && typeof b.value === "number" && Number.isNaN(b.value) && p.a.op === "var" && p.a.id === id) {
|
|
28191
|
+
return true;
|
|
28192
|
+
}
|
|
28193
|
+
}
|
|
28194
|
+
if (p.op === "gt" || p.op === "ge" || p.op === "lt" || p.op === "le") {
|
|
28195
|
+
const sides = [p.a, p.b];
|
|
28196
|
+
if (sides.some((t) => t.op === "var" && t.id === id) && sides.some((t) => t.op === "lit" && typeof t.value === "number")) {
|
|
28197
|
+
return true;
|
|
28198
|
+
}
|
|
28199
|
+
}
|
|
28200
|
+
}
|
|
28201
|
+
}
|
|
28202
|
+
return false;
|
|
28203
|
+
}
|
|
28204
|
+
function subAssumeBounds(a, b, resultTerm, assume) {
|
|
28205
|
+
const premises = assumePremiseList(assume);
|
|
28206
|
+
if (premises.length === 0) return void 0;
|
|
28207
|
+
if (!a.term || !b.term) return void 0;
|
|
28208
|
+
const facts = [];
|
|
28209
|
+
for (const p of premises) {
|
|
28210
|
+
if (p.op !== "le") continue;
|
|
28211
|
+
if (termEquals(p.a, a.term) && termEquals(p.b, b.term)) {
|
|
28212
|
+
facts.push(le(resultTerm, lit(0)));
|
|
28213
|
+
} else if (termEquals(p.a, b.term) && termEquals(p.b, a.term)) {
|
|
28214
|
+
facts.push(ge(resultTerm, lit(0)));
|
|
28215
|
+
}
|
|
28216
|
+
}
|
|
28217
|
+
return facts.length ? and(...facts) : void 0;
|
|
28218
|
+
}
|
|
28219
|
+
function sub(a, b, phi2 = pTrue, assume) {
|
|
27966
28220
|
{
|
|
27967
28221
|
const fa = builtinBrandToPrimitive(a, "number");
|
|
27968
28222
|
const fb = builtinBrandToPrimitive(b, "number");
|
|
27969
|
-
if (fa || fb) return sub(fa ?? a, fb ?? b, phi2);
|
|
28223
|
+
if (fa || fb) return sub(fa ?? a, fb ?? b, phi2, assume);
|
|
27970
28224
|
}
|
|
27971
28225
|
if (isSymbolAbs(a) || isSymbolAbs(b)) {
|
|
27972
28226
|
throw new NudoThrow(errorTypeAbs("TypeError"));
|
|
@@ -27985,6 +28239,12 @@ function sub(a, b, phi2 = pTrue) {
|
|
|
27985
28239
|
return numLit(Number(ra.value) - Number(rb.value));
|
|
27986
28240
|
}
|
|
27987
28241
|
if (isNumericLike(a) && isNumericLike(b) && a.term && b.term) {
|
|
28242
|
+
if (termEquals(a.term, b.term) && isDeterministicTerm(a.term) && // 域共享门(len-sharing.ts,第二类洞):同一带项 Abs 经元素域分发
|
|
28243
|
+
// 到两个下标位(xs.map(s=>s.length) 的 a[0] 与 a[1])——term 结构
|
|
28244
|
+
// 相同但代表不同具体值,$idx/回调实参分发位已标记,此处拒绝折叠。
|
|
28245
|
+
!isDomainShared(a) && !isDomainShared(b)) {
|
|
28246
|
+
return isNonNaN(a, phi2) || isNonNaN(b, phi2) ? numLit(0) : makeSum(numLit(NaN), numLit(0));
|
|
28247
|
+
}
|
|
27988
28248
|
const term = simplifyTerm(app("-", [a.term, b.term]));
|
|
27989
28249
|
const ab = numericBounds(a, phi2);
|
|
27990
28250
|
const bb = numericBounds(b, phi2);
|
|
@@ -28003,6 +28263,8 @@ function sub(a, b, phi2 = pTrue) {
|
|
|
28003
28263
|
facts.push(strict ? lt(term, lit(hi)) : le(term, lit(hi)));
|
|
28004
28264
|
}
|
|
28005
28265
|
}
|
|
28266
|
+
const assumePred = subAssumeBounds(a, b, term, assume);
|
|
28267
|
+
if (assumePred) facts.push(assumePred);
|
|
28006
28268
|
const conf = term.op === "lit" ? "exact" : confJoin(confJoin(a.conf, b.conf), facts.length ? "path" : "path");
|
|
28007
28269
|
return abs({ k: "prim", type: "number" }, term, facts.length ? and(...facts) : void 0, conf);
|
|
28008
28270
|
}
|
|
@@ -28015,7 +28277,7 @@ function sub(a, b, phi2 = pTrue) {
|
|
|
28015
28277
|
if (isAnyLike(a) || isAnyLike(b)) {
|
|
28016
28278
|
return toNumberResult(a, b, "-");
|
|
28017
28279
|
}
|
|
28018
|
-
const distributed = distributeSumBinOp("-", a, b, phi2, sub);
|
|
28280
|
+
const distributed = distributeSumBinOp("-", a, b, phi2, (x, y, p) => sub(x, y, p, assume));
|
|
28019
28281
|
if (distributed) return distributed;
|
|
28020
28282
|
noteAbstractArithMayThrow(a, b);
|
|
28021
28283
|
return abs({ k: "unknown" }, void 0, void 0, "partial");
|
|
@@ -28170,6 +28432,11 @@ function div(a, b, phi2 = pTrue, assume) {
|
|
|
28170
28432
|
}
|
|
28171
28433
|
return numLit(va / vb);
|
|
28172
28434
|
}
|
|
28435
|
+
if (va === 0 && !Object.is(va, -0)) {
|
|
28436
|
+
const bb = numericBounds(b, phi2);
|
|
28437
|
+
const excludesZero = bb !== void 0 && (bb.lo !== void 0 && (bb.lo.value > 0 || bb.lo.value === 0 && bb.lo.strict) || bb.hi !== void 0 && (bb.hi.value < 0 || bb.hi.value === 0 && bb.hi.strict));
|
|
28438
|
+
if (excludesZero) return numLit(0);
|
|
28439
|
+
}
|
|
28173
28440
|
if (ra.ok && rb.ok && coercibleLit(ra.value) && coercibleLit(rb.value)) {
|
|
28174
28441
|
return numLit(Number(va) / Number(vb));
|
|
28175
28442
|
}
|
|
@@ -28285,7 +28552,7 @@ function mod(a, b, phi2 = pTrue) {
|
|
|
28285
28552
|
return abs({ k: "unknown" }, void 0, void 0, "partial");
|
|
28286
28553
|
}
|
|
28287
28554
|
function cmp(op, a, b, phi2 = pTrue) {
|
|
28288
|
-
{
|
|
28555
|
+
if (op !== "eq" && op !== "ne") {
|
|
28289
28556
|
const fa = builtinBrandToPrimitive(a, "number");
|
|
28290
28557
|
const fb = builtinBrandToPrimitive(b, "number");
|
|
28291
28558
|
if (fa || fb) return cmp(op, fa ?? a, fb ?? b, phi2);
|
|
@@ -28470,55 +28737,6 @@ function mathMethod(name) {
|
|
|
28470
28737
|
const impl = Math[name];
|
|
28471
28738
|
return typeof impl === "function" ? impl : void 0;
|
|
28472
28739
|
}
|
|
28473
|
-
function isNonNaN(a, phi2) {
|
|
28474
|
-
if (a.term?.op === "lit") {
|
|
28475
|
-
return typeof a.term.value === "number" && !Number.isNaN(a.term.value);
|
|
28476
|
-
}
|
|
28477
|
-
if (a.shape.k === "sum") {
|
|
28478
|
-
return a.shape.members.every(
|
|
28479
|
-
(m) => isNonNaN(m, phi2)
|
|
28480
|
-
);
|
|
28481
|
-
}
|
|
28482
|
-
const hasNonNaNFact = (p) => {
|
|
28483
|
-
if (!p) return false;
|
|
28484
|
-
if (p.op === "and") return p.args.some(hasNonNaNFact);
|
|
28485
|
-
if (p.op === "assumeFinite") {
|
|
28486
|
-
return !!a.term && termEquals(p.t, a.term);
|
|
28487
|
-
}
|
|
28488
|
-
if (p.op === "ne") {
|
|
28489
|
-
if (p.b.op === "lit" && typeof p.b.value === "number" && Number.isNaN(p.b.value) && a.term && termEquals(p.a, a.term)) {
|
|
28490
|
-
return true;
|
|
28491
|
-
}
|
|
28492
|
-
}
|
|
28493
|
-
if (p.op === "gt" || p.op === "ge" || p.op === "lt" || p.op === "le") {
|
|
28494
|
-
if (a.term && (termEquals(p.a, a.term) || termEquals(p.b, a.term))) return true;
|
|
28495
|
-
}
|
|
28496
|
-
return false;
|
|
28497
|
-
};
|
|
28498
|
-
if (hasNonNaNFact(a.pred)) return true;
|
|
28499
|
-
if (a.term?.op === "var") {
|
|
28500
|
-
const id = a.term.id;
|
|
28501
|
-
const conjs = phi2.op === "and" ? phi2.args : [phi2];
|
|
28502
|
-
for (const p of conjs) {
|
|
28503
|
-
if (p.op === "assumeFinite" && p.t.op === "var" && p.t.id === id) {
|
|
28504
|
-
return true;
|
|
28505
|
-
}
|
|
28506
|
-
if (p.op === "ne") {
|
|
28507
|
-
const b = p.b;
|
|
28508
|
-
if (b.op === "lit" && typeof b.value === "number" && Number.isNaN(b.value) && p.a.op === "var" && p.a.id === id) {
|
|
28509
|
-
return true;
|
|
28510
|
-
}
|
|
28511
|
-
}
|
|
28512
|
-
if (p.op === "gt" || p.op === "ge" || p.op === "lt" || p.op === "le") {
|
|
28513
|
-
const sides = [p.a, p.b];
|
|
28514
|
-
if (sides.some((t) => t.op === "var" && t.id === id) && sides.some((t) => t.op === "lit" && typeof t.value === "number")) {
|
|
28515
|
-
return true;
|
|
28516
|
-
}
|
|
28517
|
-
}
|
|
28518
|
-
}
|
|
28519
|
-
}
|
|
28520
|
-
return false;
|
|
28521
|
-
}
|
|
28522
28740
|
function ieeeBoundNum(n) {
|
|
28523
28741
|
return Number.isFinite(n) || n === Infinity || n === -Infinity ? n : void 0;
|
|
28524
28742
|
}
|
|
@@ -28923,6 +29141,7 @@ export {
|
|
|
28923
29141
|
withExecPhi,
|
|
28924
29142
|
currentAssumePremises,
|
|
28925
29143
|
withAssumePremises,
|
|
29144
|
+
withInvariantSpecs,
|
|
28926
29145
|
asAbsVal,
|
|
28927
29146
|
$absVal,
|
|
28928
29147
|
callAtFunctionBoundary,
|
package/dist/exec.js
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -255,7 +255,7 @@ declare function asAbs(v: unknown): Abs | undefined;
|
|
|
255
255
|
|
|
256
256
|
declare function add(a: Abs, b: Abs, phi?: Phi): Abs;
|
|
257
257
|
/** 减法:a - b = a + (-b),数值上做单调性 */
|
|
258
|
-
declare function sub(a: Abs, b: Abs, phi?: Phi): Abs;
|
|
258
|
+
declare function sub(a: Abs, b: Abs, phi?: Phi, assume?: Phi): Abs;
|
|
259
259
|
/** 乘法:字面量直接求值;×正数同向缩放;×负数翻转不等式;×0 归零 */
|
|
260
260
|
declare function mul(a: Abs, b: Abs, phi?: Phi): Abs;
|
|
261
261
|
/**
|
package/dist/index.js
CHANGED
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
extractFileEnvNames,
|
|
15
15
|
extractFn,
|
|
16
16
|
extractFnBudget,
|
|
17
|
+
extractInvariantSegs,
|
|
17
18
|
extractMockModuleRecords,
|
|
18
19
|
extractNudoImportRecords,
|
|
19
20
|
extractNudoImports,
|
|
@@ -66,7 +67,7 @@ import {
|
|
|
66
67
|
takeInterfaceDiagsSince,
|
|
67
68
|
takeRefineDiags,
|
|
68
69
|
takeRefineDiagsSince
|
|
69
|
-
} from "./chunk-
|
|
70
|
+
} from "./chunk-N7AGFMSD.js";
|
|
70
71
|
import {
|
|
71
72
|
$add,
|
|
72
73
|
$arguments,
|
|
@@ -525,8 +526,9 @@ import {
|
|
|
525
526
|
v,
|
|
526
527
|
withAssumePremises,
|
|
527
528
|
withExecPhi,
|
|
529
|
+
withInvariantSpecs,
|
|
528
530
|
withVar
|
|
529
|
-
} from "./chunk-
|
|
531
|
+
} from "./chunk-W3Z77Y2I.js";
|
|
530
532
|
|
|
531
533
|
// src/environment.ts
|
|
532
534
|
function createEnvironment(parent, bindings = /* @__PURE__ */ new Map()) {
|
|
@@ -2413,6 +2415,49 @@ function assumePremisesPhiOf(name, source, file, issues) {
|
|
|
2413
2415
|
if (preds.length === 0) return void 0;
|
|
2414
2416
|
return { phi: and(...preds), display };
|
|
2415
2417
|
}
|
|
2418
|
+
function invariantSpecsOf(name, source, file, issues) {
|
|
2419
|
+
const segs = extractInvariantSegs(source, name);
|
|
2420
|
+
if (segs.length === 0) return void 0;
|
|
2421
|
+
const meta = extractFn(source, name, file);
|
|
2422
|
+
const bindable = /* @__PURE__ */ new Set([
|
|
2423
|
+
...contractParamNameSet(meta?.formals ?? []),
|
|
2424
|
+
...meta?.params ?? [],
|
|
2425
|
+
...fnTopLevelLocalNames(meta?.body)
|
|
2426
|
+
]);
|
|
2427
|
+
const surface = [...bindable].join(", ");
|
|
2428
|
+
const specs = [];
|
|
2429
|
+
const display = [];
|
|
2430
|
+
for (const seg of segs) {
|
|
2431
|
+
if (seg.kind === "bad") {
|
|
2432
|
+
issues.push({
|
|
2433
|
+
severity: "warning",
|
|
2434
|
+
code: "nudo:invariant-invalid",
|
|
2435
|
+
message: `${name}: malformed @nudo:invariant '${seg.raw}' (expected <name><path> <rel> <number>; path = [*] or .member steps)`,
|
|
2436
|
+
actual: seg.raw,
|
|
2437
|
+
expected: "<name><path> <rel> <number>",
|
|
2438
|
+
suggestion: "loop-carried bounds: @nudo:invariant dp[*][*] >= 0 \u2014 scalar: @nudo:invariant last >= 1",
|
|
2439
|
+
fn: name
|
|
2440
|
+
});
|
|
2441
|
+
continue;
|
|
2442
|
+
}
|
|
2443
|
+
if (!bindable.has(seg.name)) {
|
|
2444
|
+
issues.push({
|
|
2445
|
+
severity: "warning",
|
|
2446
|
+
code: "nudo:invariant-invalid",
|
|
2447
|
+
message: `${name}: @nudo:invariant name not bound as a parameter or top-level local (${seg.name})`,
|
|
2448
|
+
actual: seg.name,
|
|
2449
|
+
expected: surface || "(no params/locals)",
|
|
2450
|
+
suggestion: `reference one of: ${surface || "(no params or top-level locals)"}`,
|
|
2451
|
+
fn: name
|
|
2452
|
+
});
|
|
2453
|
+
continue;
|
|
2454
|
+
}
|
|
2455
|
+
specs.push({ name: seg.name, path: seg.path, rel: seg.rel, value: seg.value });
|
|
2456
|
+
display.push(seg.display);
|
|
2457
|
+
}
|
|
2458
|
+
if (specs.length === 0) return void 0;
|
|
2459
|
+
return { specs, display };
|
|
2460
|
+
}
|
|
2416
2461
|
function checkSourceInner(filePath, source, file, phi, opts, issues, signatures, names, truncated, identityOpts = opts, depsFp, sidecarFp) {
|
|
2417
2462
|
const hasRefineDirective = stripStringsKeepComments(source).includes("@nudo:contract");
|
|
2418
2463
|
const entryNames = localNamedExports(source);
|
|
@@ -2441,7 +2486,11 @@ function checkSourceInner(filePath, source, file, phi, opts, issues, signatures,
|
|
|
2441
2486
|
};
|
|
2442
2487
|
for (const name of names) {
|
|
2443
2488
|
const assumePhi = assumePremisesPhiOf(name, source, file, issues);
|
|
2444
|
-
const
|
|
2489
|
+
const inv = invariantSpecsOf(name, source, file, issues);
|
|
2490
|
+
const runSeeded = (body) => {
|
|
2491
|
+
if (assumePhi === void 0 && inv === void 0) return body();
|
|
2492
|
+
return withAssumePremises(assumePhi?.phi ?? pTrue, () => withInvariantSpecs(inv?.specs ?? [], body));
|
|
2493
|
+
};
|
|
2445
2494
|
if (opts.skips?.has(name)) {
|
|
2446
2495
|
const declared = opts.skips.get(name) ?? null;
|
|
2447
2496
|
const meta = extractFn(source, name, file);
|
|
@@ -2676,7 +2725,8 @@ function checkSourceInner(filePath, source, file, phi, opts, issues, signatures,
|
|
|
2676
2725
|
display,
|
|
2677
2726
|
eff.returns.constraint,
|
|
2678
2727
|
g.symbolic,
|
|
2679
|
-
assumePhi?.display
|
|
2728
|
+
assumePhi?.display,
|
|
2729
|
+
inv?.display
|
|
2680
2730
|
);
|
|
2681
2731
|
if (retIssues.some((i) => i.severity === "error")) returnViolated.add(name);
|
|
2682
2732
|
issues.push(...retIssues);
|
|
@@ -2877,23 +2927,33 @@ function checkSourceInner(filePath, source, file, phi, opts, issues, signatures,
|
|
|
2877
2927
|
budget: { ...budget }
|
|
2878
2928
|
};
|
|
2879
2929
|
}
|
|
2880
|
-
function checkReturnConstraint(fnName, cName, constraint, ret, assumeDisplay) {
|
|
2930
|
+
function checkReturnConstraint(fnName, cName, constraint, ret, assumeDisplay, invariantDisplay) {
|
|
2881
2931
|
const proof = assertImplies(ret, constraint);
|
|
2882
2932
|
if (proof.status === "proved") {
|
|
2933
|
+
const relied = [];
|
|
2883
2934
|
if (assumeDisplay && assumeDisplay.length > 0) {
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
2935
|
+
relied.push({
|
|
2936
|
+
severity: "info",
|
|
2937
|
+
code: "nudo:assume-relied",
|
|
2938
|
+
message: `${fnName}: return contract proved via @nudo:assume ${assumeDisplay.join("; ")}`,
|
|
2939
|
+
actual: `proof relies on user lemma${assumeDisplay.length > 1 ? "s" : ""}`,
|
|
2940
|
+
expected: "engine verifies the obligation, not the lemma itself",
|
|
2941
|
+
suggestion: "@nudo:assume is a user-supplied axiom \u2014 falsify it with concrete @nudo:case runs (nudo test)",
|
|
2942
|
+
fn: fnName
|
|
2943
|
+
});
|
|
2944
|
+
}
|
|
2945
|
+
if (invariantDisplay && invariantDisplay.length > 0) {
|
|
2946
|
+
relied.push({
|
|
2947
|
+
severity: "info",
|
|
2948
|
+
code: "nudo:invariant-relied",
|
|
2949
|
+
message: `${fnName}: return contract proved via @nudo:invariant ${invariantDisplay.join("; ")}`,
|
|
2950
|
+
actual: `proof relies on user invariant${invariantDisplay.length > 1 ? "s" : ""}`,
|
|
2951
|
+
expected: "engine verifies the obligation, not the loop invariant itself",
|
|
2952
|
+
suggestion: "@nudo:invariant is a user-supplied axiom \u2014 falsify it with concrete @nudo:case runs (nudo test)",
|
|
2953
|
+
fn: fnName
|
|
2954
|
+
});
|
|
2895
2955
|
}
|
|
2896
|
-
return
|
|
2956
|
+
return relied;
|
|
2897
2957
|
}
|
|
2898
2958
|
const push = (severity, actual, expected, suggestion) => [
|
|
2899
2959
|
{
|
package/dist/internal.js
CHANGED
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
stableAnalyzeKeySource,
|
|
22
22
|
stablePathKey,
|
|
23
23
|
stablePathKeyGraph
|
|
24
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-N7AGFMSD.js";
|
|
25
25
|
import {
|
|
26
26
|
$tryDigestSoft,
|
|
27
27
|
$tryMarkSoft,
|
|
@@ -169,7 +169,7 @@ import {
|
|
|
169
169
|
withEvalBudgetOverride,
|
|
170
170
|
withFnBudgetOverride,
|
|
171
171
|
wrapPromise
|
|
172
|
-
} from "./chunk-
|
|
172
|
+
} from "./chunk-W3Z77Y2I.js";
|
|
173
173
|
|
|
174
174
|
// src/algebra/inlay.ts
|
|
175
175
|
function listFunctions(source) {
|