lemmascript 0.5.12 → 0.5.14
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/package.json +2 -2
- package/tools/dist/dafny-commands.js +7 -4
- package/tools/dist/dafny-emit.js +138 -48
- package/tools/dist/extract.js +6 -2
- package/tools/dist/ir.js +57 -0
- package/tools/dist/lean-emit.js +26 -10
- package/tools/dist/lsc.js +15 -2
- package/tools/dist/names.js +52 -0
- package/tools/dist/narrow.js +60 -80
- package/tools/dist/peephole.js +8 -9
- package/tools/dist/resolve.js +6 -3
- package/tools/dist/transform.js +104 -52
package/tools/dist/transform.js
CHANGED
|
@@ -4,8 +4,9 @@
|
|
|
4
4
|
* Consumes resolved types and classifications.
|
|
5
5
|
* No type lookups, no string parsing, no re-inference.
|
|
6
6
|
*/
|
|
7
|
-
import { anyExprInStmts } from "./ir.js";
|
|
7
|
+
import { anyExprInStmts, pWild, pCtor, patternBinders, patternBinds, patternCtor } from "./ir.js";
|
|
8
8
|
import { parseTsType } from "./types.js";
|
|
9
|
+
import { freshName } from "./names.js";
|
|
9
10
|
// ── Generic IR walkers ──────────────────────────────────────
|
|
10
11
|
/**
|
|
11
12
|
* Map over all sub-expressions in an Expr. `f` is called on each node;
|
|
@@ -73,6 +74,49 @@ function mapStmt(s, f) {
|
|
|
73
74
|
case "assert": return { ...s, expr: r(s.expr) };
|
|
74
75
|
}
|
|
75
76
|
}
|
|
77
|
+
/** Rename free occurrences of `from` to `to`, stopping at every construct that
|
|
78
|
+
* rebinds `from` — lambda params, `let`/`let-bind`/`ghostLet` (shadows the
|
|
79
|
+
* rest of the block), `match` arm patterns, `forall`/`exists`, and `for-in`
|
|
80
|
+
* indices. Capture-avoiding: a nested scope that reintroduces `from` keeps its
|
|
81
|
+
* own binding untouched. `mapExpr` doesn't descend into lambda bodies, so this
|
|
82
|
+
* walks them by hand. */
|
|
83
|
+
export function renameFreeVar(e, from, to) {
|
|
84
|
+
const f = (x) => {
|
|
85
|
+
if (x.kind === "var")
|
|
86
|
+
return x.name === from ? { ...x, name: to } : x;
|
|
87
|
+
// let-expression: `value` is in the outer scope (rename), `body` sees the
|
|
88
|
+
// rebound `from` (leave it), so handle the recursion here to stop descent.
|
|
89
|
+
if (x.kind === "let" && x.name === from)
|
|
90
|
+
return { ...x, value: mapExpr(x.value, f) };
|
|
91
|
+
if ((x.kind === "forall" || x.kind === "exists") && x.var === from)
|
|
92
|
+
return x;
|
|
93
|
+
if (x.kind === "match") {
|
|
94
|
+
const scr = typeof x.scrutinee === "string"
|
|
95
|
+
? (x.scrutinee === from ? to : x.scrutinee) : mapExpr(x.scrutinee, f);
|
|
96
|
+
return { ...x, scrutinee: scr, arms: x.arms.map(a => patternBinds(a.pattern, from) ? a : { ...a, body: mapExpr(a.body, f) }) };
|
|
97
|
+
}
|
|
98
|
+
if (x.kind === "lambda") {
|
|
99
|
+
if (x.params.some(p => p.name === from))
|
|
100
|
+
return x; // param shadows `from`
|
|
101
|
+
const body = [];
|
|
102
|
+
let shadowed = false;
|
|
103
|
+
for (const s of x.body) {
|
|
104
|
+
if (shadowed) {
|
|
105
|
+
body.push(s);
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
body.push(s.kind === "forin" && s.idx === from
|
|
109
|
+
? { ...s, bound: mapExpr(s.bound, f) } // idx shadows in the loop body
|
|
110
|
+
: mapStmt(s, f));
|
|
111
|
+
if ((s.kind === "let" || s.kind === "let-bind" || s.kind === "ghostLet") && s.name === from)
|
|
112
|
+
shadowed = true;
|
|
113
|
+
}
|
|
114
|
+
return { ...x, body };
|
|
115
|
+
}
|
|
116
|
+
return null;
|
|
117
|
+
};
|
|
118
|
+
return mapExpr(e, f);
|
|
119
|
+
}
|
|
76
120
|
/** Map over all sub-expressions in a TExpr (typed IR). */
|
|
77
121
|
function mapTExpr(e, f) {
|
|
78
122
|
const hit = f(e);
|
|
@@ -145,21 +189,28 @@ let _opts = DAFNY_OPTIONS;
|
|
|
145
189
|
let _typeDecls = [];
|
|
146
190
|
/** Prefix match-bound field names to avoid capturing user variables.
|
|
147
191
|
* When prefix is given (the scrutinee name), include it to avoid
|
|
148
|
-
* collisions in nested matches on different variables.
|
|
192
|
+
* collisions in nested matches on different variables. `freshName` closes
|
|
193
|
+
* the residual gap: a user variable literally named `_value`/`_x_field` in an
|
|
194
|
+
* arm body would still be captured, so prime on any module-wide collision.
|
|
195
|
+
* Deterministic, so the pattern binder and its body substitutions agree. */
|
|
149
196
|
function matchBinder(fieldName, prefix) {
|
|
150
|
-
return prefix ? `_${prefix}_${fieldName}` : `_${fieldName}
|
|
197
|
+
return freshName(prefix ? `_${prefix}_${fieldName}` : `_${fieldName}`);
|
|
151
198
|
}
|
|
152
199
|
/** Build a match arm pattern like `.VariantName _v_field1 _v_field2` from variant info. */
|
|
153
200
|
function buildMatchPattern(variantName, fields, scopePrefix) {
|
|
154
|
-
|
|
155
|
-
return `.${variantName}`;
|
|
156
|
-
return `.${variantName} ${fields.map(f => matchBinder(f.name, scopePrefix)).join(" ")}`;
|
|
201
|
+
return pCtor(variantName, ...fields.map(f => matchBinder(f.name, scopePrefix)));
|
|
157
202
|
}
|
|
158
203
|
const _forofCounters = new Map();
|
|
159
204
|
function isNat(ty) { return ty.kind === "nat"; }
|
|
160
205
|
function isIntegral(ty) { return ty.kind === "int" || ty.kind === "nat"; }
|
|
161
206
|
function isArray(ty) { return ty.kind === "array"; }
|
|
162
207
|
function isUser(ty) { return ty.kind === "user"; }
|
|
208
|
+
function isRecordType(ty) {
|
|
209
|
+
if (ty.kind !== "user")
|
|
210
|
+
return false;
|
|
211
|
+
const base = ty.name.includes("<") ? ty.name.slice(0, ty.name.indexOf("<")) : ty.name;
|
|
212
|
+
return _typeDecls.find(d => d.name === base)?.kind === "record";
|
|
213
|
+
}
|
|
163
214
|
/** Truthiness test for a *lowered* value of source type `ty`, used by `||`
|
|
164
215
|
* falsiness lowering. Mirrors narrow.ts's `canBeFalsy`: only int/nat/string/bool
|
|
165
216
|
* values can be falsy in JS (`0`, `""`, `false`); every other value (array, user
|
|
@@ -351,7 +402,7 @@ function lowerExpr(e, binds) {
|
|
|
351
402
|
// callKind "unknown" and fall through to the regular case below where
|
|
352
403
|
// they become `methodCall`.
|
|
353
404
|
if (binds && e.kind === "call" && e.callKind === "method" && e.fn.kind === "var") {
|
|
354
|
-
const name = `_t${_liftCounter++}
|
|
405
|
+
const name = freshName(`_t${_liftCounter++}`);
|
|
355
406
|
const args = e.args.map(a => lowerExpr(a, binds));
|
|
356
407
|
binds.push({ kind: "let-bind", name, value: { kind: "app", fn: e.fn.name, args } });
|
|
357
408
|
return { kind: "var", name };
|
|
@@ -380,8 +431,8 @@ function lowerExpr(e, binds) {
|
|
|
380
431
|
return {
|
|
381
432
|
kind: "match", scrutinee: lowerExpr(e.expr, binds),
|
|
382
433
|
arms: [
|
|
383
|
-
{ pattern:
|
|
384
|
-
{ pattern: "
|
|
434
|
+
{ pattern: pCtor("some", bound), body: truthy ? { kind: "unop", op: "¬", expr: truthy } : { kind: "bool", value: false } },
|
|
435
|
+
{ pattern: pCtor("none"), body: { kind: "bool", value: true } },
|
|
385
436
|
],
|
|
386
437
|
};
|
|
387
438
|
}
|
|
@@ -433,8 +484,8 @@ function lowerExpr(e, binds) {
|
|
|
433
484
|
return {
|
|
434
485
|
kind: "match", scrutinee: optExpr,
|
|
435
486
|
arms: [
|
|
436
|
-
{ pattern: "
|
|
437
|
-
{ pattern: "
|
|
487
|
+
{ pattern: pCtor("some", "_"), body: { kind: "bool", value: !isNone } },
|
|
488
|
+
{ pattern: pCtor("none"), body: { kind: "bool", value: isNone } },
|
|
438
489
|
],
|
|
439
490
|
};
|
|
440
491
|
}
|
|
@@ -451,8 +502,8 @@ function lowerExpr(e, binds) {
|
|
|
451
502
|
return {
|
|
452
503
|
kind: "match", scrutinee: optExpr,
|
|
453
504
|
arms: [
|
|
454
|
-
{ pattern:
|
|
455
|
-
{ pattern: "
|
|
505
|
+
{ pattern: pCtor("some", bound), body: { kind: "binop", op: cmpOp, left: { kind: "var", name: bound }, right: valExpr } },
|
|
506
|
+
{ pattern: pCtor("none"), body: { kind: "bool", value: noneVal } },
|
|
456
507
|
],
|
|
457
508
|
};
|
|
458
509
|
}
|
|
@@ -470,12 +521,12 @@ function lowerExpr(e, binds) {
|
|
|
470
521
|
return {
|
|
471
522
|
kind: "match", scrutinee: optExpr,
|
|
472
523
|
arms: [
|
|
473
|
-
{ pattern:
|
|
524
|
+
{ pattern: pCtor("some", bound), body: {
|
|
474
525
|
kind: "if", cond: truthy,
|
|
475
526
|
then: { kind: "app", fn: "Some", args: [{ kind: "var", name: bound }] },
|
|
476
527
|
else: { kind: "var", name: "undefined" }
|
|
477
528
|
} },
|
|
478
|
-
{ pattern: "
|
|
529
|
+
{ pattern: pCtor("none"), body: { kind: "var", name: "undefined" } },
|
|
479
530
|
],
|
|
480
531
|
};
|
|
481
532
|
}
|
|
@@ -494,8 +545,8 @@ function lowerExpr(e, binds) {
|
|
|
494
545
|
return {
|
|
495
546
|
kind: "match", scrutinee: optExpr,
|
|
496
547
|
arms: [
|
|
497
|
-
{ pattern:
|
|
498
|
-
{ pattern: "
|
|
548
|
+
{ pattern: pCtor("some", bound), body: someBody },
|
|
549
|
+
{ pattern: pCtor("none"), body: defaultExpr },
|
|
499
550
|
],
|
|
500
551
|
};
|
|
501
552
|
}
|
|
@@ -640,10 +691,10 @@ function lowerExpr(e, binds) {
|
|
|
640
691
|
const baseName = e.obj.ty.name.includes("<") ? e.obj.ty.name.slice(0, e.obj.ty.name.indexOf("<")) : e.obj.ty.name;
|
|
641
692
|
const decl = _typeDecls.find(d => d.name === baseName && d.kind === "discriminated-union");
|
|
642
693
|
if (decl?.variants?.some(v => v.fields.some(f => f.name === e.field))) {
|
|
643
|
-
return { kind: "field", obj: transformExpr(e.obj), field: e.field, fromUnion: baseName };
|
|
694
|
+
return { kind: "field", obj: transformExpr(e.obj), field: e.field, fromUnion: baseName, datatypeField: true };
|
|
644
695
|
}
|
|
645
696
|
}
|
|
646
|
-
return { kind: "field", obj: transformExpr(e.obj), field: e.field };
|
|
697
|
+
return { kind: "field", obj: transformExpr(e.obj), field: e.field, datatypeField: isRecordType(e.obj.ty) };
|
|
647
698
|
case "index": {
|
|
648
699
|
const idx = transformExpr(e.idx);
|
|
649
700
|
if (e.obj.ty.kind === "map") {
|
|
@@ -751,7 +802,7 @@ function lowerExpr(e, binds) {
|
|
|
751
802
|
const result = { kind: "methodCall", obj: recv, objTy: e.fn.obj.ty, method, args, monadic: needsMonadic };
|
|
752
803
|
// Monadic HOF call is itself monadic — lift via binds like a method call
|
|
753
804
|
if (_opts.monadic && needsMonadic && binds) {
|
|
754
|
-
const name = `_t${_liftCounter++}
|
|
805
|
+
const name = freshName(`_t${_liftCounter++}`);
|
|
755
806
|
binds.push({ kind: "let-bind", name, value: result });
|
|
756
807
|
return { kind: "var", name };
|
|
757
808
|
}
|
|
@@ -911,7 +962,7 @@ function lowerExpr(e, binds) {
|
|
|
911
962
|
case "havoc":
|
|
912
963
|
// Dafny's * only works in var/assign positions — lift to own declaration
|
|
913
964
|
if (binds) {
|
|
914
|
-
const name = `_t${_liftCounter++}
|
|
965
|
+
const name = freshName(`_t${_liftCounter++}`);
|
|
915
966
|
binds.push({ kind: "let", name, type: e.ty, mutable: false, value: { kind: "havoc", type: e.ty } });
|
|
916
967
|
return { kind: "var", name };
|
|
917
968
|
}
|
|
@@ -945,8 +996,8 @@ function lowerExpr(e, binds) {
|
|
|
945
996
|
return {
|
|
946
997
|
kind: "match", scrutinee,
|
|
947
998
|
arms: [
|
|
948
|
-
{ pattern:
|
|
949
|
-
{ pattern: "
|
|
999
|
+
{ pattern: pCtor("some", e.binder), body: someBody },
|
|
1000
|
+
{ pattern: pCtor("none"), body: noneBody },
|
|
950
1001
|
],
|
|
951
1002
|
};
|
|
952
1003
|
}
|
|
@@ -988,7 +1039,7 @@ function lowerExpr(e, binds) {
|
|
|
988
1039
|
let body = lowerExpr(e.fallthrough, binds);
|
|
989
1040
|
if (wrapOpt)
|
|
990
1041
|
body = wrapOptionalBranch(body, e.fallthrough);
|
|
991
|
-
arms.push({ pattern:
|
|
1042
|
+
arms.push({ pattern: pWild(), body });
|
|
992
1043
|
}
|
|
993
1044
|
return { kind: "match", scrutinee: varName ?? scrutinee, arms };
|
|
994
1045
|
}
|
|
@@ -1030,7 +1081,7 @@ function ensuresToMatch(e, typeDecls) {
|
|
|
1030
1081
|
const pattern = buildMatchPattern(variantName, fields, obj.name);
|
|
1031
1082
|
let rhs = transformExpr(e.right);
|
|
1032
1083
|
rhs = replaceFieldAccess(rhs, obj.name, fields);
|
|
1033
|
-
return { kind: "match", scrutinee: obj.name, arms: [{ pattern, body: rhs }, { pattern:
|
|
1084
|
+
return { kind: "match", scrutinee: obj.name, arms: [{ pattern, body: rhs }, { pattern: pWild(), body: { kind: "bool", value: true } }] };
|
|
1034
1085
|
}
|
|
1035
1086
|
function replaceFieldAccess(e, varName, fields) {
|
|
1036
1087
|
return mapExpr(e, x => {
|
|
@@ -1217,8 +1268,8 @@ function matchToIfChains(stmts) {
|
|
|
1217
1268
|
if (s.kind !== "match")
|
|
1218
1269
|
return [s];
|
|
1219
1270
|
const arms = s.arms.map(a => ({ ...a, body: matchToIfChains(a.body) }));
|
|
1220
|
-
const ctorArms = arms.filter(a => a.pattern.
|
|
1221
|
-
const firstCtor = ctorArms[0]
|
|
1271
|
+
const ctorArms = arms.filter(a => a.pattern.kind !== "wild");
|
|
1272
|
+
const firstCtor = ctorArms[0] ? patternCtor(ctorArms[0].pattern) : undefined;
|
|
1222
1273
|
const decl = firstCtor
|
|
1223
1274
|
? _typeDecls.find(d => (d.kind === "discriminated-union" || d.kind === "string-union") &&
|
|
1224
1275
|
((d.variants?.some(v => v.name === firstCtor)) || (d.values?.includes(firstCtor))))
|
|
@@ -1226,15 +1277,14 @@ function matchToIfChains(stmts) {
|
|
|
1226
1277
|
if (!decl)
|
|
1227
1278
|
return [{ ...s, arms }]; // not a user union (e.g. Option) — leave as match
|
|
1228
1279
|
const scrutExpr = typeof s.scrutinee === "string" ? { kind: "var", name: s.scrutinee } : s.scrutinee;
|
|
1229
|
-
const defaultArm = arms.find(a => a.pattern.
|
|
1280
|
+
const defaultArm = arms.find(a => a.pattern.kind === "wild");
|
|
1230
1281
|
let elseBranch = defaultArm ? defaultArm.body : [];
|
|
1231
1282
|
for (let k = ctorArms.length - 1; k >= 0; k--) {
|
|
1232
1283
|
const armBody = ctorArms[k].body;
|
|
1233
1284
|
if (armBody.length === 0)
|
|
1234
1285
|
continue; // empty arm (no-op) — let it fall through to `else`
|
|
1235
|
-
const
|
|
1236
|
-
const
|
|
1237
|
-
const binders = toks.slice(1);
|
|
1286
|
+
const ctor = patternCtor(ctorArms[k].pattern) ?? "";
|
|
1287
|
+
const binders = patternBinders(ctorArms[k].pattern);
|
|
1238
1288
|
const variant = decl.variants?.find(v => v.name === ctor);
|
|
1239
1289
|
// Discriminator condition. A nullary discriminated-union constructor would
|
|
1240
1290
|
// need `DecidableEq` for `x = .Ctor` (which such unions don't derive), so
|
|
@@ -1242,8 +1292,8 @@ function matchToIfChains(stmts) {
|
|
|
1242
1292
|
// string-unions derive DecidableEq, so `=` is fine there.
|
|
1243
1293
|
const cond = decl.kind === "discriminated-union" && binders.length === 0
|
|
1244
1294
|
? { kind: "match", scrutinee: scrutExpr, arms: [
|
|
1245
|
-
{ pattern:
|
|
1246
|
-
{ pattern:
|
|
1295
|
+
{ pattern: pCtor(ctor), body: { kind: "bool", value: true } },
|
|
1296
|
+
{ pattern: pWild(), body: { kind: "bool", value: false } }
|
|
1247
1297
|
] }
|
|
1248
1298
|
: { kind: "binop", op: "=", left: scrutExpr, right: { kind: "constructor", name: ctor, type: decl.name } };
|
|
1249
1299
|
// Bind only the constructor-field binders the body actually uses, pinning the
|
|
@@ -1357,11 +1407,11 @@ function transformStmts(stmts, typeDecls) {
|
|
|
1357
1407
|
const count = _forofCounters.get(keyName) ?? 0;
|
|
1358
1408
|
_forofCounters.set(keyName, count + 1);
|
|
1359
1409
|
const suffix = count === 0 ? "" : `${count + 1}`;
|
|
1360
|
-
const keysSeqName = `_${keyName}_keys${suffix}
|
|
1410
|
+
const keysSeqName = freshName(`_${keyName}_keys${suffix}`);
|
|
1361
1411
|
const convExpr = { kind: "app", fn: "SetToSeq", args: [{ kind: "field", obj: iterExpr, field: "keys" }] };
|
|
1362
1412
|
result.push({ kind: "let", name: keysSeqName, type: { kind: "array", elem: keyTy }, mutable: false, value: convExpr });
|
|
1363
1413
|
const keysVar = { kind: "var", name: keysSeqName };
|
|
1364
|
-
const idxName = `_${keyName}_idx${suffix}
|
|
1414
|
+
const idxName = freshName(`_${keyName}_idx${suffix}`);
|
|
1365
1415
|
const idx = { kind: "var", name: idxName };
|
|
1366
1416
|
const arrSize = { kind: "field", obj: keysVar, field: "size" };
|
|
1367
1417
|
const bodyStmts = eliminateTopLevelContinue(transformStmts(s.body, typeDecls));
|
|
@@ -1383,11 +1433,11 @@ function transformStmts(stmts, typeDecls) {
|
|
|
1383
1433
|
const count = _forofCounters.get(keyName) ?? 0;
|
|
1384
1434
|
_forofCounters.set(keyName, count + 1);
|
|
1385
1435
|
const suffix = count === 0 ? "" : `${count + 1}`;
|
|
1386
|
-
const keysSeqName = `_${keyName}_keys${suffix}
|
|
1436
|
+
const keysSeqName = freshName(`_${keyName}_keys${suffix}`);
|
|
1387
1437
|
const convExpr = { kind: "app", fn: "SetToSeq", args: [{ kind: "field", obj: iterExpr, field: "keys" }] };
|
|
1388
1438
|
result.push({ kind: "let", name: keysSeqName, type: { kind: "array", elem: keyTy }, mutable: false, value: convExpr });
|
|
1389
1439
|
const keysVar = { kind: "var", name: keysSeqName };
|
|
1390
|
-
const idxName = `_${keyName}_idx${suffix}
|
|
1440
|
+
const idxName = freshName(`_${keyName}_idx${suffix}`);
|
|
1391
1441
|
const idx = { kind: "var", name: idxName };
|
|
1392
1442
|
const arrSize = { kind: "field", obj: keysVar, field: "size" };
|
|
1393
1443
|
const bodyStmts = eliminateTopLevelContinue(transformStmts(s.body, typeDecls));
|
|
@@ -1405,7 +1455,7 @@ function transformStmts(stmts, typeDecls) {
|
|
|
1405
1455
|
}
|
|
1406
1456
|
// Sets aren't indexable — bind SetToSeq to a variable for iteration
|
|
1407
1457
|
if (s.iterable.ty.kind === "set") {
|
|
1408
|
-
const seqName = `_${varName}_seq
|
|
1458
|
+
const seqName = freshName(`_${varName}_seq`);
|
|
1409
1459
|
const convExpr = { kind: "app", fn: "SetToSeq", args: [iterExpr] };
|
|
1410
1460
|
const elemTy = varTy.kind !== "unknown" ? varTy : { kind: "string" };
|
|
1411
1461
|
result.push({ kind: "let", name: seqName, type: { kind: "array", elem: elemTy }, mutable: false, value: convExpr });
|
|
@@ -1414,7 +1464,7 @@ function transformStmts(stmts, typeDecls) {
|
|
|
1414
1464
|
const count = _forofCounters.get(varName) ?? 0;
|
|
1415
1465
|
_forofCounters.set(varName, count + 1);
|
|
1416
1466
|
const suffix = count === 0 ? "" : `${count + 1}`;
|
|
1417
|
-
const idxName = `_${varName}_idx${suffix}
|
|
1467
|
+
const idxName = freshName(`_${varName}_idx${suffix}`);
|
|
1418
1468
|
const idx = { kind: "var", name: idxName };
|
|
1419
1469
|
const arrSize = { kind: "field", obj: iterExpr, field: "size" };
|
|
1420
1470
|
const bodyStmts = eliminateTopLevelContinue(transformStmts(s.body, typeDecls));
|
|
@@ -1489,15 +1539,17 @@ function transformStmt(s, typeDecls) {
|
|
|
1489
1539
|
const arrIR = transformExpr(arrExpr);
|
|
1490
1540
|
const arrTy = arrExpr.ty;
|
|
1491
1541
|
const elemTy = arrTy.kind === "array" ? arrTy.elem : { kind: "unknown" };
|
|
1492
|
-
const idxName = `_${param}_idx
|
|
1542
|
+
const idxName = freshName(`_${param}_idx`);
|
|
1493
1543
|
const idx = { kind: "var", name: idxName };
|
|
1494
1544
|
const arrSize = { kind: "field", obj: arrIR, field: "size" };
|
|
1495
1545
|
const elemVar = { kind: "var", name: param };
|
|
1496
1546
|
const keyIR = transformExpr(keyExpr);
|
|
1497
1547
|
const valIR = transformExpr(valExpr);
|
|
1498
1548
|
const mapSet = { kind: "methodCall", obj: { kind: "var", name: s.name }, objTy: s.ty, method: "set", args: [keyIR, valIR], monadic: false };
|
|
1499
|
-
// Auto-invariant: all processed elements' keys are in the map
|
|
1500
|
-
|
|
1549
|
+
// Auto-invariant: all processed elements' keys are in the map. The
|
|
1550
|
+
// quantifier wraps user expressions, so its binder must be fresh.
|
|
1551
|
+
const kiName = freshName("ki");
|
|
1552
|
+
const kVar = { kind: "var", name: kiName };
|
|
1501
1553
|
const mapHasKey = {
|
|
1502
1554
|
kind: "implies",
|
|
1503
1555
|
premises: [
|
|
@@ -1506,7 +1558,7 @@ function transformStmt(s, typeDecls) {
|
|
|
1506
1558
|
],
|
|
1507
1559
|
conclusion: { kind: "methodCall", obj: { kind: "var", name: s.name }, objTy: s.ty, method: "has", args: [keyIR.kind === "field" ? { kind: "field", obj: { kind: "index", arr: arrIR, idx: kVar }, field: keyIR.field } : keyIR], monadic: false },
|
|
1508
1560
|
};
|
|
1509
|
-
const autoInv = { kind: "forall", var:
|
|
1561
|
+
const autoInv = { kind: "forall", var: kiName, type: { kind: "int" }, body: mapHasKey };
|
|
1510
1562
|
const stmts = [
|
|
1511
1563
|
{ kind: "let", name: s.name, type: s.ty, mutable: true, value: { kind: "emptyMap" } },
|
|
1512
1564
|
{ kind: "forin", idx: idxName, bound: arrSize,
|
|
@@ -1627,8 +1679,8 @@ function transformStmt(s, typeDecls) {
|
|
|
1627
1679
|
return [{
|
|
1628
1680
|
kind: "match", scrutinee,
|
|
1629
1681
|
arms: [
|
|
1630
|
-
{ pattern:
|
|
1631
|
-
{ pattern: "
|
|
1682
|
+
{ pattern: pCtor("some", s.binder), body: someBody },
|
|
1683
|
+
{ pattern: pCtor("none"), body: noneBody },
|
|
1632
1684
|
],
|
|
1633
1685
|
}];
|
|
1634
1686
|
}
|
|
@@ -1712,7 +1764,7 @@ function emitMatchStmt(scrutinee, typeName, cases, fallthrough, typeDecls) {
|
|
|
1712
1764
|
arms.push({ pattern, body });
|
|
1713
1765
|
}
|
|
1714
1766
|
else {
|
|
1715
|
-
arms.push({ pattern:
|
|
1767
|
+
arms.push({ pattern: pWild(), body: transformStmts(fallthrough, typeDecls) });
|
|
1716
1768
|
}
|
|
1717
1769
|
}
|
|
1718
1770
|
return { kind: "match", scrutinee: isPath ? transformExpr(scrutinee) : prefix, arms };
|
|
@@ -1768,7 +1820,7 @@ function emitSwitchStmt(s, typeDecls) {
|
|
|
1768
1820
|
? buildMatchArms(cases, undefined, ef.enumTyName, typeDecls, (body) => transformStmts(body, typeDecls))
|
|
1769
1821
|
: buildMatchArms(cases, s.expr.kind === "var" ? s.expr.name : "?", s.expr.ty.kind === "user" ? s.expr.ty.name : undefined, typeDecls, (body, vn, fields) => transformStmts(replaceFieldAccessInTStmts(body, vn, fields), typeDecls));
|
|
1770
1822
|
if (s.defaultBody.length > 0)
|
|
1771
|
-
arms.push({ pattern:
|
|
1823
|
+
arms.push({ pattern: pWild(), body: transformStmts(s.defaultBody, typeDecls) });
|
|
1772
1824
|
return { kind: "match", scrutinee: ef ? ef.scrutinee : (s.expr.kind === "var" ? s.expr.name : "?"), arms };
|
|
1773
1825
|
}
|
|
1774
1826
|
/** Replace obj.field → replacement var in typed IR.
|
|
@@ -1900,8 +1952,8 @@ function transformPureBody(stmts, typeDecls) {
|
|
|
1900
1952
|
return {
|
|
1901
1953
|
kind: "match", scrutinee,
|
|
1902
1954
|
arms: [
|
|
1903
|
-
{ pattern:
|
|
1904
|
-
{ pattern: "
|
|
1955
|
+
{ pattern: pCtor("some", s.binder), body: someExpr },
|
|
1956
|
+
{ pattern: pCtor("none"), body: noneExpr },
|
|
1905
1957
|
],
|
|
1906
1958
|
};
|
|
1907
1959
|
}
|
|
@@ -1923,7 +1975,7 @@ function transformPureSwitch(s, typeDecls) {
|
|
|
1923
1975
|
const body = transformPureBody(s.defaultBody, typeDecls);
|
|
1924
1976
|
if (!body)
|
|
1925
1977
|
return null;
|
|
1926
|
-
arms.push({ pattern:
|
|
1978
|
+
arms.push({ pattern: pWild(), body });
|
|
1927
1979
|
}
|
|
1928
1980
|
return { kind: "match", scrutinee: ef.scrutinee, arms };
|
|
1929
1981
|
}
|
|
@@ -1946,7 +1998,7 @@ function transformPureSwitch(s, typeDecls) {
|
|
|
1946
1998
|
const body = transformPureBody(s.defaultBody, typeDecls);
|
|
1947
1999
|
if (!body)
|
|
1948
2000
|
return null;
|
|
1949
|
-
arms.push({ pattern:
|
|
2001
|
+
arms.push({ pattern: pWild(), body });
|
|
1950
2002
|
}
|
|
1951
2003
|
if (s.expr.kind !== "var")
|
|
1952
2004
|
return null;
|
|
@@ -1994,7 +2046,7 @@ function transformPureMatch(chain, typeDecls) {
|
|
|
1994
2046
|
const body = transformPureBody(chain.fallthrough, typeDecls);
|
|
1995
2047
|
if (!body)
|
|
1996
2048
|
return null;
|
|
1997
|
-
arms.push({ pattern:
|
|
2049
|
+
arms.push({ pattern: pWild(), body });
|
|
1998
2050
|
}
|
|
1999
2051
|
}
|
|
2000
2052
|
return { kind: "match", scrutinee: chain.varName, arms };
|