@nudojs/core 1.2.2 → 1.4.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/{check-report-0B9Z66n0.d.ts → check-report-Ci1dDysO.d.ts} +102 -25
- package/dist/{chunk-TOURNNEA.js → chunk-7QUVTEYZ.js} +1154 -254
- package/dist/{chunk-4V3CRVSD.js → chunk-LFCF6BLE.js} +3486 -2107
- package/dist/exec.d.ts +590 -3
- package/dist/exec.js +21 -1
- package/dist/index.d.ts +127 -132
- package/dist/index.js +417 -269
- package/dist/internal.d.ts +111 -7
- package/dist/internal.js +102 -28
- package/dist/run-WJfj477N.d.ts +769 -0
- package/package.json +1 -1
- package/dist/hof-types-DqDpAxfh.d.ts +0 -382
- package/dist/index-BNiZvVHw.d.ts +0 -739
package/dist/index.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import "./chunk-US532PZA.js";
|
|
2
2
|
import {
|
|
3
|
+
CONSTRAINT_BUILDERS,
|
|
4
|
+
CONSTRAINT_BUILDER_NAMES,
|
|
5
|
+
CONSTRAINT_EXPR_RE,
|
|
3
6
|
NudoSidecarError,
|
|
4
7
|
SELF,
|
|
5
8
|
andC,
|
|
@@ -9,17 +12,25 @@ import {
|
|
|
9
12
|
buildArgsFromAssume,
|
|
10
13
|
canSkipLiteralCallScan,
|
|
11
14
|
collectAbsFreeVars,
|
|
15
|
+
constraintAdmitsNullish,
|
|
12
16
|
constraintToEntryAbs,
|
|
13
17
|
effectiveInterface,
|
|
14
18
|
evictGeneralizeMemoForPaths,
|
|
15
19
|
execNudoModule,
|
|
20
|
+
extractBalancedParens,
|
|
16
21
|
extractDeclaredThrows,
|
|
22
|
+
extractFileEnvNames,
|
|
17
23
|
extractFn,
|
|
24
|
+
extractFnBudget,
|
|
25
|
+
extractMockModuleRecords,
|
|
26
|
+
extractNudoImportRecords,
|
|
18
27
|
extractNudoImports,
|
|
19
28
|
extractRefineReturnFromSource,
|
|
20
29
|
extractRefinesFromSource,
|
|
30
|
+
findFnDirectiveScope,
|
|
21
31
|
fn,
|
|
22
32
|
fnConstraintToEntryReqs,
|
|
33
|
+
fnDirectiveCommentLines,
|
|
23
34
|
formatConstraint,
|
|
24
35
|
formatEffectiveInterfaceDisplay,
|
|
25
36
|
formatInterfaceTierLine,
|
|
@@ -30,6 +41,7 @@ import {
|
|
|
30
41
|
getTerm,
|
|
31
42
|
hashSource,
|
|
32
43
|
instantiateConstraint,
|
|
44
|
+
instantiateOnTerm,
|
|
33
45
|
interfaceDiagCount,
|
|
34
46
|
interfaceSourceOf,
|
|
35
47
|
interfaceTierOf,
|
|
@@ -37,6 +49,7 @@ import {
|
|
|
37
49
|
isNodeModulesPath,
|
|
38
50
|
isNudoConstraint,
|
|
39
51
|
lenTerm,
|
|
52
|
+
listFnDirectiveScopes,
|
|
40
53
|
listFunctionNames,
|
|
41
54
|
listTopFunctions,
|
|
42
55
|
litC,
|
|
@@ -44,8 +57,12 @@ import {
|
|
|
44
57
|
loadModuleDepsFingerprint,
|
|
45
58
|
localNamedExports,
|
|
46
59
|
normPath,
|
|
60
|
+
nullable,
|
|
47
61
|
number,
|
|
48
62
|
omit,
|
|
63
|
+
parseEnvPayload,
|
|
64
|
+
parseMockModulePayload,
|
|
65
|
+
parseNudoImportPayload,
|
|
49
66
|
partial,
|
|
50
67
|
pick,
|
|
51
68
|
refineDiagCount,
|
|
@@ -53,13 +70,20 @@ import {
|
|
|
53
70
|
resetGeneralizeMemo,
|
|
54
71
|
resetHashSourceCache,
|
|
55
72
|
resetNudoModuleExecCache,
|
|
73
|
+
resetSidecarLoadFailureCache,
|
|
74
|
+
scanBudgetDecl,
|
|
75
|
+
scanCaseArgSpans,
|
|
76
|
+
scanCaseTags,
|
|
77
|
+
scanContractSegments,
|
|
56
78
|
scanLiteralCalls,
|
|
79
|
+
scanThrowsDecl,
|
|
57
80
|
setInterfaceDiagCollector,
|
|
58
81
|
setRefineDiagCollector,
|
|
59
82
|
shape,
|
|
60
83
|
sidecarClosureFingerprint,
|
|
61
84
|
sidecarPathOf,
|
|
62
85
|
stableAnalyzeKeySource,
|
|
86
|
+
stablePathKey,
|
|
63
87
|
string,
|
|
64
88
|
takeInterfaceDiags,
|
|
65
89
|
takeInterfaceDiagsSince,
|
|
@@ -67,7 +91,7 @@ import {
|
|
|
67
91
|
takeRefineDiagsSince,
|
|
68
92
|
throwConstraintToKinds,
|
|
69
93
|
union
|
|
70
|
-
} from "./chunk-
|
|
94
|
+
} from "./chunk-7QUVTEYZ.js";
|
|
71
95
|
import {
|
|
72
96
|
$add,
|
|
73
97
|
$arguments,
|
|
@@ -189,10 +213,13 @@ import {
|
|
|
189
213
|
NudoThrow,
|
|
190
214
|
NudoUnsupportedError,
|
|
191
215
|
OBJECT_PROTO_METHOD_NAMES,
|
|
216
|
+
PROMISE_MICRO_ERROR_LABEL,
|
|
217
|
+
PROMISE_MICRO_OVERFLOW_LABEL,
|
|
192
218
|
RUNTIME_IMPORT_RE,
|
|
193
219
|
TYPEOF_NAMES,
|
|
194
220
|
abs,
|
|
195
221
|
absFunction,
|
|
222
|
+
absOnly,
|
|
196
223
|
absShapeKey,
|
|
197
224
|
absToString,
|
|
198
225
|
add,
|
|
@@ -206,10 +233,12 @@ import {
|
|
|
206
233
|
asAbs,
|
|
207
234
|
asAbsVal,
|
|
208
235
|
assignSourceSlots,
|
|
236
|
+
assumeFinite,
|
|
209
237
|
attachFnImpl,
|
|
210
238
|
beginCollectionFork,
|
|
211
239
|
betaOf,
|
|
212
240
|
bigintLit,
|
|
241
|
+
bindImports,
|
|
213
242
|
bindingsOf,
|
|
214
243
|
bitandAbs,
|
|
215
244
|
bitnotAbs,
|
|
@@ -222,12 +251,14 @@ import {
|
|
|
222
251
|
callAbsMethod,
|
|
223
252
|
callAtFunctionBoundary,
|
|
224
253
|
callTranspiledExport,
|
|
254
|
+
callTranspiledExportApply,
|
|
225
255
|
callTranspiledExportFull,
|
|
226
256
|
canonicalArrayIndex,
|
|
227
257
|
clearBClasses,
|
|
228
258
|
clearCollectionTables,
|
|
229
259
|
clearStaleTermPred,
|
|
230
260
|
cmp,
|
|
261
|
+
collectAbsExports,
|
|
231
262
|
collectionElementJoin,
|
|
232
263
|
collectionExactLen,
|
|
233
264
|
confJoin,
|
|
@@ -275,6 +306,7 @@ import {
|
|
|
275
306
|
foldRequireSpecArg,
|
|
276
307
|
foldStaticStringExpr,
|
|
277
308
|
formalParamDisplayNames,
|
|
309
|
+
formalParamSignatureNames,
|
|
278
310
|
formalParamsFromNodes,
|
|
279
311
|
formatAbs,
|
|
280
312
|
formatAbsMultiline,
|
|
@@ -295,8 +327,10 @@ import {
|
|
|
295
327
|
gt,
|
|
296
328
|
gtNum,
|
|
297
329
|
hostBuiltinCtorName,
|
|
330
|
+
identBoundaryRegex,
|
|
298
331
|
implies,
|
|
299
332
|
instantiateReturn,
|
|
333
|
+
isAbsApplyResult,
|
|
300
334
|
isArrMutator,
|
|
301
335
|
isBigPrim,
|
|
302
336
|
isDefinitelyFalse,
|
|
@@ -333,6 +367,7 @@ import {
|
|
|
333
367
|
looseEqAbs,
|
|
334
368
|
lt,
|
|
335
369
|
ltNum,
|
|
370
|
+
makeAbsApplyResult,
|
|
336
371
|
makeArrayCtorAbs,
|
|
337
372
|
makeMapAbs,
|
|
338
373
|
makeSetAbs,
|
|
@@ -357,6 +392,7 @@ import {
|
|
|
357
392
|
migrateNullProto,
|
|
358
393
|
mod,
|
|
359
394
|
mul,
|
|
395
|
+
namespaceAbsOf,
|
|
360
396
|
namespaceNameOf,
|
|
361
397
|
ne,
|
|
362
398
|
negAbs,
|
|
@@ -438,6 +474,7 @@ import {
|
|
|
438
474
|
strLit,
|
|
439
475
|
strictEqAbs,
|
|
440
476
|
stringOfSymbol,
|
|
477
|
+
stripStringsKeepComments,
|
|
441
478
|
stripTypes,
|
|
442
479
|
sub,
|
|
443
480
|
substAbs,
|
|
@@ -470,7 +507,7 @@ import {
|
|
|
470
507
|
v,
|
|
471
508
|
withExecPhi,
|
|
472
509
|
withVar
|
|
473
|
-
} from "./chunk-
|
|
510
|
+
} from "./chunk-LFCF6BLE.js";
|
|
474
511
|
|
|
475
512
|
// src/environment.ts
|
|
476
513
|
function createEnvironment(parent, bindings = /* @__PURE__ */ new Map()) {
|
|
@@ -599,15 +636,15 @@ function checkArg(arg, expect, phi = pTrue) {
|
|
|
599
636
|
}
|
|
600
637
|
const combined = arg.pred && arg.pred.op !== "true" ? phi.op === "true" ? arg.pred : { op: "and", args: [phi, arg.pred] } : phi;
|
|
601
638
|
if (implies(combined, expect)) return void 0;
|
|
602
|
-
const
|
|
603
|
-
if (
|
|
604
|
-
const ok = decideLitCmp(
|
|
639
|
+
const lvR = litValue(arg);
|
|
640
|
+
if (lvR.ok && (expect.op === "gt" || expect.op === "ge" || expect.op === "lt" || expect.op === "le")) {
|
|
641
|
+
const ok = decideLitCmp(lvR.value, expect);
|
|
605
642
|
if (ok === true) return void 0;
|
|
606
643
|
if (ok === false) {
|
|
607
644
|
return {
|
|
608
645
|
severity: "error",
|
|
609
646
|
code: "nudo:constraint-violated",
|
|
610
|
-
message: `argument ${JSON.stringify(
|
|
647
|
+
message: `argument ${JSON.stringify(lvR.value)} does not satisfy ${predToString(expect)}`,
|
|
611
648
|
suggestion: `use a value satisfying the constraint, or relax the function precondition`
|
|
612
649
|
};
|
|
613
650
|
}
|
|
@@ -703,6 +740,257 @@ function formatDiagnostics(diags) {
|
|
|
703
740
|
}).join("\n");
|
|
704
741
|
}
|
|
705
742
|
|
|
743
|
+
// src/algebra/postcondition.ts
|
|
744
|
+
function proved() {
|
|
745
|
+
return { status: "proved" };
|
|
746
|
+
}
|
|
747
|
+
function disproved(reason) {
|
|
748
|
+
return { status: "disproved", reason };
|
|
749
|
+
}
|
|
750
|
+
function unprovable(reason) {
|
|
751
|
+
return { status: "unprovable", reason };
|
|
752
|
+
}
|
|
753
|
+
var RET_ANCHOR = "__nudo_return__";
|
|
754
|
+
function retAnchorTerm(ret) {
|
|
755
|
+
return ret.term ?? v(RET_ANCHOR);
|
|
756
|
+
}
|
|
757
|
+
function isValueSetUnknown(ret) {
|
|
758
|
+
if (ret.conf === "opaque" || ret.conf === "widened" || ret.conf === "mock") {
|
|
759
|
+
return true;
|
|
760
|
+
}
|
|
761
|
+
if (ret.shape.k === "unknown" && !ret.term) return true;
|
|
762
|
+
if (ret.shape.k === "any") return true;
|
|
763
|
+
return false;
|
|
764
|
+
}
|
|
765
|
+
function isBoundedEvidenceMissing(ret, constraint) {
|
|
766
|
+
if (constraint.preds.length === 0 && !constraint.members) return false;
|
|
767
|
+
if (ret.term?.op === "lit") return false;
|
|
768
|
+
if (ret.pred && ret.pred.op !== "true") return false;
|
|
769
|
+
return true;
|
|
770
|
+
}
|
|
771
|
+
function assertImplies(ret, constraint, opts) {
|
|
772
|
+
const phi = opts?.phi ?? pTrue;
|
|
773
|
+
if (ret.shape.k === "never") return proved();
|
|
774
|
+
if (isAnyConstraint(constraint)) return proved();
|
|
775
|
+
const nullishOk = constraintAdmitsNullish(constraint);
|
|
776
|
+
if (isNullishLitAbs(ret)) {
|
|
777
|
+
return nullishOk ? proved() : disproved(`nullish return \u22AD ${formatConstraint(constraint)}`);
|
|
778
|
+
}
|
|
779
|
+
let sumMembers;
|
|
780
|
+
if (ret.shape.k === "sum") {
|
|
781
|
+
const members = ret.shape.members;
|
|
782
|
+
const nullishMembers = members.filter((m) => isNullishLitAbs(m));
|
|
783
|
+
if (nullishMembers.length > 0 && !nullishOk) {
|
|
784
|
+
return disproved(
|
|
785
|
+
`nullish return arm \u22AD ${formatConstraint(constraint)} (contract does not admit nullish)`
|
|
786
|
+
);
|
|
787
|
+
}
|
|
788
|
+
sumMembers = members.filter((m) => !isNullishLitAbs(m));
|
|
789
|
+
}
|
|
790
|
+
if (isValueSetUnknown(ret)) {
|
|
791
|
+
return unprovable(
|
|
792
|
+
ret.shape.k === "any" ? "any does not establish the return contract" : "inference failure (unknown/opaque) \u2014 cannot prove the return contract"
|
|
793
|
+
);
|
|
794
|
+
}
|
|
795
|
+
if (constraint.members && constraint.members.length > 0) {
|
|
796
|
+
let sawUnprovable = false;
|
|
797
|
+
for (const m of constraint.members) {
|
|
798
|
+
const r = assertImplies(ret, m, opts);
|
|
799
|
+
if (r.status === "proved") return proved();
|
|
800
|
+
if (r.status === "unprovable") sawUnprovable = true;
|
|
801
|
+
}
|
|
802
|
+
return sawUnprovable ? unprovable("union: no member proved the return contract") : disproved(`return \u22AD union(${constraint.members.map((m) => formatConstraint(m)).join(", ")})`);
|
|
803
|
+
}
|
|
804
|
+
if (sumMembers && (constraint.fields || constraint.element)) {
|
|
805
|
+
let sawUnprovable = false;
|
|
806
|
+
for (const m of sumMembers) {
|
|
807
|
+
if (isValueSetUnknown(m)) continue;
|
|
808
|
+
const r = assertImplies(m, constraint, opts);
|
|
809
|
+
if (r.status === "disproved") return r;
|
|
810
|
+
if (r.status === "unprovable") sawUnprovable = true;
|
|
811
|
+
}
|
|
812
|
+
return sawUnprovable ? unprovable("sum: some arms unprovable") : proved();
|
|
813
|
+
}
|
|
814
|
+
if (constraint.fields) {
|
|
815
|
+
return assertShapeFields(ret, constraint, opts);
|
|
816
|
+
}
|
|
817
|
+
if (constraint.element) {
|
|
818
|
+
if (ret.shape.k !== "arr" && ret.shape.k !== "tuple") {
|
|
819
|
+
return disproved(`return shape ${ret.shape.k} \u22AD array(...)`);
|
|
820
|
+
}
|
|
821
|
+
return proved();
|
|
822
|
+
}
|
|
823
|
+
if (constraint.fn) return proved();
|
|
824
|
+
return assertScalar(ret, constraint, phi);
|
|
825
|
+
}
|
|
826
|
+
function isAnyConstraint(c) {
|
|
827
|
+
return !c.prim && c.preds.length === 0 && !c.fields && !c.element && !c.members && !c.fn && !isIntFlag(c);
|
|
828
|
+
}
|
|
829
|
+
function assertShapeFields(ret, constraint, opts) {
|
|
830
|
+
let slots;
|
|
831
|
+
if (ret.shape.k === "obj") {
|
|
832
|
+
slots = ret.shape.slots;
|
|
833
|
+
} else if (ret.shape.k === "brand") {
|
|
834
|
+
const inner = ret.shape.shape;
|
|
835
|
+
if (inner.shape.k === "obj") slots = inner.shape.slots;
|
|
836
|
+
}
|
|
837
|
+
if (!slots) {
|
|
838
|
+
return disproved(`return shape ${ret.shape.k} \u22AD object shape`);
|
|
839
|
+
}
|
|
840
|
+
for (const [key, field] of Object.entries(constraint.fields)) {
|
|
841
|
+
const slot = getSlot(slots, key);
|
|
842
|
+
if (!slot) {
|
|
843
|
+
if (!field.optional && !field.constraint.isOptional) {
|
|
844
|
+
return disproved(`missing field ${key}`);
|
|
845
|
+
}
|
|
846
|
+
continue;
|
|
847
|
+
}
|
|
848
|
+
const r = assertImplies(slot.value, field.constraint, opts);
|
|
849
|
+
if (r.status === "disproved") {
|
|
850
|
+
return disproved(`field ${key}: ${r.reason}`);
|
|
851
|
+
}
|
|
852
|
+
if (r.status === "unprovable") {
|
|
853
|
+
return unprovable(`field ${key}: ${r.reason}`);
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
return proved();
|
|
857
|
+
}
|
|
858
|
+
function assertScalar(ret, constraint, phi) {
|
|
859
|
+
if (constraint.prim) {
|
|
860
|
+
const retPrim = primOfAbs(ret);
|
|
861
|
+
if (retPrim && retPrim !== constraint.prim) {
|
|
862
|
+
return disproved(`typeof return = "${retPrim}" \u22AD "${constraint.prim}"`);
|
|
863
|
+
}
|
|
864
|
+
if (!retPrim) {
|
|
865
|
+
const k = ret.shape.k;
|
|
866
|
+
if (k === "obj" || k === "arr" || k === "tuple" || k === "fn" || k === "brand" || k === "eff") {
|
|
867
|
+
return disproved(`return shape ${k} \u22AD prim "${constraint.prim}"`);
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
if (isIntFlag(constraint) && constraint.prim === "number") {
|
|
872
|
+
const lvR = litValue(ret);
|
|
873
|
+
const lv = lvR.ok ? lvR.value : void 0;
|
|
874
|
+
if (typeof lv === "number" && !Number.isInteger(lv)) {
|
|
875
|
+
return disproved(`return ${lv} \u22AD int`);
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
const t = retAnchorTerm(ret);
|
|
879
|
+
const constraintPred = instantiateOnTerm(constraint, t);
|
|
880
|
+
const retPred = ret.pred && ret.pred.op !== "true" ? phi.op === "true" ? ret.pred : and(phi, ret.pred) : phi;
|
|
881
|
+
if (constraint.preds.length === 0 && constraintPred.op === "true") {
|
|
882
|
+
return proved();
|
|
883
|
+
}
|
|
884
|
+
if (constraint.preds.length === 0 && constraintPred.op === "typeof" && constraint.prim) {
|
|
885
|
+
return proved();
|
|
886
|
+
}
|
|
887
|
+
if (constraintPred.op !== "true") {
|
|
888
|
+
if (implies(retPred, constraintPred)) return proved();
|
|
889
|
+
if (ret.pred && ret.pred.op !== "true" && numericBoundsImplyLocal(ret.pred, constraintPred)) {
|
|
890
|
+
return proved();
|
|
891
|
+
}
|
|
892
|
+
if (isBoundedEvidenceMissing(ret, constraint)) {
|
|
893
|
+
return unprovable(
|
|
894
|
+
`cannot prove return satisfies ${formatConstraint(constraint)} (no bound evidence on the return value)`
|
|
895
|
+
);
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
if (ret.term?.op === "lit") {
|
|
899
|
+
const lv = ret.term.value;
|
|
900
|
+
if (typeof lv === "number" || typeof lv === "string" || typeof lv === "boolean" || lv === null || lv === void 0) {
|
|
901
|
+
if (literalMeetsConstraint(lv, constraint)) {
|
|
902
|
+
return proved();
|
|
903
|
+
}
|
|
904
|
+
const boundDesc = describeBoundViolation(lv, constraint);
|
|
905
|
+
return disproved(
|
|
906
|
+
boundDesc ?? `return ${JSON.stringify(lv)} \u22AD ${formatConstraint(constraint)}`
|
|
907
|
+
);
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
if (constraintPred.op !== "true" || constraint.preds.length > 0) {
|
|
911
|
+
if (isBoundedEvidenceMissing(ret, constraint)) {
|
|
912
|
+
return unprovable(
|
|
913
|
+
`cannot prove return satisfies ${formatConstraint(constraint)} (no bound evidence on the return value)`
|
|
914
|
+
);
|
|
915
|
+
}
|
|
916
|
+
return disproved(
|
|
917
|
+
`return (${formatAbs(ret)}) \u22AD ${formatConstraint(constraint)} (postcondition not implied)`
|
|
918
|
+
);
|
|
919
|
+
}
|
|
920
|
+
return proved();
|
|
921
|
+
}
|
|
922
|
+
function numericBoundsImplyLocal(src, tgt) {
|
|
923
|
+
const one = (p) => p.op === "gt" || p.op === "ge" || p.op === "lt" || p.op === "le";
|
|
924
|
+
if (!one(src) || !one(tgt)) return false;
|
|
925
|
+
if (src.op !== tgt.op) {
|
|
926
|
+
if (src.op === "gt" && tgt.op === "ge") {
|
|
927
|
+
return sameTermSide(src, tgt) && litGE(src.b, tgt.b);
|
|
928
|
+
}
|
|
929
|
+
if (src.op === "lt" && tgt.op === "le") {
|
|
930
|
+
return sameTermSide(src, tgt) && litLE(src.b, tgt.b);
|
|
931
|
+
}
|
|
932
|
+
return false;
|
|
933
|
+
}
|
|
934
|
+
if (!sameTermSide(src, tgt)) return false;
|
|
935
|
+
const sb = src.b.op === "lit" ? src.b.value : void 0;
|
|
936
|
+
const tb = tgt.b.op === "lit" ? tgt.b.value : void 0;
|
|
937
|
+
if (typeof sb !== "number" || typeof tb !== "number") return false;
|
|
938
|
+
if (src.op === "gt" || src.op === "ge") return sb >= tb;
|
|
939
|
+
return sb <= tb;
|
|
940
|
+
}
|
|
941
|
+
function sameTermSide(a, b) {
|
|
942
|
+
return a.a.op === b.a.op && termEquals(a.a, b.a);
|
|
943
|
+
}
|
|
944
|
+
function litGE(a, b) {
|
|
945
|
+
if (a.op !== "lit" || b.op !== "lit") return false;
|
|
946
|
+
return typeof a.value === "number" && typeof b.value === "number" && a.value >= b.value;
|
|
947
|
+
}
|
|
948
|
+
function litLE(a, b) {
|
|
949
|
+
if (a.op !== "lit" || b.op !== "lit") return false;
|
|
950
|
+
return typeof a.value === "number" && typeof b.value === "number" && a.value <= b.value;
|
|
951
|
+
}
|
|
952
|
+
function primOfAbs(a) {
|
|
953
|
+
if (a.shape.k === "prim") return a.shape.type;
|
|
954
|
+
const lvR = litValue(a);
|
|
955
|
+
const lv = lvR.ok ? lvR.value : void 0;
|
|
956
|
+
if (typeof lv === "number") return "number";
|
|
957
|
+
if (typeof lv === "string") return "string";
|
|
958
|
+
if (typeof lv === "boolean") return "boolean";
|
|
959
|
+
if (typeof lv === "bigint") return "bigint";
|
|
960
|
+
return void 0;
|
|
961
|
+
}
|
|
962
|
+
function describeBoundViolation(lv, constraint) {
|
|
963
|
+
const opSym = { gt: ">", ge: "\u2265", lt: "<", le: "\u2264" };
|
|
964
|
+
for (const p of constraint.preds) {
|
|
965
|
+
for (const atom of p.op === "and" ? p.args : [p]) {
|
|
966
|
+
if (atom.op !== "gt" && atom.op !== "ge" && atom.op !== "lt" && atom.op !== "le") continue;
|
|
967
|
+
if (atom.b.op !== "lit" || typeof atom.b.value !== "number") continue;
|
|
968
|
+
const n = atom.b.value;
|
|
969
|
+
const sym = opSym[atom.op];
|
|
970
|
+
if (typeof lv === "number") {
|
|
971
|
+
let ok = true;
|
|
972
|
+
if (atom.op === "gt") ok = lv > n;
|
|
973
|
+
if (atom.op === "ge") ok = lv >= n;
|
|
974
|
+
if (atom.op === "lt") ok = lv < n;
|
|
975
|
+
if (atom.op === "le") ok = lv <= n;
|
|
976
|
+
if (!ok) return `return ${sym} ${n}`;
|
|
977
|
+
}
|
|
978
|
+
if (typeof lv === "string" && isLengthSelfTerm(atom.a)) {
|
|
979
|
+
let ok = true;
|
|
980
|
+
if (atom.op === "gt") ok = lv.length > n;
|
|
981
|
+
if (atom.op === "ge") ok = lv.length >= n;
|
|
982
|
+
if (atom.op === "lt") ok = lv.length < n;
|
|
983
|
+
if (atom.op === "le") ok = lv.length <= n;
|
|
984
|
+
if (!ok) return `length(self) ${sym} ${n}`;
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
return void 0;
|
|
989
|
+
}
|
|
990
|
+
function isLengthSelfTerm(t) {
|
|
991
|
+
return t.op === "app" && t.fn === "length" && t.args.length === 1 && t.args[0].op === "var" && t.args[0].id === SELF;
|
|
992
|
+
}
|
|
993
|
+
|
|
706
994
|
// src/algebra/projection.ts
|
|
707
995
|
var PROJECTABLE_CONF = /* @__PURE__ */ new Set(["exact", "path"]);
|
|
708
996
|
var ARR_FALLBACK_CONF = /* @__PURE__ */ new Set([
|
|
@@ -1137,8 +1425,9 @@ function estimateEntryParamCount(source, fnName, file) {
|
|
|
1137
1425
|
}
|
|
1138
1426
|
} catch {
|
|
1139
1427
|
}
|
|
1428
|
+
const name = identBoundaryRegex(fnName);
|
|
1140
1429
|
const re = new RegExp(
|
|
1141
|
-
`(?:function\\s+${
|
|
1430
|
+
`(?:function\\s+${name}\\s*\\(([^)]*)\\)|${name}\\s*=\\s*(?:async\\s*)?function(?:\\s+${name})?\\s*\\(([^)]*)\\)|${name}\\s*=\\s*(?:async\\s*)?\\(([^)]*)\\)\\s*=>)`
|
|
1142
1431
|
);
|
|
1143
1432
|
const m = re.exec(source);
|
|
1144
1433
|
if (m) {
|
|
@@ -1149,13 +1438,15 @@ function estimateEntryParamCount(source, fnName, file) {
|
|
|
1149
1438
|
return 1;
|
|
1150
1439
|
}
|
|
1151
1440
|
function isDefaultExportName(source, fnName) {
|
|
1441
|
+
const name = identBoundaryRegex(fnName);
|
|
1152
1442
|
const re = new RegExp(
|
|
1153
|
-
`export\\s+default\\s+(?:async\\s+)?(?:function\\s+${
|
|
1443
|
+
`export\\s+default\\s+(?:async\\s+)?(?:function\\s+${name}|${name})`
|
|
1154
1444
|
);
|
|
1155
1445
|
return re.test(source);
|
|
1156
1446
|
}
|
|
1157
1447
|
function formatEntrySigLine(name, g, throws) {
|
|
1158
|
-
const
|
|
1448
|
+
const names = g.formals && g.formals.length > 0 ? formalParamSignatureNames(g.formals) : g.params;
|
|
1449
|
+
const ps = names.map((p, i) => {
|
|
1159
1450
|
const t = g.typeParams[i]?.value;
|
|
1160
1451
|
const shown = !t ? "any" : t.shape.k === "unknown" ? "unknown" : formatShape(t);
|
|
1161
1452
|
return `${p}: ${shown}`;
|
|
@@ -1219,6 +1510,7 @@ function resetCheckSourceMemo() {
|
|
|
1219
1510
|
checkKeyDeps.clear();
|
|
1220
1511
|
checkDepIndex.clear();
|
|
1221
1512
|
resetHashSourceCache();
|
|
1513
|
+
resetSidecarLoadFailureCache();
|
|
1222
1514
|
}
|
|
1223
1515
|
function loadModuleId(fn2) {
|
|
1224
1516
|
if (!fn2) return 0;
|
|
@@ -1249,7 +1541,7 @@ function unindexCheckKey(key) {
|
|
|
1249
1541
|
function evictCheckSourceMemoForPaths(paths) {
|
|
1250
1542
|
let n = 0;
|
|
1251
1543
|
for (const raw of paths) {
|
|
1252
|
-
const p =
|
|
1544
|
+
const p = stablePathKey(raw);
|
|
1253
1545
|
const keys = checkDepIndex.get(p);
|
|
1254
1546
|
if (!keys) continue;
|
|
1255
1547
|
for (const key of [...keys]) {
|
|
@@ -1325,8 +1617,9 @@ function checkMemoSet(key, value, depPaths) {
|
|
|
1325
1617
|
}
|
|
1326
1618
|
checkReportMemo.set(key, value);
|
|
1327
1619
|
if (depPaths.length > 0) {
|
|
1328
|
-
|
|
1329
|
-
|
|
1620
|
+
const normDeps = [...new Set(depPaths.map(stablePathKey))];
|
|
1621
|
+
checkKeyDeps.set(key, normDeps);
|
|
1622
|
+
for (const p of normDeps) {
|
|
1330
1623
|
let set = checkDepIndex.get(p);
|
|
1331
1624
|
if (!set) {
|
|
1332
1625
|
set = /* @__PURE__ */ new Set();
|
|
@@ -1525,8 +1818,9 @@ function projectCaseArgField(arg, field) {
|
|
|
1525
1818
|
}
|
|
1526
1819
|
function scanCaseInconsistency(source, knownFns, opts) {
|
|
1527
1820
|
const out = [];
|
|
1528
|
-
|
|
1529
|
-
|
|
1821
|
+
const directiveSrc = stripStringsKeepComments(source);
|
|
1822
|
+
if (!directiveSrc.includes("@nudo:case")) return out;
|
|
1823
|
+
const hasContractOrigin = directiveSrc.includes("@nudo:contract") || opts.sidecarPresent === true;
|
|
1530
1824
|
if (!hasContractOrigin) return out;
|
|
1531
1825
|
const file = opts.file ?? parseSource(source);
|
|
1532
1826
|
const parseLitArg = (s) => {
|
|
@@ -1650,13 +1944,14 @@ function scanCaseInconsistency(source, knownFns, opts) {
|
|
|
1650
1944
|
}
|
|
1651
1945
|
arg = projected;
|
|
1652
1946
|
}
|
|
1653
|
-
const
|
|
1654
|
-
|
|
1947
|
+
const argLit = arg.term?.op === "lit" ? arg.term.value : void 0;
|
|
1948
|
+
const argIsLit = arg.term?.op === "lit";
|
|
1949
|
+
if (!argIsLit || typeof argLit !== "number" && typeof argLit !== "string" && typeof argLit !== "boolean" && argLit !== null && argLit !== void 0) {
|
|
1655
1950
|
continue;
|
|
1656
1951
|
}
|
|
1657
1952
|
const hasEqOr = (entry.constraint.members?.length ?? 0) > 0 || entry.constraint.preds.some((p) => p.op === "eq");
|
|
1658
1953
|
if (hasEqOr) {
|
|
1659
|
-
if (!literalMeetsConstraint(
|
|
1954
|
+
if (!literalMeetsConstraint(argLit, entry.constraint)) {
|
|
1660
1955
|
const paramName = entry.param || paramNames[idx] || `arg${idx}`;
|
|
1661
1956
|
out.push({
|
|
1662
1957
|
severity: "error",
|
|
@@ -1671,16 +1966,16 @@ function scanCaseInconsistency(source, knownFns, opts) {
|
|
|
1671
1966
|
}
|
|
1672
1967
|
continue;
|
|
1673
1968
|
}
|
|
1674
|
-
if (typeof
|
|
1969
|
+
if (typeof argLit !== "number") continue;
|
|
1675
1970
|
const flatten = (p) => p.op === "and" ? p.args.flatMap(flatten) : p.op === "true" ? [] : [p];
|
|
1676
1971
|
for (const p of flatten(entry.pred)) {
|
|
1677
1972
|
if ((p.op === "gt" || p.op === "ge" || p.op === "lt" || p.op === "le") && p.b.op === "lit" && typeof p.b.value === "number") {
|
|
1678
1973
|
const n = p.b.value;
|
|
1679
1974
|
let ok = true;
|
|
1680
|
-
if (p.op === "gt") ok =
|
|
1681
|
-
if (p.op === "ge") ok =
|
|
1682
|
-
if (p.op === "lt") ok =
|
|
1683
|
-
if (p.op === "le") ok =
|
|
1975
|
+
if (p.op === "gt") ok = argLit > n;
|
|
1976
|
+
if (p.op === "ge") ok = argLit >= n;
|
|
1977
|
+
if (p.op === "lt") ok = argLit < n;
|
|
1978
|
+
if (p.op === "le") ok = argLit <= n;
|
|
1684
1979
|
if (!ok) {
|
|
1685
1980
|
const paramName = entry.param || paramNames[idx] || `arg${idx}`;
|
|
1686
1981
|
out.push({
|
|
@@ -1698,36 +1993,18 @@ function scanCaseInconsistency(source, knownFns, opts) {
|
|
|
1698
1993
|
}
|
|
1699
1994
|
}
|
|
1700
1995
|
};
|
|
1701
|
-
const
|
|
1702
|
-
if (!
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
decl.declarations[0].init.type
|
|
1711
|
-
)
|
|
1712
|
-
))) {
|
|
1713
|
-
const id = decl.type === "FunctionDeclaration" ? decl.id?.name : decl.declarations[0]?.id?.name;
|
|
1714
|
-
if (id && knownFns.includes(id)) {
|
|
1715
|
-
for (const c of obj2.leadingComments ?? []) {
|
|
1716
|
-
const caseArgs = parseCaseArgs(c.value);
|
|
1717
|
-
if (!caseArgs) continue;
|
|
1718
|
-
const caseName = /@nudo:case\s+"([^"]+)"/.exec(c.value)?.[1] ?? "?";
|
|
1719
|
-
checkCaseAgainstReqs(id, caseName, caseArgs, c.loc?.start.line);
|
|
1720
|
-
}
|
|
1996
|
+
for (const scope of listFnDirectiveScopes(file)) {
|
|
1997
|
+
if (!knownFns.includes(scope.name)) continue;
|
|
1998
|
+
for (let ci = 0; ci < scope.commentTexts.length; ci++) {
|
|
1999
|
+
const text = scope.commentTexts[ci];
|
|
2000
|
+
const startLine = scope.commentStartLines[ci] ?? 0;
|
|
2001
|
+
for (const tag of scanCaseTags(text)) {
|
|
2002
|
+
const caseArgs = parseCaseArgs(`@nudo:case "${tag.name}" (${tag.argsText})`);
|
|
2003
|
+
if (!caseArgs) continue;
|
|
2004
|
+
checkCaseAgainstReqs(scope.name, tag.name, caseArgs, startLine);
|
|
1721
2005
|
}
|
|
1722
2006
|
}
|
|
1723
|
-
|
|
1724
|
-
if (key === "loc" || key === "start" || key === "end" || key === "leadingComments") continue;
|
|
1725
|
-
const val = obj2[key];
|
|
1726
|
-
if (Array.isArray(val)) val.forEach(visit);
|
|
1727
|
-
else if (val && typeof val === "object") visit(val);
|
|
1728
|
-
}
|
|
1729
|
-
};
|
|
1730
|
-
visit(file);
|
|
2007
|
+
}
|
|
1731
2008
|
return out;
|
|
1732
2009
|
}
|
|
1733
2010
|
|
|
@@ -1749,6 +2026,7 @@ function checkSource(filePath, source, phi = pTrue, opts = {}) {
|
|
|
1749
2026
|
}
|
|
1750
2027
|
setRefineDiagCollector(null);
|
|
1751
2028
|
setInterfaceDiagCollector(null);
|
|
2029
|
+
resetSidecarLoadFailureCache();
|
|
1752
2030
|
const refineSince = refineDiagCount();
|
|
1753
2031
|
const ifaceSince = interfaceDiagCount();
|
|
1754
2032
|
const useMemo = phi.op === "true";
|
|
@@ -1819,12 +2097,13 @@ function sidecarDiagIssues(diags) {
|
|
|
1819
2097
|
const k = `${d.code}\0${d.message}`;
|
|
1820
2098
|
if (seen.has(k)) continue;
|
|
1821
2099
|
seen.add(k);
|
|
1822
|
-
|
|
2100
|
+
const severity = d.code === "nudo:contract-syntax" || d.code === "nudo:directive-syntax" ? "warning" : "error";
|
|
2101
|
+
out.push({ severity, code: d.code, message: d.message });
|
|
1823
2102
|
}
|
|
1824
2103
|
return out;
|
|
1825
2104
|
}
|
|
1826
2105
|
function checkSourceInner(filePath, source, file, phi, opts, issues, signatures, names, truncated, identityOpts = opts, depsFp, sidecarFp) {
|
|
1827
|
-
const hasRefineDirective = source
|
|
2106
|
+
const hasRefineDirective = stripStringsKeepComments(source).includes("@nudo:contract");
|
|
1828
2107
|
const exportedNames = sidecarFp !== void 0 ? localNamedExports(source) : void 0;
|
|
1829
2108
|
const entryNames = localNamedExports(source);
|
|
1830
2109
|
const entryThrowsMode = opts.entryThrows ?? "error";
|
|
@@ -1889,7 +2168,7 @@ function checkSourceInner(filePath, source, file, phi, opts, issues, signatures,
|
|
|
1889
2168
|
eff.returns.constraint,
|
|
1890
2169
|
declared
|
|
1891
2170
|
);
|
|
1892
|
-
if (retIssues.
|
|
2171
|
+
if (retIssues.some((i) => i.severity === "error")) returnViolated.add(name);
|
|
1893
2172
|
issues.push(...retIssues);
|
|
1894
2173
|
}
|
|
1895
2174
|
continue;
|
|
@@ -1935,7 +2214,7 @@ function checkSourceInner(filePath, source, file, phi, opts, issues, signatures,
|
|
|
1935
2214
|
message: `${name} (export): may throw ${gateDisplay}`,
|
|
1936
2215
|
actual: `${name}(\u2026) throws ${gateDisplay}`,
|
|
1937
2216
|
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`,
|
|
2217
|
+
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
2218
|
fn: name,
|
|
1940
2219
|
...loc.line !== void 0 ? { line: loc.line } : {},
|
|
1941
2220
|
...loc.column !== void 0 ? { column: loc.column } : {}
|
|
@@ -1980,7 +2259,7 @@ function checkSourceInner(filePath, source, file, phi, opts, issues, signatures,
|
|
|
1980
2259
|
message: `${name} (export): may throw ${gateDisplay}`,
|
|
1981
2260
|
actual: `${formatEntrySigLine(name, g, gateDisplay)}`,
|
|
1982
2261
|
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`,
|
|
2262
|
+
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
2263
|
fn: name,
|
|
1985
2264
|
...loc.line !== void 0 ? { line: loc.line } : {},
|
|
1986
2265
|
...loc.column !== void 0 ? { column: loc.column } : {}
|
|
@@ -1998,7 +2277,8 @@ function checkSourceInner(filePath, source, file, phi, opts, issues, signatures,
|
|
|
1998
2277
|
});
|
|
1999
2278
|
signatures.push({
|
|
2000
2279
|
name,
|
|
2001
|
-
|
|
2280
|
+
// 解构形参展示 `{ grade, findings }`,不落 `_p0`(#64:契约要上屏)
|
|
2281
|
+
params: g.formals && g.formals.length > 0 ? formalParamSignatureNames(g.formals) : g.params,
|
|
2002
2282
|
paramTypes,
|
|
2003
2283
|
abs: g.symbolic,
|
|
2004
2284
|
display: throwsDisplay ? `${fmt.display} throws ${throwsDisplay}` : fmt.display,
|
|
@@ -2078,7 +2358,7 @@ function checkSourceInner(filePath, source, file, phi, opts, issues, signatures,
|
|
|
2078
2358
|
eff.returns.constraint,
|
|
2079
2359
|
g.symbolic
|
|
2080
2360
|
);
|
|
2081
|
-
if (retIssues.
|
|
2361
|
+
if (retIssues.some((i) => i.severity === "error")) returnViolated.add(name);
|
|
2082
2362
|
issues.push(...retIssues);
|
|
2083
2363
|
}
|
|
2084
2364
|
if (eff && eff.source === "generated") {
|
|
@@ -2105,7 +2385,25 @@ function checkSourceInner(filePath, source, file, phi, opts, issues, signatures,
|
|
|
2105
2385
|
severity: "info",
|
|
2106
2386
|
code: "nudo:fork-truncated",
|
|
2107
2387
|
message: `Branch expansion was truncated (fork budget); affected results widened to unknown#opaque (budget)`,
|
|
2108
|
-
suggestion: "optional:
|
|
2388
|
+
suggestion: "optional: raise nudo.analysis.maxForks, or @nudo:budget forks=\u2026 on the function \u2014 non-blocking"
|
|
2389
|
+
});
|
|
2390
|
+
continue;
|
|
2391
|
+
}
|
|
2392
|
+
if (label === PROMISE_MICRO_OVERFLOW_LABEL) {
|
|
2393
|
+
issues.push({
|
|
2394
|
+
severity: "info",
|
|
2395
|
+
code: "nudo:promise-micro-truncated",
|
|
2396
|
+
message: `Promise microtask queue hit its hard cap; further then/catch callbacks were dropped (results stay fail-closed)`,
|
|
2397
|
+
suggestion: "optional: reduce top-level Promise.then fan-out \u2014 non-blocking"
|
|
2398
|
+
});
|
|
2399
|
+
continue;
|
|
2400
|
+
}
|
|
2401
|
+
if (label === PROMISE_MICRO_ERROR_LABEL) {
|
|
2402
|
+
issues.push({
|
|
2403
|
+
severity: "info",
|
|
2404
|
+
code: "nudo:promise-micro-error",
|
|
2405
|
+
message: `A promise microtask threw while draining; the error was recorded and did not rewrite the synchronous return value`,
|
|
2406
|
+
suggestion: "optional: inspect the then/catch callback for a throw path \u2014 non-blocking"
|
|
2109
2407
|
});
|
|
2110
2408
|
continue;
|
|
2111
2409
|
}
|
|
@@ -2124,7 +2422,7 @@ function checkSourceInner(filePath, source, file, phi, opts, issues, signatures,
|
|
|
2124
2422
|
severity: "info",
|
|
2125
2423
|
code: "nudo:recursion-truncated",
|
|
2126
2424
|
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
|
|
2425
|
+
suggestion: "raise the function budget with @nudo:budget calls=\u2026 depth=\u2026 (or narrow the recursion base case / declare @nudo:contract return)",
|
|
2128
2426
|
fn: label
|
|
2129
2427
|
});
|
|
2130
2428
|
}
|
|
@@ -2187,7 +2485,7 @@ function checkSourceInner(filePath, source, file, phi, opts, issues, signatures,
|
|
|
2187
2485
|
rc.constraint,
|
|
2188
2486
|
rec.result
|
|
2189
2487
|
);
|
|
2190
|
-
if (retIssues.
|
|
2488
|
+
if (retIssues.some((i) => i.severity === "error")) returnViolated.add(rec.fnName);
|
|
2191
2489
|
issues.push(...retIssues);
|
|
2192
2490
|
}
|
|
2193
2491
|
}
|
|
@@ -2246,118 +2544,33 @@ function checkSourceInner(filePath, source, file, phi, opts, issues, signatures,
|
|
|
2246
2544
|
};
|
|
2247
2545
|
}
|
|
2248
2546
|
function checkReturnConstraint(fnName, cName, constraint, ret) {
|
|
2249
|
-
const
|
|
2250
|
-
if (
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
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",
|
|
2547
|
+
const proof = assertImplies(ret, constraint);
|
|
2548
|
+
if (proof.status === "proved") return [];
|
|
2549
|
+
const push = (severity, actual, expected, suggestion) => [
|
|
2550
|
+
{
|
|
2551
|
+
severity,
|
|
2552
|
+
code: severity === "error" ? "nudo:constraint-violated" : "nudo:unproven-return",
|
|
2269
2553
|
message: `${fnName}: return value \u22AD @nudo:contract return ${cName}`,
|
|
2270
2554
|
actual,
|
|
2271
2555
|
expected,
|
|
2272
2556
|
suggestion,
|
|
2273
2557
|
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
|
-
}
|
|
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
2558
|
}
|
|
2559
|
+
];
|
|
2560
|
+
if (proof.status === "disproved") {
|
|
2561
|
+
return push(
|
|
2562
|
+
"error",
|
|
2563
|
+
formatAbs(ret),
|
|
2564
|
+
proof.reason,
|
|
2565
|
+
proof.reason.includes("nullish") ? `use nullable(${formatConstraint(constraint)}) or union(..., lit(null)) to allow nullish returns` : `return a value satisfying ${formatConstraint(constraint)}`
|
|
2566
|
+
);
|
|
2336
2567
|
}
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
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)}`);
|
|
2359
|
-
}
|
|
2360
|
-
return out;
|
|
2568
|
+
return push(
|
|
2569
|
+
"warning",
|
|
2570
|
+
formatAbs(ret),
|
|
2571
|
+
formatConstraint(constraint),
|
|
2572
|
+
`${proof.reason}; add a precondition, narrow the return expression, or relax the return contract`
|
|
2573
|
+
);
|
|
2361
2574
|
}
|
|
2362
2575
|
|
|
2363
2576
|
// src/algebra/check-report.ts
|
|
@@ -2393,6 +2606,12 @@ function actionsForIssue(i) {
|
|
|
2393
2606
|
];
|
|
2394
2607
|
case "nudo:entry-may-throw":
|
|
2395
2608
|
return [
|
|
2609
|
+
{
|
|
2610
|
+
kind: "draft",
|
|
2611
|
+
command: "nudo contract --draft",
|
|
2612
|
+
label: "narrow the entry with a sidecar fn({ \u2026 }) param contract \u2014 most honest fix for helpers (also tightens the signature)",
|
|
2613
|
+
...i.fn ? { hint: i.fn } : {}
|
|
2614
|
+
},
|
|
2396
2615
|
{
|
|
2397
2616
|
kind: "relax",
|
|
2398
2617
|
label: "declare intentional fail-fast: @nudo:throws Error (or case `!! throws`)",
|
|
@@ -2406,8 +2625,7 @@ function actionsForIssue(i) {
|
|
|
2406
2625
|
kind: "ignore-throws",
|
|
2407
2626
|
command: "nudo check --ignore-throws TypeError",
|
|
2408
2627
|
label: "migration switch: ignore this throw class on L2"
|
|
2409
|
-
}
|
|
2410
|
-
draft
|
|
2628
|
+
}
|
|
2411
2629
|
];
|
|
2412
2630
|
case "nudo:unknown-inference":
|
|
2413
2631
|
case "nudo:opaque-result":
|
|
@@ -2424,10 +2642,12 @@ function actionsForIssue(i) {
|
|
|
2424
2642
|
];
|
|
2425
2643
|
case "nudo:fork-truncated":
|
|
2426
2644
|
case "nudo:recursion-truncated":
|
|
2645
|
+
case "nudo:promise-micro-truncated":
|
|
2646
|
+
case "nudo:promise-micro-error":
|
|
2427
2647
|
return [
|
|
2428
2648
|
{
|
|
2429
2649
|
kind: "info",
|
|
2430
|
-
label: "results widened to unknown \u2014 raise budget or narrow control flow"
|
|
2650
|
+
label: "results widened to unknown \u2014 raise budget (@nudo:budget forks/calls/depth on the function) or narrow control flow"
|
|
2431
2651
|
}
|
|
2432
2652
|
];
|
|
2433
2653
|
case "nudo:host-effect-blocked":
|
|
@@ -2661,107 +2881,6 @@ function formatGitlabCodeQuality(r, opts = {}) {
|
|
|
2661
2881
|
};
|
|
2662
2882
|
});
|
|
2663
2883
|
}
|
|
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
2884
|
export {
|
|
2766
2885
|
$add,
|
|
2767
2886
|
$arguments,
|
|
@@ -2873,6 +2992,9 @@ export {
|
|
|
2873
2992
|
$while,
|
|
2874
2993
|
$whileSeq,
|
|
2875
2994
|
$yield,
|
|
2995
|
+
CONSTRAINT_BUILDERS,
|
|
2996
|
+
CONSTRAINT_BUILDER_NAMES,
|
|
2997
|
+
CONSTRAINT_EXPR_RE,
|
|
2876
2998
|
DEFAULT_MAX_LOOP_ITERS,
|
|
2877
2999
|
MAX_EVAL_CALL_DEPTH,
|
|
2878
3000
|
MAX_EVAL_TOTAL_CALLS,
|
|
@@ -2887,6 +3009,7 @@ export {
|
|
|
2887
3009
|
TYPEOF_NAMES,
|
|
2888
3010
|
abs,
|
|
2889
3011
|
absFunction,
|
|
3012
|
+
absOnly,
|
|
2890
3013
|
absShapeKey,
|
|
2891
3014
|
absToConstraint,
|
|
2892
3015
|
absToString,
|
|
@@ -2905,6 +3028,7 @@ export {
|
|
|
2905
3028
|
asAbs,
|
|
2906
3029
|
asAbsVal,
|
|
2907
3030
|
assignSourceSlots,
|
|
3031
|
+
assumeFinite,
|
|
2908
3032
|
attachFnImpl,
|
|
2909
3033
|
beginCollectionFork,
|
|
2910
3034
|
betaOf,
|
|
@@ -2924,6 +3048,7 @@ export {
|
|
|
2924
3048
|
callAbsMethod,
|
|
2925
3049
|
callAtFunctionBoundary,
|
|
2926
3050
|
callTranspiledExport,
|
|
3051
|
+
callTranspiledExportApply,
|
|
2927
3052
|
callTranspiledExportFull,
|
|
2928
3053
|
canonicalArrayIndex,
|
|
2929
3054
|
checkArg,
|
|
@@ -2938,6 +3063,7 @@ export {
|
|
|
2938
3063
|
collectionElementJoin,
|
|
2939
3064
|
collectionExactLen,
|
|
2940
3065
|
confJoin,
|
|
3066
|
+
constraintAdmitsNullish,
|
|
2941
3067
|
constraintToEntryAbs,
|
|
2942
3068
|
contractParamNameSet,
|
|
2943
3069
|
createEnvironment,
|
|
@@ -2982,19 +3108,27 @@ export {
|
|
|
2982
3108
|
evictGeneralizeMemoForPaths,
|
|
2983
3109
|
execNudoModule,
|
|
2984
3110
|
extStateOf,
|
|
3111
|
+
extractBalancedParens,
|
|
2985
3112
|
extractDeclaredThrows,
|
|
3113
|
+
extractFileEnvNames,
|
|
2986
3114
|
extractFn,
|
|
3115
|
+
extractFnBudget,
|
|
3116
|
+
extractMockModuleRecords,
|
|
3117
|
+
extractNudoImportRecords,
|
|
2987
3118
|
extractNudoImports,
|
|
2988
3119
|
extractRefineReturnFromSource,
|
|
2989
3120
|
extractRefinesFromSource,
|
|
2990
3121
|
falseConstraint,
|
|
2991
3122
|
fillTuple,
|
|
3123
|
+
findFnDirectiveScope,
|
|
2992
3124
|
fn,
|
|
2993
3125
|
fnConstraintToEntryReqs,
|
|
3126
|
+
fnDirectiveCommentLines,
|
|
2994
3127
|
fnOf,
|
|
2995
3128
|
foldRequireSpecArg,
|
|
2996
3129
|
foldStaticStringExpr,
|
|
2997
3130
|
formalParamDisplayNames,
|
|
3131
|
+
formalParamSignatureNames,
|
|
2998
3132
|
formalParamsFromNodes,
|
|
2999
3133
|
formatAbs,
|
|
3000
3134
|
formatAbsMultiline,
|
|
@@ -3031,6 +3165,7 @@ export {
|
|
|
3031
3165
|
interfaceDiagCount,
|
|
3032
3166
|
interfaceSourceOf,
|
|
3033
3167
|
interfaceTierOf,
|
|
3168
|
+
isAbsApplyResult,
|
|
3034
3169
|
isArrMutator,
|
|
3035
3170
|
isBigPrim,
|
|
3036
3171
|
isDefinitelyFalse,
|
|
@@ -3064,6 +3199,7 @@ export {
|
|
|
3064
3199
|
leavePromiseExecutorScope,
|
|
3065
3200
|
lenTerm,
|
|
3066
3201
|
leqAbs,
|
|
3202
|
+
listFnDirectiveScopes,
|
|
3067
3203
|
listFunctionNames,
|
|
3068
3204
|
lit,
|
|
3069
3205
|
litC,
|
|
@@ -3076,6 +3212,7 @@ export {
|
|
|
3076
3212
|
looseEqAbs,
|
|
3077
3213
|
lt,
|
|
3078
3214
|
ltNum,
|
|
3215
|
+
makeAbsApplyResult,
|
|
3079
3216
|
makeArrayCtorAbs,
|
|
3080
3217
|
makeMapAbs,
|
|
3081
3218
|
makeSetAbs,
|
|
@@ -3100,6 +3237,7 @@ export {
|
|
|
3100
3237
|
mock,
|
|
3101
3238
|
mod,
|
|
3102
3239
|
mul,
|
|
3240
|
+
namespaceAbsOf,
|
|
3103
3241
|
namespaceNameOf,
|
|
3104
3242
|
ne,
|
|
3105
3243
|
negAbs,
|
|
@@ -3111,6 +3249,7 @@ export {
|
|
|
3111
3249
|
noteEvalCallRecord,
|
|
3112
3250
|
noteEvalFallback,
|
|
3113
3251
|
notePromiseExecutorFork,
|
|
3252
|
+
nullable,
|
|
3114
3253
|
num,
|
|
3115
3254
|
numLit,
|
|
3116
3255
|
numVar,
|
|
@@ -3123,6 +3262,9 @@ export {
|
|
|
3123
3262
|
or,
|
|
3124
3263
|
pFalse,
|
|
3125
3264
|
pTrue,
|
|
3265
|
+
parseEnvPayload,
|
|
3266
|
+
parseMockModulePayload,
|
|
3267
|
+
parseNudoImportPayload,
|
|
3126
3268
|
parseSource,
|
|
3127
3269
|
partial,
|
|
3128
3270
|
phiAnd,
|
|
@@ -3160,10 +3302,16 @@ export {
|
|
|
3160
3302
|
resetNudoModuleExecCache,
|
|
3161
3303
|
resetParseSourceCache,
|
|
3162
3304
|
resetPhi,
|
|
3305
|
+
resetSidecarLoadFailureCache,
|
|
3163
3306
|
runTranspiled,
|
|
3164
3307
|
runTranspiledOptionsMemoKey,
|
|
3165
3308
|
runWithLoopExits,
|
|
3166
3309
|
runtimeImportOf,
|
|
3310
|
+
scanBudgetDecl,
|
|
3311
|
+
scanCaseArgSpans,
|
|
3312
|
+
scanCaseTags,
|
|
3313
|
+
scanContractSegments,
|
|
3314
|
+
scanThrowsDecl,
|
|
3167
3315
|
serializeCheckJson,
|
|
3168
3316
|
serializeCheckJsonMulti,
|
|
3169
3317
|
setAddEntry,
|