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.
@@ -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
- if (fields.length === 0)
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: `.some ${bound}`, body: truthy ? { kind: "unop", op: "¬", expr: truthy } : { kind: "bool", value: false } },
384
- { pattern: ".none", body: { kind: "bool", value: true } },
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: ".some _", body: { kind: "bool", value: !isNone } },
437
- { pattern: ".none", body: { kind: "bool", value: isNone } },
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: `.some ${bound}`, body: { kind: "binop", op: cmpOp, left: { kind: "var", name: bound }, right: valExpr } },
455
- { pattern: ".none", body: { kind: "bool", value: noneVal } },
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: `.some ${bound}`, body: {
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: ".none", body: { kind: "var", name: "undefined" } },
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: `.some ${bound}`, body: someBody },
498
- { pattern: ".none", body: defaultExpr },
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: `.some ${e.binder}`, body: someBody },
949
- { pattern: ".none", body: noneBody },
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: "_", body });
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: "_", body: { kind: "bool", value: true } }] };
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.trim() !== "_");
1221
- const firstCtor = ctorArms[0]?.pattern.trim().split(/\s+/)[0].replace(/^\./, "");
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.trim() === "_");
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 toks = ctorArms[k].pattern.trim().split(/\s+/);
1236
- const ctor = toks[0].replace(/^\./, "");
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: `.${ctor}`, body: { kind: "bool", value: true } },
1246
- { pattern: "_", body: { kind: "bool", value: false } }
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
- const kVar = { kind: "var", name: "ki" };
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: "ki", type: { kind: "int" }, body: mapHasKey };
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: `.some ${s.binder}`, body: someBody },
1631
- { pattern: ".none", body: noneBody },
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: "_", body: transformStmts(fallthrough, typeDecls) });
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: "_", body: transformStmts(s.defaultBody, typeDecls) });
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: `.some ${s.binder}`, body: someExpr },
1904
- { pattern: ".none", body: noneExpr },
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: "_", body });
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: "_", body });
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: "_", body });
2049
+ arms.push({ pattern: pWild(), body });
1998
2050
  }
1999
2051
  }
2000
2052
  return { kind: "match", scrutinee: chain.varName, arms };