lemmascript 0.5.18 → 0.5.20
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/README.md +1 -1
- package/package.json +1 -1
- package/tools/dist/autohavoc.js +2 -0
- package/tools/dist/builtins.js +125 -0
- package/tools/dist/condition-facts.js +364 -0
- package/tools/dist/dafny-emit.js +228 -37
- package/tools/dist/extract.js +175 -37
- package/tools/dist/info-command.js +68 -0
- package/tools/dist/ir.js +27 -7
- package/tools/dist/lean-emit.js +29 -18
- package/tools/dist/lsc.js +53 -5
- package/tools/dist/names.js +10 -6
- package/tools/dist/narrow.js +296 -677
- package/tools/dist/peephole.js +12 -94
- package/tools/dist/rawir.js +15 -1
- package/tools/dist/resolve.js +268 -249
- package/tools/dist/specparser.js +21 -17
- package/tools/dist/transform.js +411 -131
- package/tools/dist/typedecls.js +59 -0
package/tools/dist/dafny-emit.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Dafny emitter — IR → Dafny text.
|
|
3
3
|
*/
|
|
4
|
-
import { usesName, usesNameInDecl } from "./ir.js";
|
|
5
|
-
import {
|
|
4
|
+
import { exactIntegerLiteral, usesName, usesNameInDecl, usesNameInStmts } from "./ir.js";
|
|
5
|
+
import { freshNameWhere, userNames } from "./names.js";
|
|
6
6
|
import { renameFreeVar } from "./transform.js";
|
|
7
7
|
/** Fresh binder for a comprehension wrapping the given subexpressions: `base`
|
|
8
8
|
* verbatim unless one of them references it, then primed until free. A *local*
|
|
9
9
|
* check — a same-named name elsewhere in the module keeps the plain binder. */
|
|
10
10
|
function freshBinder(base, ...wrapped) {
|
|
11
|
-
return
|
|
11
|
+
return freshNameWhere(base, name => wrapped.some(w => usesName(w, name)));
|
|
12
12
|
}
|
|
13
13
|
/** Binder + body for lowering a single-return lambda to a comprehension whose
|
|
14
14
|
* receiver is emitted inside the binder's scope. TS scoping keeps the receiver
|
|
@@ -80,6 +80,24 @@ const DAFNY_KEYWORDS = new Set([
|
|
|
80
80
|
// by methodHeader and reset per decl. `\result` in an ensures must use the
|
|
81
81
|
// *same* name, so escapeName routes it here.
|
|
82
82
|
let _resultName = "res";
|
|
83
|
+
// User-type names appearing as the type of a havoc anywhere in the module —
|
|
84
|
+
// populated per file by emitDafnyFile, read by the opaque-type case.
|
|
85
|
+
const _havocedTypeNames = new Set();
|
|
86
|
+
/** Collect the user-type names of every havoc in a decl tree. */
|
|
87
|
+
function collectHavocedTypeNames(v, out) {
|
|
88
|
+
if (Array.isArray(v)) {
|
|
89
|
+
for (const x of v)
|
|
90
|
+
collectHavocedTypeNames(x, out);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
if (v === null || typeof v !== "object")
|
|
94
|
+
return;
|
|
95
|
+
const n = v;
|
|
96
|
+
if (n.kind === "havoc" && n.type?.kind === "user")
|
|
97
|
+
out.add(n.type.name);
|
|
98
|
+
for (const x of Object.values(v))
|
|
99
|
+
collectHavocedTypeNames(x, out);
|
|
100
|
+
}
|
|
83
101
|
// ── Dafny name allocation ──────────────────────────────────
|
|
84
102
|
//
|
|
85
103
|
// freshName (names.ts) freshens in the *raw TS* namespace — but that is not the
|
|
@@ -128,6 +146,13 @@ function resetDafnyNameCache() {
|
|
|
128
146
|
_takenDafnyNames.add(emitted);
|
|
129
147
|
}
|
|
130
148
|
}
|
|
149
|
+
/** Source-name → emitted-Dafny-name pairs from the most recent emitDafnyFile
|
|
150
|
+
* call (identity-mapped names included). Consumed by `lsc info --typed` so
|
|
151
|
+
* satellites (e.g. lemmascript-crosscheck) can address emitted declarations
|
|
152
|
+
* by their mangled names (`_Box` → `i_Box'`) without re-deriving the rules. */
|
|
153
|
+
export function emittedNameMap() {
|
|
154
|
+
return new Map([..._generatedDafnyNames, ..._userDafnyNames]);
|
|
155
|
+
}
|
|
131
156
|
function escapeName(name) {
|
|
132
157
|
// \result is carried through the IR as var "\\result"; render it as the
|
|
133
158
|
// current method's out-parameter name (chosen locally by methodHeader).
|
|
@@ -138,6 +163,13 @@ function escapeName(name) {
|
|
|
138
163
|
return user;
|
|
139
164
|
return escapeGeneratedName(name);
|
|
140
165
|
}
|
|
166
|
+
function isEmittedUserName(name) {
|
|
167
|
+
for (const emitted of _userDafnyNames.values()) {
|
|
168
|
+
if (emitted === name)
|
|
169
|
+
return true;
|
|
170
|
+
}
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
141
173
|
/** Allocate a toolchain-generated name (an ANF temp, a comprehension binder, a
|
|
142
174
|
* companion `_ensures` lemma). Escapes to a base, then freshens in the Dafny
|
|
143
175
|
* namespace so it can't collapse onto an escaped user name. Bypasses the user
|
|
@@ -171,7 +203,7 @@ function methodHeader(prefix, params, returnType, scope) {
|
|
|
171
203
|
// name is recorded so `\result` references resolve to it.
|
|
172
204
|
const taken = (n) => params.some(p => escapeName(p.name) === n) ||
|
|
173
205
|
(scope !== undefined && usesNameInDecl(scope.requires, scope.ensures, scope.body, n));
|
|
174
|
-
const resName =
|
|
206
|
+
const resName = freshNameWhere("res", taken);
|
|
175
207
|
_resultName = resName;
|
|
176
208
|
return `${sig} returns (${resName}: ${tyToDafny(returnType)})`;
|
|
177
209
|
}
|
|
@@ -183,9 +215,8 @@ const OP_MAP = {
|
|
|
183
215
|
};
|
|
184
216
|
function mapOp(op) { return OP_MAP[op] ?? op; }
|
|
185
217
|
// ── Expression emission ─────────────────────────────────────
|
|
186
|
-
/** Emit a match scrutinee — either a variable name (string) or an expression. */
|
|
187
218
|
function emitScrutinee(s) {
|
|
188
|
-
return
|
|
219
|
+
return emitExpr(s);
|
|
189
220
|
}
|
|
190
221
|
/** Collapse nested forall/exists into a single quantifier with multiple bound vars. */
|
|
191
222
|
function emitQuantifier(e, keyword) {
|
|
@@ -212,6 +243,8 @@ function emitExpr(e) {
|
|
|
212
243
|
switch (e.kind) {
|
|
213
244
|
case "var": return e.name === "undefined" ? "None" : escapeName(e.name);
|
|
214
245
|
case "num": return `${e.value}`;
|
|
246
|
+
// Already canonical decimal; Dafny's `int` is mathematical, so no `n` suffix.
|
|
247
|
+
case "bigint": return e.value;
|
|
215
248
|
case "bool": return e.value ? "true" : "false";
|
|
216
249
|
case "str": return `"${e.value.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\n/g, '\\n')}"`;
|
|
217
250
|
case "constructor": {
|
|
@@ -281,8 +314,43 @@ function emitExpr(e) {
|
|
|
281
314
|
}
|
|
282
315
|
return `${obj}[${args[0]}..${args[1]}]`;
|
|
283
316
|
}
|
|
284
|
-
if (e.method === "map")
|
|
285
|
-
|
|
317
|
+
if (e.method === "map") {
|
|
318
|
+
// Always a seq comprehension: Seq.Map would hide the element access
|
|
319
|
+
// behind a closure, defeating Dafny's termination checker for
|
|
320
|
+
// recursive rebuild walkers. A literal lambda argument is
|
|
321
|
+
// beta-reduced through a `var` binding — an applied closure defeats
|
|
322
|
+
// the checker too; any other argument is applied to the element
|
|
323
|
+
// directly. A non-variable receiver is bound once up front: the
|
|
324
|
+
// comprehension mentions it three times, and splicing would make
|
|
325
|
+
// any closure literal inside it (e.g. a Filter predicate) three
|
|
326
|
+
// distinct closures, unprovably equal through an opaque callee.
|
|
327
|
+
// Name freshness is a local IR-level check until a backend name
|
|
328
|
+
// allocator exists.
|
|
329
|
+
const lam = e.args[0];
|
|
330
|
+
const fresh = (base, taken) => {
|
|
331
|
+
let name = base;
|
|
332
|
+
for (let n = 2; taken(name); n++)
|
|
333
|
+
name = `${base}${n}`;
|
|
334
|
+
return name;
|
|
335
|
+
};
|
|
336
|
+
const taken = (n) => usesName(e.obj, n) || usesName(lam, n) ||
|
|
337
|
+
(lam.kind === "lambda" && (lam.params.some(pp => pp.name === n) ||
|
|
338
|
+
usesNameInStmts(lam.body, n)));
|
|
339
|
+
const bind = e.obj.kind !== "var";
|
|
340
|
+
const s = bind ? fresh("s_map", taken) : obj;
|
|
341
|
+
const idx = fresh("i_map", n => taken(n) || n === s);
|
|
342
|
+
let core;
|
|
343
|
+
if (lam.kind === "lambda" && lam.params.length === 1 &&
|
|
344
|
+
lam.body.length === 1 && lam.body[0].kind === "return") {
|
|
345
|
+
const p = escapeName(lam.params[0].name);
|
|
346
|
+
core = `var ${p} := ${s}[${idx}]; ${emitExpr(lam.body[0].value)}`;
|
|
347
|
+
}
|
|
348
|
+
else {
|
|
349
|
+
core = `(${args[0]})(${s}[${idx}])`;
|
|
350
|
+
}
|
|
351
|
+
const comp = `seq(|${s}|, ${idx} requires 0 <= ${idx} < |${s}| => ${core})`;
|
|
352
|
+
return bind ? `(var ${s} := ${obj}; ${comp})` : comp;
|
|
353
|
+
}
|
|
286
354
|
if (e.method === "filter")
|
|
287
355
|
return `Std.Collections.Seq.Filter(${args[0]}, ${obj})`;
|
|
288
356
|
// filterMap (synthesized in resolve): drop Nones and unwrap to seq<T>.
|
|
@@ -293,6 +361,11 @@ function emitExpr(e) {
|
|
|
293
361
|
}
|
|
294
362
|
if (e.method === "every")
|
|
295
363
|
return `Std.Collections.Seq.All(${obj}, ${args[0]})`;
|
|
364
|
+
if (e.method === "find") {
|
|
365
|
+
needPreamble("OptionType");
|
|
366
|
+
needPreamble("SeqFind");
|
|
367
|
+
return `SeqFind(${obj}, ${args[0]})`;
|
|
368
|
+
}
|
|
296
369
|
if (e.method === "findLast") {
|
|
297
370
|
needPreamble("OptionType");
|
|
298
371
|
needPreamble("SeqFindLast");
|
|
@@ -348,10 +421,14 @@ function emitExpr(e) {
|
|
|
348
421
|
return `StringSplit(${obj}, ${args[0]})`;
|
|
349
422
|
}
|
|
350
423
|
if (e.method === "slice") {
|
|
351
|
-
// JS negative index: arr.slice(0, -N) → arr[0..|arr|-N].
|
|
352
|
-
//
|
|
353
|
-
//
|
|
354
|
-
|
|
424
|
+
// JS negative index: arr.slice(0, -N) → arr[0..|arr|-N]. Transform
|
|
425
|
+
// folds unary minus on a numeric literal into a negative `num` node,
|
|
426
|
+
// but leaves a negated bigint structural — `exactIntegerLiteral`
|
|
427
|
+
// recognizes both.
|
|
428
|
+
const negVal = (a) => {
|
|
429
|
+
const v = exactIntegerLiteral(a);
|
|
430
|
+
return v !== null && v < 0n ? (-v).toString(10) : null;
|
|
431
|
+
};
|
|
355
432
|
const loN = negVal(e.args[0]);
|
|
356
433
|
const loEx = loN !== null ? `|${obj}|-${loN}` : args[0];
|
|
357
434
|
if (args.length === 1)
|
|
@@ -395,6 +472,10 @@ function emitExpr(e) {
|
|
|
395
472
|
return `(|${obj}| >= |${args[0]}| && ${obj}[..|${args[0]}|] == ${args[0]})`;
|
|
396
473
|
if (e.method === "charCodeAt")
|
|
397
474
|
return `(${obj}[${args[0]}] as int)`;
|
|
475
|
+
if (e.method === "repeat") {
|
|
476
|
+
needPreamble("StringRepeat");
|
|
477
|
+
return `StringRepeat(${obj}, ${args[0]})`;
|
|
478
|
+
}
|
|
398
479
|
}
|
|
399
480
|
// Map methods
|
|
400
481
|
if (ty === "map") {
|
|
@@ -453,6 +534,8 @@ function emitExpr(e) {
|
|
|
453
534
|
return `!(${emitExpr(e.expr)})`;
|
|
454
535
|
if (e.op === "-" && e.expr.kind === "num")
|
|
455
536
|
return `(-(${e.expr.value}))`;
|
|
537
|
+
if (e.op === "-" && e.expr.kind === "bigint")
|
|
538
|
+
return `(-(${e.expr.value}))`;
|
|
456
539
|
if (e.op === "-")
|
|
457
540
|
return `(-(${emitExpr(e.expr)}))`;
|
|
458
541
|
return `${op}(${emitExpr(e.expr)})`;
|
|
@@ -461,34 +544,30 @@ function emitExpr(e) {
|
|
|
461
544
|
// Discriminant check: x == .Ctor → x.Ctor?
|
|
462
545
|
const op = mapOp(e.op);
|
|
463
546
|
if ((op === "==" || op === "!=") && e.right.kind === "constructor") {
|
|
464
|
-
const ctorName =
|
|
547
|
+
const ctorName = dafnyCtorName(e.right.name.replace(/^\./, ""));
|
|
465
548
|
const pred = `${emitExpr(e.left)}.${ctorName}?`;
|
|
466
549
|
return op === "!=" ? `(!${pred})` : pred;
|
|
467
550
|
}
|
|
468
551
|
// Bitwise operators on int: translate to arithmetic
|
|
469
552
|
// x >> n → x / 2^n (right shift)
|
|
470
553
|
// x << n → x * 2^n (left shift)
|
|
471
|
-
if (e.op === ">>") {
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
}
|
|
478
|
-
if (e.op === "<<") {
|
|
479
|
-
if (e.right.kind === "num") {
|
|
480
|
-
return `(${emitExpr(e.left)} * ${Math.pow(2, e.right.value)})`;
|
|
554
|
+
if (e.op === ">>" || e.op === "<<") {
|
|
555
|
+
const shift = exactIntegerLiteral(e.right);
|
|
556
|
+
// Cap the fold: a huge literal shift would inline an absurd numeral.
|
|
557
|
+
if (shift !== null && shift >= 0n && shift <= 1024n) {
|
|
558
|
+
const factor = (1n << shift).toString(10);
|
|
559
|
+
return `(${emitExpr(e.left)} ${e.op === ">>" ? "/" : "*"} ${factor})`;
|
|
481
560
|
}
|
|
482
561
|
needPreamble("Pow2");
|
|
483
|
-
return `(${emitExpr(e.left)} * Pow2(${emitExpr(e.right)}))`;
|
|
562
|
+
return `(${emitExpr(e.left)} ${e.op === ">>" ? "/" : "*"} Pow2(${emitExpr(e.right)}))`;
|
|
484
563
|
}
|
|
485
564
|
// x & mask → x % (mask + 1) for literal masks of form 2^n - 1, else BitAnd
|
|
486
565
|
if (e.op === "&") {
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
const modulus = mask +
|
|
490
|
-
if ((modulus & (modulus -
|
|
491
|
-
return `(${emitExpr(e.left)} % ${modulus})`;
|
|
566
|
+
const mask = exactIntegerLiteral(e.right);
|
|
567
|
+
if (mask !== null && mask >= 0n) {
|
|
568
|
+
const modulus = mask + 1n;
|
|
569
|
+
if ((modulus & (modulus - 1n)) === 0n) {
|
|
570
|
+
return `(${emitExpr(e.left)} % ${modulus.toString(10)})`;
|
|
492
571
|
}
|
|
493
572
|
}
|
|
494
573
|
needPreamble("BitAnd");
|
|
@@ -532,6 +611,8 @@ function emitExpr(e) {
|
|
|
532
611
|
needPreamble("CeilReal");
|
|
533
612
|
if (e.fn === "FloorReal")
|
|
534
613
|
needPreamble("FloorReal");
|
|
614
|
+
if (e.fn === "StringFromCharCode")
|
|
615
|
+
needPreamble("StringFromCharCode");
|
|
535
616
|
if (e.fn === "NatToString")
|
|
536
617
|
needPreamble("NatToString");
|
|
537
618
|
if (e.fn === "IntToString") {
|
|
@@ -556,6 +637,20 @@ function emitExpr(e) {
|
|
|
556
637
|
needPreamble("Perm");
|
|
557
638
|
if (e.fn === "SetFromSeq")
|
|
558
639
|
needPreamble("SetFromSeq");
|
|
640
|
+
// A constructor application must spell the name the same way the
|
|
641
|
+
// datatype declaration does — `dafnyCtorName`, not `escapeName`, since
|
|
642
|
+
// tags come from source strings ("spec-pure") that escapeName leaves alone.
|
|
643
|
+
if (e.ctorOf) {
|
|
644
|
+
const ctor = dafnyCtorName(e.fn);
|
|
645
|
+
// A source local may have the same name as a discriminated-union
|
|
646
|
+
// variant (`const error = ...; return { kind: "error", error }`).
|
|
647
|
+
// The bare `error(error)` is then parsed as a call through the local.
|
|
648
|
+
// Qualify whenever the emitted constructor spelling is already claimed
|
|
649
|
+
// in the user namespace, as well as when two datatypes share it.
|
|
650
|
+
return _ambiguousCtors.has(e.fn) || isEmittedUserName(ctor)
|
|
651
|
+
? `${e.ctorOf}.${ctor}(${args.join(", ")})`
|
|
652
|
+
: `${ctor}(${args.join(", ")})`;
|
|
653
|
+
}
|
|
559
654
|
return `${escapeName(e.fn)}(${args.join(", ")})`;
|
|
560
655
|
}
|
|
561
656
|
case "field": {
|
|
@@ -568,6 +663,11 @@ function emitExpr(e) {
|
|
|
568
663
|
return `${obj}.Keys`;
|
|
569
664
|
if (e.field === "toNat")
|
|
570
665
|
return obj;
|
|
666
|
+
if (e.ctor && e.fromUnion) {
|
|
667
|
+
const renamed = _ctorFieldRenames.get(`${e.fromUnion}.${e.ctor}.${e.field}`);
|
|
668
|
+
if (renamed)
|
|
669
|
+
return `${obj}.${escapeName(renamed)}`;
|
|
670
|
+
}
|
|
571
671
|
return `${obj}.${escapeName(e.field)}`;
|
|
572
672
|
}
|
|
573
673
|
case "toNat":
|
|
@@ -588,7 +688,10 @@ function emitExpr(e) {
|
|
|
588
688
|
if (e.fields.length === 0) {
|
|
589
689
|
return emitExpr(e.spread);
|
|
590
690
|
}
|
|
591
|
-
const updates = e.fields.map(f =>
|
|
691
|
+
const updates = e.fields.map(f => {
|
|
692
|
+
const renamed = e.ctor && e.ctorOf ? _ctorFieldRenames.get(`${e.ctorOf}.${e.ctor}.${f.name}`) : undefined;
|
|
693
|
+
return `${escapeName(renamed ?? f.name)} := ${emitExpr(f.value)}`;
|
|
694
|
+
});
|
|
592
695
|
return `${emitExpr(e.spread)}.(${updates.join(", ")})`;
|
|
593
696
|
}
|
|
594
697
|
// Match constructor by field names — prefer exact match over first-field heuristic
|
|
@@ -792,9 +895,9 @@ function emitDecl(d) {
|
|
|
792
895
|
const collides = new Set([...typesByField].filter(([, s]) => s.size > 1).map(([n]) => n));
|
|
793
896
|
const ctors = d.constructors.map(c => {
|
|
794
897
|
if (c.fields.length === 0)
|
|
795
|
-
return
|
|
796
|
-
const fields = c.fields.map(f => collides.has(f.name) ? { ...f, name: `${f.name}_${c.name}` } : f);
|
|
797
|
-
return `${
|
|
898
|
+
return dafnyCtorName(c.name);
|
|
899
|
+
const fields = c.fields.map(f => collides.has(f.name) ? { ...f, name: `${f.name}_${c.name.replace(/[^A-Za-z0-9_'?]/g, "_")}` } : f);
|
|
900
|
+
return `${dafnyCtorName(c.name)}(${paramList(fields)})`;
|
|
798
901
|
});
|
|
799
902
|
return `datatype ${escapeName(d.name)}${tp} = ${ctors.join(" | ")}`;
|
|
800
903
|
}
|
|
@@ -808,7 +911,10 @@ function emitDecl(d) {
|
|
|
808
911
|
case "opaque-type": {
|
|
809
912
|
// Abstract type — no definition. `(==)` so it can sit inside datatypes
|
|
810
913
|
// that derive structural equality. Never constructed or destructured.
|
|
811
|
-
|
|
914
|
+
// `0` (auto-init) only when a havoc of this type needs a witness to
|
|
915
|
+
// satisfy definite assignment — `var x: T := *` requires it.
|
|
916
|
+
const autoInit = _havocedTypeNames.has(d.name) ? ", 0" : "";
|
|
917
|
+
return `type ${escapeName(d.name)}(==${autoInit})`;
|
|
812
918
|
}
|
|
813
919
|
case "def": {
|
|
814
920
|
const tp = d.typeParams.length > 0 ? `<${d.typeParams.join(", ")}>` : "";
|
|
@@ -1036,6 +1142,19 @@ const SEQ_FIND_LAST_INDEX = `function SeqFindLastIndex<T>(s: seq<T>, p: T -> boo
|
|
|
1036
1142
|
else if p(s[|s|-1]) then |s| - 1
|
|
1037
1143
|
else SeqFindLastIndex(s[..|s|-1], p)
|
|
1038
1144
|
}`;
|
|
1145
|
+
const SEQ_FIND = `function SeqFind<T>(s: seq<T>, p: T -> bool): Option<T>
|
|
1146
|
+
ensures SeqFind(s, p).Some? ==> p(SeqFind(s, p).value)
|
|
1147
|
+
ensures SeqFind(s, p).Some? ==> SeqFind(s, p).value in s
|
|
1148
|
+
ensures SeqFind(s, p).Some? ==>
|
|
1149
|
+
exists i: nat :: i < |s| && s[i] == SeqFind(s, p).value && p(s[i]) &&
|
|
1150
|
+
(forall j: nat :: j < i ==> !p(s[j]))
|
|
1151
|
+
ensures SeqFind(s, p).None? ==> forall i :: 0 <= i < |s| ==> !p(s[i])
|
|
1152
|
+
decreases |s|
|
|
1153
|
+
{
|
|
1154
|
+
if |s| == 0 then None
|
|
1155
|
+
else if p(s[0]) then Some(s[0])
|
|
1156
|
+
else SeqFind(s[1..], p)
|
|
1157
|
+
}`;
|
|
1039
1158
|
const SEQ_FIND_LAST = `function SeqFindLast<T>(s: seq<T>, p: T -> bool): Option<T>
|
|
1040
1159
|
ensures SeqFindLast(s, p).Some? ==> p(SeqFindLast(s, p).value)
|
|
1041
1160
|
ensures SeqFindLast(s, p).Some? ==> SeqFindLast(s, p).value in s
|
|
@@ -1189,6 +1308,27 @@ const STRING_TO_UPPER = `function StringToUpper(s: string): string
|
|
|
1189
1308
|
var upper := if 'a' <= c <= 'z' then (c - 'a' + 'A') as char else c;
|
|
1190
1309
|
[upper] + StringToUpper(s[1..])
|
|
1191
1310
|
}`;
|
|
1311
|
+
// `String.fromCharCode(n)` — the inverse of `s.charCodeAt(i)`'s `(s[i] as int)`.
|
|
1312
|
+
// Dafny's `char` is a Unicode scalar value, so the argument must miss the
|
|
1313
|
+
// surrogate range; that is the `requires`, discharged at each call site. The two
|
|
1314
|
+
// `ensures` give callers the round-trip law without unfolding the body.
|
|
1315
|
+
const STRING_FROM_CHAR_CODE = `function StringFromCharCode(n: int): string
|
|
1316
|
+
requires 0 <= n < 0xD800 || 0xE000 <= n < 0x110000
|
|
1317
|
+
ensures |StringFromCharCode(n)| == 1
|
|
1318
|
+
ensures (StringFromCharCode(n)[0] as int) == n
|
|
1319
|
+
{
|
|
1320
|
+
[n as char]
|
|
1321
|
+
}`;
|
|
1322
|
+
// `s.repeat(n)` — n copies of s, concatenated. The per-index ensures is stated
|
|
1323
|
+
// for the single-character receiver (the common case: padding with one digit).
|
|
1324
|
+
const STRING_REPEAT = `function StringRepeat(s: string, n: int): string
|
|
1325
|
+
requires n >= 0
|
|
1326
|
+
ensures |StringRepeat(s, n)| == |s| * n
|
|
1327
|
+
ensures |s| == 1 ==> forall i :: 0 <= i < n ==> StringRepeat(s, n)[i] == s[0]
|
|
1328
|
+
decreases n
|
|
1329
|
+
{
|
|
1330
|
+
if n == 0 then "" else s + StringRepeat(s, n - 1)
|
|
1331
|
+
}`;
|
|
1192
1332
|
const MATH_MIN = `function MathMin(a: int, b: int): int { if a <= b then a else b }`;
|
|
1193
1333
|
const MATH_MAX = `function MathMax(a: int, b: int): int { if a >= b then a else b }`;
|
|
1194
1334
|
const MAX_OF_SEQ = `function MaxOfSeq(s: seq<int>): int
|
|
@@ -1294,6 +1434,7 @@ const PREAMBLE_CODE = [
|
|
|
1294
1434
|
["SeqFindIndex", SEQ_FIND_INDEX],
|
|
1295
1435
|
["SeqFindLastIndex", SEQ_FIND_LAST_INDEX],
|
|
1296
1436
|
["SeqFilterSome", SEQ_FILTER_SOME],
|
|
1437
|
+
["SeqFind", SEQ_FIND],
|
|
1297
1438
|
["SeqFindLast", SEQ_FIND_LAST],
|
|
1298
1439
|
["SeqFlatten", SEQ_FLATTEN],
|
|
1299
1440
|
["SeqJoin", SEQ_JOIN],
|
|
@@ -1305,6 +1446,8 @@ const PREAMBLE_CODE = [
|
|
|
1305
1446
|
["StringTrim", STRING_TRIM],
|
|
1306
1447
|
["StringToLower", STRING_TO_LOWER],
|
|
1307
1448
|
["StringToUpper", STRING_TO_UPPER],
|
|
1449
|
+
["StringFromCharCode", STRING_FROM_CHAR_CODE],
|
|
1450
|
+
["StringRepeat", STRING_REPEAT],
|
|
1308
1451
|
["NatToString", NAT_TO_STRING],
|
|
1309
1452
|
["IntToString", INT_TO_STRING],
|
|
1310
1453
|
["MathAbs", MATH_ABS],
|
|
@@ -1319,10 +1462,18 @@ const PREAMBLE_CODE = [
|
|
|
1319
1462
|
let _recordCtors = new Map();
|
|
1320
1463
|
let _structureDecls = new Map();
|
|
1321
1464
|
let _declaredTypes = new Set();
|
|
1465
|
+
let _ambiguousCtors = new Set();
|
|
1466
|
+
// `"<union>.<ctor>.<field>"` → per-constructor destructor name, for fields the
|
|
1467
|
+
// inductive emission renames (shared name, differing types). Field reads and
|
|
1468
|
+
// datatype updates with a pinned ctor must use the renamed destructor.
|
|
1469
|
+
let _ctorFieldRenames = new Map();
|
|
1322
1470
|
function buildRecordCtorMap(decls) {
|
|
1323
1471
|
_recordCtors = new Map();
|
|
1324
1472
|
_structureDecls = new Map();
|
|
1325
1473
|
_declaredTypes = new Set();
|
|
1474
|
+
_ambiguousCtors = new Set();
|
|
1475
|
+
_ctorFieldRenames = new Map();
|
|
1476
|
+
const ctorSeen = new Set();
|
|
1326
1477
|
function collectDecl(d) {
|
|
1327
1478
|
if (d.kind === "structure") {
|
|
1328
1479
|
_declaredTypes.add(d.name);
|
|
@@ -1330,8 +1481,36 @@ function buildRecordCtorMap(decls) {
|
|
|
1330
1481
|
if (d.fields.length > 0)
|
|
1331
1482
|
_recordCtors.set(d.fields[0].name, d.name);
|
|
1332
1483
|
}
|
|
1333
|
-
if (d.kind === "inductive")
|
|
1484
|
+
if (d.kind === "inductive") {
|
|
1334
1485
|
_declaredTypes.add(d.name);
|
|
1486
|
+
// Constructor names shared by two datatypes in this module (Expr.let vs
|
|
1487
|
+
// Stmt.let) can't be used bare — emitters must qualify them.
|
|
1488
|
+
for (const c of d.constructors) {
|
|
1489
|
+
if (ctorSeen.has(c.name))
|
|
1490
|
+
_ambiguousCtors.add(c.name);
|
|
1491
|
+
ctorSeen.add(c.name);
|
|
1492
|
+
}
|
|
1493
|
+
// Mirror the destructor renaming the inductive case of emitDecl performs
|
|
1494
|
+
// (shared field name, differing types → per-constructor names), so reads
|
|
1495
|
+
// and updates can be translated to the renamed destructors.
|
|
1496
|
+
const typesByField = new Map();
|
|
1497
|
+
for (const c of d.constructors)
|
|
1498
|
+
for (const f of c.fields) {
|
|
1499
|
+
let s = typesByField.get(f.name);
|
|
1500
|
+
if (!s) {
|
|
1501
|
+
s = new Set();
|
|
1502
|
+
typesByField.set(f.name, s);
|
|
1503
|
+
}
|
|
1504
|
+
s.add(tyToDafny(f.type));
|
|
1505
|
+
}
|
|
1506
|
+
const collides = new Set([...typesByField].filter(([, s]) => s.size > 1).map(([n]) => n));
|
|
1507
|
+
for (const c of d.constructors)
|
|
1508
|
+
for (const f of c.fields) {
|
|
1509
|
+
if (!collides.has(f.name))
|
|
1510
|
+
continue;
|
|
1511
|
+
_ctorFieldRenames.set(`${d.name}.${c.name}.${f.name}`, `${f.name}_${c.name.replace(/[^A-Za-z0-9_'?]/g, "_")}`);
|
|
1512
|
+
}
|
|
1513
|
+
}
|
|
1335
1514
|
if (d.kind === "type-alias")
|
|
1336
1515
|
_declaredTypes.add(d.name);
|
|
1337
1516
|
if (d.kind === "def")
|
|
@@ -1359,9 +1538,19 @@ function resolveTy(ty) {
|
|
|
1359
1538
|
return { kind: "set", elem: resolveTy(ty.elem) };
|
|
1360
1539
|
return ty;
|
|
1361
1540
|
}
|
|
1541
|
+
/** Constructor names come from source strings (string-union values like
|
|
1542
|
+
* "spec-pure", discriminated-union tags), which may contain characters no
|
|
1543
|
+
* TS identifier has; map those to `_` before the ordinary escaping. A
|
|
1544
|
+
* collision after mapping fails loudly in Dafny (duplicate constructor)
|
|
1545
|
+
* rather than silently merging. */
|
|
1546
|
+
function dafnyCtorName(name) {
|
|
1547
|
+
return escapeName(name.replace(/[^A-Za-z0-9_'?]/g, "_"));
|
|
1548
|
+
}
|
|
1362
1549
|
function qualifyCtor(name, type) {
|
|
1363
1550
|
const rawName = name.replace(/^\./, "");
|
|
1364
|
-
|
|
1551
|
+
// hasOwn: a ctor literally named "constructor" (the IR's own Expr variant)
|
|
1552
|
+
// must not hit Object.prototype.constructor through the bare index.
|
|
1553
|
+
const mapped = (Object.hasOwn(CTOR_MAP, rawName) ? CTOR_MAP[rawName] : undefined) ?? dafnyCtorName(rawName);
|
|
1365
1554
|
if (type)
|
|
1366
1555
|
return `${type}.${mapped}`;
|
|
1367
1556
|
return mapped;
|
|
@@ -1375,7 +1564,7 @@ const CTOR_MAP = { "some": "Some", "none": "None" };
|
|
|
1375
1564
|
function translatePattern(p) {
|
|
1376
1565
|
if (p.kind === "wild")
|
|
1377
1566
|
return "_";
|
|
1378
|
-
const ctorName = CTOR_MAP[p.ctor] ??
|
|
1567
|
+
const ctorName = (Object.hasOwn(CTOR_MAP, p.ctor) ? CTOR_MAP[p.ctor] : undefined) ?? dafnyCtorName(p.ctor);
|
|
1379
1568
|
if (p.binders.length === 0)
|
|
1380
1569
|
return ctorName;
|
|
1381
1570
|
return `${ctorName}(${p.binders.map(escapeName).join(", ")})`;
|
|
@@ -1385,6 +1574,8 @@ export function emitDafnyFile(file, tsFileName, opts) {
|
|
|
1385
1574
|
resetDafnyNameCache();
|
|
1386
1575
|
buildRecordCtorMap(file.decls);
|
|
1387
1576
|
_neededPreambles.clear();
|
|
1577
|
+
_havocedTypeNames.clear();
|
|
1578
|
+
collectHavocedTypeNames(file.decls, _havocedTypeNames);
|
|
1388
1579
|
// Track successfully emitted pure defs — method wrappers are only
|
|
1389
1580
|
// skipped when the corresponding pure def was actually emitted.
|
|
1390
1581
|
const emittedPureDefs = new Set();
|