lemmascript 0.2.0 → 0.3.1
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 +3 -2
- package/package.json +1 -1
- package/tools/dist/dafny-commands.js +4 -3
- package/tools/dist/dafny-emit.js +206 -161
- package/tools/dist/extract.js +483 -51
- package/tools/dist/lean-emit.js +6 -2
- package/tools/dist/lsc.js +28 -5
- package/tools/dist/resolve.js +353 -113
- package/tools/dist/specparser.js +5 -2
- package/tools/dist/transform.js +452 -117
- package/tools/dist/types.js +14 -1
package/tools/dist/transform.js
CHANGED
|
@@ -35,7 +35,10 @@ function mapExpr(e, f) {
|
|
|
35
35
|
case "record": return { ...e, spread: e.spread ? r(e.spread) : null, fields: e.fields.map(fi => ({ ...fi, value: r(fi.value) })) };
|
|
36
36
|
case "arrayLiteral": return { ...e, elems: e.elems.map(r) };
|
|
37
37
|
case "if": return { ...e, cond: r(e.cond), then: r(e.then), else: r(e.else) };
|
|
38
|
-
case "match":
|
|
38
|
+
case "match": {
|
|
39
|
+
const scr = typeof e.scrutinee === "string" ? e.scrutinee : r(e.scrutinee);
|
|
40
|
+
return { ...e, scrutinee: scr, arms: e.arms.map(a => ({ ...a, body: r(a.body) })) };
|
|
41
|
+
}
|
|
39
42
|
case "forall": return { ...e, body: r(e.body) };
|
|
40
43
|
case "exists": return { ...e, body: r(e.body) };
|
|
41
44
|
case "let": return { ...e, value: r(e.value), body: r(e.body) };
|
|
@@ -63,8 +66,51 @@ function mapStmt(s, f) {
|
|
|
63
66
|
case "assert": return { ...s, expr: r(s.expr) };
|
|
64
67
|
}
|
|
65
68
|
}
|
|
66
|
-
|
|
67
|
-
|
|
69
|
+
/** Map over all sub-expressions in a TExpr (typed IR). */
|
|
70
|
+
function mapTExpr(e, f) {
|
|
71
|
+
const hit = f(e);
|
|
72
|
+
if (hit)
|
|
73
|
+
return hit;
|
|
74
|
+
const r = (x) => mapTExpr(x, f);
|
|
75
|
+
switch (e.kind) {
|
|
76
|
+
case "var":
|
|
77
|
+
case "num":
|
|
78
|
+
case "str":
|
|
79
|
+
case "bool":
|
|
80
|
+
case "result":
|
|
81
|
+
case "havoc": return e;
|
|
82
|
+
case "binop": return { ...e, left: r(e.left), right: r(e.right) };
|
|
83
|
+
case "unop": return { ...e, expr: r(e.expr) };
|
|
84
|
+
case "call": return { ...e, fn: r(e.fn), args: e.args.map(r) };
|
|
85
|
+
case "index": return { ...e, obj: r(e.obj), idx: r(e.idx) };
|
|
86
|
+
case "field": return { ...e, obj: r(e.obj) };
|
|
87
|
+
case "record": return { ...e, spread: e.spread ? r(e.spread) : null, fields: e.fields.map(fi => ({ ...fi, value: r(fi.value) })) };
|
|
88
|
+
case "arrayLiteral": return { ...e, elems: e.elems.map(r) };
|
|
89
|
+
case "conditional": return { ...e, cond: r(e.cond), then: r(e.then), else: r(e.else) };
|
|
90
|
+
case "forall": return { ...e, body: r(e.body) };
|
|
91
|
+
case "exists": return { ...e, body: r(e.body) };
|
|
92
|
+
case "lambda": return e;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/** Map over all expressions in a TStmt tree (typed IR). */
|
|
96
|
+
function mapTStmt(s, f) {
|
|
97
|
+
const r = (e) => mapTExpr(e, f);
|
|
98
|
+
switch (s.kind) {
|
|
99
|
+
case "let": return { ...s, init: r(s.init) };
|
|
100
|
+
case "assign": return { ...s, value: r(s.value) };
|
|
101
|
+
case "return": return { ...s, value: r(s.value) };
|
|
102
|
+
case "break":
|
|
103
|
+
case "continue":
|
|
104
|
+
case "throw": return s;
|
|
105
|
+
case "expr": return { ...s, expr: r(s.expr) };
|
|
106
|
+
case "if": return { ...s, cond: r(s.cond), then: s.then.map(t => mapTStmt(t, f)), else: s.else.map(t => mapTStmt(t, f)) };
|
|
107
|
+
case "while": return { ...s, cond: r(s.cond), invariants: s.invariants.map(r), body: s.body.map(t => mapTStmt(t, f)) };
|
|
108
|
+
case "switch": return { ...s, expr: r(s.expr), cases: s.cases.map(c => ({ ...c, body: c.body.map(t => mapTStmt(t, f)) })), defaultBody: s.defaultBody.map(t => mapTStmt(t, f)) };
|
|
109
|
+
case "forof": return { ...s, iterable: r(s.iterable), invariants: s.invariants.map(r), body: s.body.map(t => mapTStmt(t, f)) };
|
|
110
|
+
case "ghostLet": return { ...s, init: r(s.init) };
|
|
111
|
+
case "ghostAssign": return { ...s, value: r(s.value) };
|
|
112
|
+
case "assert": return { ...s, expr: r(s.expr) };
|
|
113
|
+
}
|
|
68
114
|
}
|
|
69
115
|
export const LEAN_OPTIONS = {
|
|
70
116
|
backend: "lean",
|
|
@@ -75,10 +121,20 @@ export const DAFNY_OPTIONS = {
|
|
|
75
121
|
monadic: false,
|
|
76
122
|
};
|
|
77
123
|
/** Active options — set before each transform call. */
|
|
78
|
-
let _opts =
|
|
79
|
-
/**
|
|
80
|
-
|
|
81
|
-
|
|
124
|
+
let _opts = DAFNY_OPTIONS;
|
|
125
|
+
/** Type declarations — set once per module transform for discriminated union handling. */
|
|
126
|
+
let _typeDecls = [];
|
|
127
|
+
/** Prefix match-bound field names to avoid capturing user variables.
|
|
128
|
+
* When prefix is given (the scrutinee name), include it to avoid
|
|
129
|
+
* collisions in nested matches on different variables. */
|
|
130
|
+
function matchBinder(fieldName, prefix) {
|
|
131
|
+
return prefix ? `_${prefix}_${fieldName}` : `_${fieldName}`;
|
|
132
|
+
}
|
|
133
|
+
/** Build a match arm pattern like `.VariantName _v_field1 _v_field2` from variant info. */
|
|
134
|
+
function buildMatchPattern(variantName, fields, scopePrefix) {
|
|
135
|
+
if (fields.length === 0)
|
|
136
|
+
return `.${variantName}`;
|
|
137
|
+
return `.${variantName} ${fields.map(f => matchBinder(f.name, scopePrefix)).join(" ")}`;
|
|
82
138
|
}
|
|
83
139
|
const _forofCounters = new Map();
|
|
84
140
|
function isNat(ty) { return ty.kind === "nat"; }
|
|
@@ -124,6 +180,13 @@ function transformExpr(e) { return lowerExpr(e, null); }
|
|
|
124
180
|
* a method call can appear inline in TS. It does NOT propagate into
|
|
125
181
|
* field, index, record, forall, or exists sub-expressions.
|
|
126
182
|
*/
|
|
183
|
+
/** Wrap an expression in Some/None for optional-typed conditionals.
|
|
184
|
+
* If the raw TExpr is `undefined`, emit `.none`; otherwise wrap in `Some`. */
|
|
185
|
+
function wrapOptionalBranch(expr, raw) {
|
|
186
|
+
return (raw.kind === "var" && raw.name === "undefined")
|
|
187
|
+
? { kind: "constructor", name: ".none" }
|
|
188
|
+
: { kind: "app", fn: "Some", args: [expr] };
|
|
189
|
+
}
|
|
127
190
|
function lowerExpr(e, binds) {
|
|
128
191
|
// Monadic lifting: extract embedded method calls to let-binds
|
|
129
192
|
// Pass binds through to args so nested method calls are also lifted
|
|
@@ -215,6 +278,11 @@ function lowerExpr(e, binds) {
|
|
|
215
278
|
],
|
|
216
279
|
};
|
|
217
280
|
}
|
|
281
|
+
// || undefined on optional → identity (no-op: x || undefined = x)
|
|
282
|
+
if (e.op === "||" && e.left.ty.kind === "optional" &&
|
|
283
|
+
e.right.kind === "var" && e.right.name === "undefined") {
|
|
284
|
+
return lowerExpr(e.left, binds);
|
|
285
|
+
}
|
|
218
286
|
// || on optional → match Some/None with default
|
|
219
287
|
if (e.op === "||" && e.left.ty.kind === "optional") {
|
|
220
288
|
const optExpr = lowerExpr(e.left, binds);
|
|
@@ -228,6 +296,43 @@ function lowerExpr(e, binds) {
|
|
|
228
296
|
],
|
|
229
297
|
};
|
|
230
298
|
}
|
|
299
|
+
// || on map index → if key in map then map[key] else default
|
|
300
|
+
if (e.op === "||" && e.left.kind === "index" && e.left.obj.ty.kind === "map") {
|
|
301
|
+
const map = lowerExpr(e.left.obj, binds);
|
|
302
|
+
const key = lowerExpr(e.left.idx, binds);
|
|
303
|
+
const right = lowerExpr(e.right, binds);
|
|
304
|
+
return {
|
|
305
|
+
kind: "if",
|
|
306
|
+
cond: { kind: "binop", op: "in", left: key, right: map },
|
|
307
|
+
then: { kind: "index", arr: map, idx: key }, else: right,
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
// || on non-optional string/array/user → if non-empty then x else default
|
|
311
|
+
if (e.op === "||" && (e.left.ty.kind === "string" || e.left.ty.kind === "array" ||
|
|
312
|
+
(e.left.ty.kind === "user" && e.right.ty.kind === "string"))) {
|
|
313
|
+
const left = lowerExpr(e.left, binds);
|
|
314
|
+
const right = lowerExpr(e.right, binds);
|
|
315
|
+
return {
|
|
316
|
+
kind: "if",
|
|
317
|
+
cond: { kind: "binop", op: ">", left: { kind: "field", obj: left, field: "size" }, right: { kind: "num", value: 0 } },
|
|
318
|
+
then: left, else: right,
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
// int + string → NatToString(int) + string (string concatenation)
|
|
322
|
+
if (e.op === "+" && _opts.backend === "dafny") {
|
|
323
|
+
const isIntL = e.left.ty.kind === "int" || e.left.ty.kind === "nat";
|
|
324
|
+
const isIntR = e.right.ty.kind === "int" || e.right.ty.kind === "nat";
|
|
325
|
+
if (isIntL && e.right.ty.kind === "string") {
|
|
326
|
+
return { kind: "binop", op: "+",
|
|
327
|
+
left: { kind: "app", fn: "NatToString", args: [lowerExpr(e.left, binds)] },
|
|
328
|
+
right: lowerExpr(e.right, binds) };
|
|
329
|
+
}
|
|
330
|
+
if (e.left.ty.kind === "string" && isIntR) {
|
|
331
|
+
return { kind: "binop", op: "+",
|
|
332
|
+
left: lowerExpr(e.left, binds),
|
|
333
|
+
right: { kind: "app", fn: "NatToString", args: [lowerExpr(e.right, binds)] } };
|
|
334
|
+
}
|
|
335
|
+
}
|
|
231
336
|
return {
|
|
232
337
|
kind: "binop",
|
|
233
338
|
op: OP_MAP[e.op] ?? e.op,
|
|
@@ -249,6 +354,15 @@ function lowerExpr(e, binds) {
|
|
|
249
354
|
return { kind: "index", arr: transformExpr(e.obj), idx: wrappedIdx };
|
|
250
355
|
}
|
|
251
356
|
case "call": {
|
|
357
|
+
// Math.abs/min/max → preamble functions
|
|
358
|
+
if (e.fn.kind === "field" && e.fn.obj.kind === "var" && e.fn.obj.name === "Math") {
|
|
359
|
+
if (e.fn.field === "abs" && e.args.length === 1)
|
|
360
|
+
return { kind: "app", fn: "MathAbs", args: [lowerExpr(e.args[0], binds)] };
|
|
361
|
+
if (e.fn.field === "min" && e.args.length === 2)
|
|
362
|
+
return { kind: "app", fn: "MathMin", args: e.args.map(a => lowerExpr(a, binds)) };
|
|
363
|
+
if (e.fn.field === "max" && e.args.length === 2)
|
|
364
|
+
return { kind: "app", fn: "MathMax", args: e.args.map(a => lowerExpr(a, binds)) };
|
|
365
|
+
}
|
|
252
366
|
// Math.ceil(x): CeilReal on real args, identity on int
|
|
253
367
|
if (e.fn.kind === "field" && e.fn.field === "ceil" && e.fn.obj.kind === "var" && e.fn.obj.name === "Math" && e.args.length === 1) {
|
|
254
368
|
const arg = e.args[0];
|
|
@@ -296,13 +410,82 @@ function lowerExpr(e, binds) {
|
|
|
296
410
|
const prefix = e.callKind === "spec-pure" && _opts.backend === "lean" ? "Pure." : "";
|
|
297
411
|
return { kind: "app", fn: prefix + e.fn.name, args: e.args.map(a => lowerExpr(a, binds)) };
|
|
298
412
|
}
|
|
299
|
-
case "record":
|
|
300
|
-
|
|
413
|
+
case "record": {
|
|
414
|
+
// Discriminated union: { kind: 'NoOp' } → constructor NoOp
|
|
415
|
+
if (e.ty.kind === "user" && !e.spread) {
|
|
416
|
+
const tyName = e.ty.name;
|
|
417
|
+
// Match base type name (strip generic args: "Result<Model, Err>" → "Result")
|
|
418
|
+
const baseName = tyName.includes("<") ? tyName.slice(0, tyName.indexOf("<")) : tyName;
|
|
419
|
+
const decl = _typeDecls.find(d => d.name === baseName && (d.kind === "discriminated-union" || d.kind === "string-union"));
|
|
420
|
+
if (decl && decl.discriminant) {
|
|
421
|
+
const discField = e.fields.find(f => f.name === decl.discriminant);
|
|
422
|
+
if (discField && (discField.value.kind === "str" || discField.value.kind === "bool")) {
|
|
423
|
+
const variantName = String(discField.value.kind === "str" ? discField.value.value : discField.value.value);
|
|
424
|
+
const variant = decl.variants?.find(v => v.name === variantName);
|
|
425
|
+
if (variant) {
|
|
426
|
+
const nonDiscFields = e.fields.filter(f => f.name !== decl.discriminant);
|
|
427
|
+
if (nonDiscFields.length === 0) {
|
|
428
|
+
return { kind: "constructor", name: variantName, type: tyName };
|
|
429
|
+
}
|
|
430
|
+
// Constructor with args: match variant field order
|
|
431
|
+
const args = variant.fields.map(vf => {
|
|
432
|
+
const ef = nonDiscFields.find(f => f.name === vf.name);
|
|
433
|
+
return ef ? lowerExpr(ef.value, binds) : { kind: "var", name: "None" };
|
|
434
|
+
});
|
|
435
|
+
return { kind: "app", fn: variantName, args };
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
// For spread records, propagate declared field types and wrap optionals
|
|
441
|
+
if (e.spread) {
|
|
442
|
+
const spreadTy = e.spread.ty.kind === "optional" ? e.spread.ty.inner : e.spread.ty;
|
|
443
|
+
const structName = spreadTy.kind === "user" ? spreadTy.name : undefined;
|
|
444
|
+
const structDecl = structName ? _typeDecls.find(d => d.name === structName && d.kind === "record") : undefined;
|
|
445
|
+
// Also check discriminated-union variants for field types
|
|
446
|
+
const unionDecl = structName ? _typeDecls.find(d => d.name === structName && d.kind === "discriminated-union") : undefined;
|
|
447
|
+
const loweredFields = e.fields.map(f => {
|
|
448
|
+
// Propagate declared field type onto value if it has unknown type
|
|
449
|
+
let fieldValue = f.value;
|
|
450
|
+
const fieldDecl = structDecl?.fields?.find(sf => sf.name === f.name);
|
|
451
|
+
let declaredTy;
|
|
452
|
+
if (fieldDecl) {
|
|
453
|
+
declaredTy = fieldDecl.type;
|
|
454
|
+
}
|
|
455
|
+
else if (unionDecl?.variants) {
|
|
456
|
+
for (const v of unionDecl.variants) {
|
|
457
|
+
const vf = v.fields.find(vf => vf.name === f.name);
|
|
458
|
+
if (vf) {
|
|
459
|
+
declaredTy = vf.type;
|
|
460
|
+
break;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
if (declaredTy && fieldValue.ty.kind === "unknown") {
|
|
465
|
+
fieldValue = { ...fieldValue, ty: declaredTy };
|
|
466
|
+
}
|
|
467
|
+
let value = lowerExpr(fieldValue, binds);
|
|
468
|
+
// Wrap non-optional values in Some for optional fields
|
|
469
|
+
if (declaredTy?.kind === "optional") {
|
|
470
|
+
const isUndef = f.value.kind === "var" && f.value.name === "undefined";
|
|
471
|
+
if (f.value.ty.kind !== "optional" && !isUndef) {
|
|
472
|
+
value = { kind: "app", fn: "Some", args: [value] };
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
return { name: f.name, value };
|
|
476
|
+
});
|
|
477
|
+
return { kind: "record", spread: lowerExpr(e.spread, binds), fields: loweredFields };
|
|
478
|
+
}
|
|
479
|
+
return { kind: "record", spread: null, fields: e.fields.map(f => ({ name: f.name, value: lowerExpr(f.value, binds) })) };
|
|
480
|
+
}
|
|
301
481
|
case "arrayLiteral":
|
|
302
482
|
if (e.ty.kind === "map" && e.elems.length === 0)
|
|
303
483
|
return { kind: "emptyMap" };
|
|
304
484
|
if (e.ty.kind === "set" && e.elems.length === 0)
|
|
305
485
|
return { kind: "emptySet" };
|
|
486
|
+
// Set with initial elements: new Set([a, b]) → {a, b}
|
|
487
|
+
if (e.ty.kind === "set")
|
|
488
|
+
return { kind: "app", fn: "SetLiteral", args: e.elems.map(el => lowerExpr(el, binds)) };
|
|
306
489
|
return { kind: "arrayLiteral", elems: e.elems.map(el => lowerExpr(el, binds)) };
|
|
307
490
|
case "lambda":
|
|
308
491
|
return { kind: "lambda", params: e.params.map(p => ({ name: p.name, type: p.ty })), body: transformStmts(e.body, []) };
|
|
@@ -311,23 +494,53 @@ function lowerExpr(e, binds) {
|
|
|
311
494
|
case "exists":
|
|
312
495
|
return { kind: "exists", var: e.var, type: e.varTy, body: transformExpr(e.body) };
|
|
313
496
|
case "conditional": {
|
|
314
|
-
|
|
497
|
+
// When narrowedExpr is set, the match replaces the condition — don't lift from it
|
|
498
|
+
const condBinds = (e.narrowedVar && e.narrowedExpr) ? null : binds;
|
|
499
|
+
const cond = lowerExpr(e.cond, condBinds);
|
|
315
500
|
let thenExpr = lowerExpr(e.then, binds);
|
|
316
501
|
let elseExpr = lowerExpr(e.else, binds);
|
|
317
|
-
//
|
|
318
|
-
if (e.
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
thenExpr = { kind: "app", fn: "Some", args: [thenExpr] };
|
|
502
|
+
// Explicit !== undefined with narrowedExpr → match Some/None on the optional expression
|
|
503
|
+
if (e.narrowedVar && e.narrowedExpr) {
|
|
504
|
+
const scrutinee = lowerExpr(e.narrowedExpr, binds);
|
|
505
|
+
const bound = matchBinder(e.narrowedVar);
|
|
506
|
+
if (bound !== e.narrowedVar) {
|
|
507
|
+
thenExpr = replaceVar(thenExpr, e.narrowedVar, { kind: "var", name: bound });
|
|
324
508
|
}
|
|
325
|
-
|
|
326
|
-
|
|
509
|
+
// Wrap in Some/None only when result is optional (one branch is undefined)
|
|
510
|
+
if (e.ty.kind === "optional") {
|
|
511
|
+
thenExpr = wrapOptionalBranch(thenExpr, e.then);
|
|
512
|
+
elseExpr = wrapOptionalBranch(elseExpr, e.else);
|
|
327
513
|
}
|
|
328
|
-
|
|
329
|
-
|
|
514
|
+
return {
|
|
515
|
+
kind: "match", scrutinee,
|
|
516
|
+
arms: [
|
|
517
|
+
{ pattern: `.some ${bound}`, body: thenExpr },
|
|
518
|
+
{ pattern: ".none", body: elseExpr },
|
|
519
|
+
],
|
|
520
|
+
};
|
|
521
|
+
}
|
|
522
|
+
// Optional cond with narrowedVar → match Some/None (truthiness)
|
|
523
|
+
if (e.narrowedVar && e.cond.ty.kind === "optional") {
|
|
524
|
+
const bound = matchBinder(e.narrowedVar);
|
|
525
|
+
// Replace the synthetic/narrowed var with the match-bound name
|
|
526
|
+
if (bound !== e.narrowedVar) {
|
|
527
|
+
thenExpr = replaceVar(thenExpr, e.narrowedVar, { kind: "var", name: bound });
|
|
330
528
|
}
|
|
529
|
+
// The match produces an Optional: wrap branches in Some/None.
|
|
530
|
+
thenExpr = wrapOptionalBranch(thenExpr, e.then);
|
|
531
|
+
elseExpr = wrapOptionalBranch(elseExpr, e.else);
|
|
532
|
+
return {
|
|
533
|
+
kind: "match", scrutinee: cond,
|
|
534
|
+
arms: [
|
|
535
|
+
{ pattern: `.some ${bound}`, body: thenExpr },
|
|
536
|
+
{ pattern: ".none", body: elseExpr },
|
|
537
|
+
],
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
// Non-optional: regular if with optional wrapping
|
|
541
|
+
if (e.ty.kind === "optional") {
|
|
542
|
+
thenExpr = wrapOptionalBranch(thenExpr, e.then);
|
|
543
|
+
elseExpr = wrapOptionalBranch(elseExpr, e.else);
|
|
331
544
|
}
|
|
332
545
|
return { kind: "if", cond, then: thenExpr, else: elseExpr };
|
|
333
546
|
}
|
|
@@ -374,7 +587,7 @@ function ensuresToMatch(e, typeDecls) {
|
|
|
374
587
|
if (!variant)
|
|
375
588
|
return null;
|
|
376
589
|
const fields = variant.fields;
|
|
377
|
-
const pattern =
|
|
590
|
+
const pattern = buildMatchPattern(variantName, fields, obj.name);
|
|
378
591
|
let rhs = transformExpr(e.right);
|
|
379
592
|
rhs = replaceFieldAccess(rhs, obj.name, fields);
|
|
380
593
|
return { kind: "match", scrutinee: obj.name, arms: [{ pattern, body: rhs }, { pattern: "_", body: { kind: "bool", value: true } }] };
|
|
@@ -384,7 +597,7 @@ function replaceFieldAccess(e, varName, fields) {
|
|
|
384
597
|
if (x.kind === "field" && x.obj.kind === "var" && x.obj.name === varName) {
|
|
385
598
|
const f = fields.find(f => f.name === x.field);
|
|
386
599
|
if (f)
|
|
387
|
-
return { kind: "var", name: matchBinder(f.name) };
|
|
600
|
+
return { kind: "var", name: matchBinder(f.name, varName) };
|
|
388
601
|
}
|
|
389
602
|
// If this let shadows the matched variable, stop replacing in the body
|
|
390
603
|
if (x.kind === "let" && x.name === varName)
|
|
@@ -407,13 +620,12 @@ function transformStmts(stmts, typeDecls) {
|
|
|
407
620
|
continue;
|
|
408
621
|
}
|
|
409
622
|
// Detect optional check → match on Some/None
|
|
410
|
-
const
|
|
411
|
-
if (
|
|
412
|
-
|
|
413
|
-
result.push(emitOptionalMatch(opt.varName, opt.negated, s, typeDecls, rest));
|
|
623
|
+
const optMatch = prepareOptionalMatch(s, stmts.slice(i + 1));
|
|
624
|
+
if (optMatch) {
|
|
625
|
+
result.push(emitOptionalMatch(optMatch.check.varName, optMatch.check.negated, s, typeDecls, stmts.slice(i + 1), optMatch.check.fieldExpr));
|
|
414
626
|
// If rest was consumed into the Some branch, skip remaining
|
|
415
|
-
const
|
|
416
|
-
if (
|
|
627
|
+
const origSome = optMatch.check.negated ? s.else : s.then;
|
|
628
|
+
if (origSome.length === 0 && i + 1 < stmts.length) {
|
|
417
629
|
return result;
|
|
418
630
|
}
|
|
419
631
|
i++;
|
|
@@ -610,6 +822,17 @@ function transformStmt(s, typeDecls) {
|
|
|
610
822
|
return [...binds, { kind: "assign", target: "_", value: expr }];
|
|
611
823
|
}
|
|
612
824
|
case "if": {
|
|
825
|
+
// Restructure && with optional check: extract the leftmost optional check
|
|
826
|
+
// from a && chain and nest the rest inside. Handles left-associative chains:
|
|
827
|
+
// if ((x !== undefined && b) && c) → if (x !== undefined) { if (b && c) { ... } }
|
|
828
|
+
if (s.cond.kind === "binop" && s.cond.op === "&&" && s.else.length === 0) {
|
|
829
|
+
const extracted = extractLeftmostOptional(s.cond);
|
|
830
|
+
if (extracted) {
|
|
831
|
+
const innerIf = { kind: "if", cond: extracted.rest, then: s.then, else: [] };
|
|
832
|
+
const outerIf = { kind: "if", cond: extracted.optCond, then: [innerIf], else: [] };
|
|
833
|
+
return transformStmts([outerIf], typeDecls);
|
|
834
|
+
}
|
|
835
|
+
}
|
|
613
836
|
// Lift from condition only (Lean rule: don't lift from branches)
|
|
614
837
|
const { binds, expr: cond } = liftMethodCalls(s.cond);
|
|
615
838
|
return [...binds, { kind: "if", cond, then: transformStmts(s.then, typeDecls), else: transformStmts(s.else, typeDecls) }];
|
|
@@ -686,7 +909,7 @@ function parseDiscriminantCond(cond) {
|
|
|
686
909
|
return null;
|
|
687
910
|
return { varName: cond.left.obj.name, typeName: cond.left.obj.ty.name, variant: cond.right.value };
|
|
688
911
|
}
|
|
689
|
-
function emitOptionalMatch(varName, negated, s, typeDecls, restStmts) {
|
|
912
|
+
function emitOptionalMatch(varName, negated, s, typeDecls, restStmts, fieldExpr) {
|
|
690
913
|
let someBranch = negated ? s.else : s.then;
|
|
691
914
|
const noneBranch = negated ? s.then : s.else;
|
|
692
915
|
// Early-return pattern: if (x === undefined) { return ... } — Some branch is empty,
|
|
@@ -695,20 +918,87 @@ function emitOptionalMatch(varName, negated, s, typeDecls, restStmts) {
|
|
|
695
918
|
someBranch = restStmts;
|
|
696
919
|
}
|
|
697
920
|
const bound = matchBinder(`${varName}_val`);
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
921
|
+
// Replace the narrowed variable/field in the Some branch body.
|
|
922
|
+
// Field chains: replace in TStmt before transform (so downstream narrowing sees simple vars).
|
|
923
|
+
// Simple vars: replace in IR after transform (the original mechanism).
|
|
924
|
+
let someBody;
|
|
925
|
+
if (fieldExpr && fieldExpr.kind === "field" && fieldExpr.obj.kind === "var") {
|
|
926
|
+
const innerTy = fieldExpr.ty.kind === "optional" ? fieldExpr.ty.inner : fieldExpr.ty;
|
|
927
|
+
const replaced = replaceFieldsInTStmts(someBranch, fieldExpr.obj.name, [
|
|
928
|
+
{ fieldName: fieldExpr.field, newName: bound, fallbackTy: innerTy },
|
|
929
|
+
]);
|
|
930
|
+
someBody = transformStmts(replaced, typeDecls);
|
|
931
|
+
}
|
|
932
|
+
else {
|
|
933
|
+
const transformed = transformStmts(someBranch, typeDecls);
|
|
934
|
+
someBody = transformed.map(stmt => mapStmtExprs(stmt, e => replaceVar(e, varName, { kind: "var", name: bound })));
|
|
935
|
+
}
|
|
936
|
+
return {
|
|
937
|
+
kind: "match", scrutinee: varName,
|
|
938
|
+
arms: [
|
|
939
|
+
{ pattern: `.some ${bound}`, body: someBody },
|
|
940
|
+
{ pattern: ".none", body: noneBranch.length > 0 ? transformStmts(noneBranch, typeDecls) : [] },
|
|
941
|
+
],
|
|
942
|
+
};
|
|
706
943
|
}
|
|
707
944
|
/** Apply an expression transform to all expressions in a statement (convenience wrapper). */
|
|
708
945
|
function mapStmtExprs(s, r) {
|
|
709
946
|
return mapStmt(s, e => r(e));
|
|
710
947
|
}
|
|
711
|
-
|
|
948
|
+
// ── Optional narrowing helpers ──────────────────────────────
|
|
949
|
+
//
|
|
950
|
+
// Optional narrowing converts TS `if (x === undefined)` patterns to Dafny
|
|
951
|
+
// `match x { Some(val) => ..., None => ... }`.
|
|
952
|
+
//
|
|
953
|
+
// The resolve phase (resolve.ts) handles:
|
|
954
|
+
// - Flow narrowing: after `if (x === undefined) return`, x is non-optional
|
|
955
|
+
// - && narrowing: in `x !== undefined && f(x)`, f(x) sees x as non-optional
|
|
956
|
+
// - Conditional narrowing: in `x !== undefined ? x.field : default`, sets
|
|
957
|
+
// narrowedVar/narrowedExpr on TExpr for the transform phase
|
|
958
|
+
//
|
|
959
|
+
// The transform phase (here) handles:
|
|
960
|
+
// - Statement-level: `transformStmts` detects optional checks → `emitOptionalMatch`
|
|
961
|
+
// - Expression-level: `lowerExpr` conditional reads narrowedVar/narrowedExpr → match
|
|
962
|
+
// - && restructuring: `extractLeftmostOptional` splits `&&` chains into nested ifs
|
|
963
|
+
// so `emitOptionalMatch` can detect the inner optional check
|
|
964
|
+
//
|
|
965
|
+
// Both phases detect `v !== undefined` patterns. The resolve phase uses
|
|
966
|
+
// `detectOptionalCheck` (on RawExpr), the transform uses `parseOptionalCheck` (on TExpr).
|
|
967
|
+
// These are separate because they operate on different IR types, but both handle
|
|
968
|
+
// simple variables and field access chains.
|
|
969
|
+
/** Shared logic for optional match in both imperative and pure function paths.
|
|
970
|
+
* Detects optional check, selects branches, handles early-return consumption.
|
|
971
|
+
* Returns null if the condition is not an optional check. */
|
|
972
|
+
function prepareOptionalMatch(s, restStmts) {
|
|
973
|
+
const check = parseOptionalCheck(s.cond);
|
|
974
|
+
if (!check)
|
|
975
|
+
return null;
|
|
976
|
+
let someBranch = check.negated ? s.else : s.then;
|
|
977
|
+
const noneBranch = check.negated ? s.then : (s.else.length > 0 ? s.else : restStmts);
|
|
978
|
+
// Early-return pattern: Some branch is empty → consume rest of block
|
|
979
|
+
if (someBranch.length === 0 && restStmts.length > 0)
|
|
980
|
+
someBranch = restStmts;
|
|
981
|
+
const bound = matchBinder(`${check.varName}_val`);
|
|
982
|
+
return { check, someBranch, noneBranch, bound };
|
|
983
|
+
}
|
|
984
|
+
/** Extract the leftmost optional check from a && chain, returning the check and the rest.
|
|
985
|
+
* (x !== undefined && b) && c → { optCond: x !== undefined, rest: b && c } */
|
|
986
|
+
function extractLeftmostOptional(cond) {
|
|
987
|
+
if (cond.kind !== "binop" || cond.op !== "&&")
|
|
988
|
+
return null;
|
|
989
|
+
const check = parseOptionalCheck(cond.left);
|
|
990
|
+
if (check && !check.negated)
|
|
991
|
+
return { optCond: cond.left, rest: cond.right };
|
|
992
|
+
if (cond.left.kind === "binop" && cond.left.op === "&&") {
|
|
993
|
+
const inner = extractLeftmostOptional(cond.left);
|
|
994
|
+
if (inner)
|
|
995
|
+
return { optCond: inner.optCond, rest: { ...cond, left: inner.rest } };
|
|
996
|
+
}
|
|
997
|
+
return null;
|
|
998
|
+
}
|
|
999
|
+
/** Detect `v !== undefined` or `undefined !== v` where v has optional type.
|
|
1000
|
+
* Also handles field access chains like `obj.field !== undefined`.
|
|
1001
|
+
* When `fieldExpr` is returned, callers must use field-aware replacement. */
|
|
712
1002
|
function parseOptionalCheck(cond) {
|
|
713
1003
|
if (cond.kind !== "binop" || (cond.op !== "!==" && cond.op !== "==="))
|
|
714
1004
|
return null;
|
|
@@ -717,20 +1007,50 @@ function parseOptionalCheck(cond) {
|
|
|
717
1007
|
varExpr = cond.left;
|
|
718
1008
|
if (cond.left.kind === "var" && cond.left.name === "undefined")
|
|
719
1009
|
varExpr = cond.right;
|
|
720
|
-
if (!varExpr
|
|
1010
|
+
if (!varExpr)
|
|
721
1011
|
return null;
|
|
722
|
-
|
|
1012
|
+
if (varExpr.kind === "var" && varExpr.ty.kind === "optional") {
|
|
1013
|
+
return { varName: varExpr.name, negated: cond.op === "===" };
|
|
1014
|
+
}
|
|
1015
|
+
if (varExpr.kind === "field" && varExpr.ty.kind === "optional") {
|
|
1016
|
+
// Serialize field chain as a dotted name for use as match scrutinee
|
|
1017
|
+
const chain = serializeFieldChain(varExpr);
|
|
1018
|
+
if (chain)
|
|
1019
|
+
return { varName: chain, negated: cond.op === "===", fieldExpr: varExpr };
|
|
1020
|
+
}
|
|
1021
|
+
return null;
|
|
723
1022
|
}
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
1023
|
+
/** Serialize a field access chain to a dotted variable path, or null if not a simple chain. */
|
|
1024
|
+
function serializeFieldChain(e) {
|
|
1025
|
+
if (e.kind === "var")
|
|
1026
|
+
return e.name;
|
|
1027
|
+
if (e.kind === "field") {
|
|
1028
|
+
const parent = serializeFieldChain(e.obj);
|
|
1029
|
+
return parent ? `${parent}.${e.field}` : null;
|
|
1030
|
+
}
|
|
1031
|
+
return null;
|
|
1032
|
+
}
|
|
1033
|
+
/** Build match arms from variant cases — shared by imperative and pure paths.
|
|
1034
|
+
* Looks up variant fields from typeDecls, builds patterns via buildMatchPattern,
|
|
1035
|
+
* and delegates body transformation to the caller-provided function.
|
|
1036
|
+
* Returns null if any body transformation returns null (pure path abort). */
|
|
1037
|
+
function buildMatchArms(cases, varName, typeName, typeDecls, transformBody) {
|
|
1038
|
+
const decl = typeName ? typeDecls.find(d => d.name === typeName) : undefined;
|
|
1039
|
+
const arms = [];
|
|
1040
|
+
for (const c of cases) {
|
|
1041
|
+
const variant = decl?.variants?.find(v => v.name === c.name);
|
|
728
1042
|
const fields = variant?.fields ?? [];
|
|
729
|
-
const pattern =
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
1043
|
+
const pattern = buildMatchPattern(c.name, fields, varName);
|
|
1044
|
+
const body = transformBody(c.body, varName, fields);
|
|
1045
|
+
if (body === null)
|
|
1046
|
+
return null;
|
|
1047
|
+
arms.push({ pattern, body });
|
|
1048
|
+
}
|
|
1049
|
+
return arms;
|
|
1050
|
+
}
|
|
1051
|
+
function emitMatchStmt(chain, typeDecls) {
|
|
1052
|
+
const cases = chain.cases.map(c => ({ name: c.variant, body: c.body }));
|
|
1053
|
+
const arms = buildMatchArms(cases, chain.varName, chain.typeName, typeDecls, (body, vn, fields) => transformStmts(replaceFieldAccessInTStmts(body, vn, fields), typeDecls));
|
|
734
1054
|
if (chain.fallthrough.length > 0)
|
|
735
1055
|
arms.push({ pattern: "_", body: transformStmts(chain.fallthrough, typeDecls) });
|
|
736
1056
|
return { kind: "match", scrutinee: chain.varName, arms };
|
|
@@ -738,41 +1058,39 @@ function emitMatchStmt(chain, typeDecls) {
|
|
|
738
1058
|
function emitSwitchStmt(s, typeDecls) {
|
|
739
1059
|
const varName = s.expr.kind === "var" ? s.expr.name : "?";
|
|
740
1060
|
const typeName = s.expr.ty.kind === "user" ? s.expr.ty.name : undefined;
|
|
741
|
-
const
|
|
742
|
-
const arms =
|
|
743
|
-
const variant = decl?.variants?.find(v => v.name === c.label);
|
|
744
|
-
const fields = variant?.fields ?? [];
|
|
745
|
-
const pattern = fields.length > 0 ? `.${c.label} ${fields.map(f => matchBinder(f.name)).join(" ")}` : `.${c.label}`;
|
|
746
|
-
let body = transformStmts(c.body, typeDecls);
|
|
747
|
-
body = replaceFieldAccessInStmts(body, varName, fields);
|
|
748
|
-
return { pattern, body };
|
|
749
|
-
});
|
|
1061
|
+
const cases = s.cases.map(c => ({ name: c.label, body: c.body }));
|
|
1062
|
+
const arms = buildMatchArms(cases, varName, typeName, typeDecls, (body, vn, fields) => transformStmts(replaceFieldAccessInTStmts(body, vn, fields), typeDecls));
|
|
750
1063
|
if (s.defaultBody.length > 0)
|
|
751
1064
|
arms.push({ pattern: "_", body: transformStmts(s.defaultBody, typeDecls) });
|
|
752
1065
|
return { kind: "match", scrutinee: varName, arms };
|
|
753
1066
|
}
|
|
754
|
-
|
|
755
|
-
|
|
1067
|
+
/** Replace obj.field → replacement var in typed IR (before transform).
|
|
1068
|
+
* Used by discriminant match/switch and optional match to rewrite field accesses
|
|
1069
|
+
* into simple variables before the transform phase, so downstream narrowing
|
|
1070
|
+
* (parseOptionalCheck, extractLeftmostOptional) sees simple variable references.
|
|
1071
|
+
* Uses the TExpr's resolved type when available, falling back to `fallbackTy`. */
|
|
1072
|
+
function replaceFieldsInTStmts(stmts, objName, replacements) {
|
|
1073
|
+
if (replacements.length === 0)
|
|
756
1074
|
return stmts;
|
|
757
|
-
|
|
758
|
-
if (e.kind === "field" && e.obj.kind === "var" && e.obj.name ===
|
|
759
|
-
const
|
|
760
|
-
if (
|
|
761
|
-
|
|
1075
|
+
return stmts.map(s => mapTStmt(s, e => {
|
|
1076
|
+
if (e.kind === "field" && e.obj.kind === "var" && e.obj.name === objName) {
|
|
1077
|
+
const r = replacements.find(r => r.fieldName === e.field);
|
|
1078
|
+
if (r) {
|
|
1079
|
+
const ty = e.ty.kind !== "unknown" ? e.ty : r.fallbackTy;
|
|
1080
|
+
return { kind: "var", name: r.newName, ty };
|
|
1081
|
+
}
|
|
762
1082
|
}
|
|
763
1083
|
return null;
|
|
764
|
-
};
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
}
|
|
775
|
-
return result;
|
|
1084
|
+
}));
|
|
1085
|
+
}
|
|
1086
|
+
/** Replace all variant fields of obj → match binder vars in typed IR.
|
|
1087
|
+
* Thin wrapper around replaceFieldsInTStmts for discriminant match/switch. */
|
|
1088
|
+
function replaceFieldAccessInTStmts(stmts, varName, fields) {
|
|
1089
|
+
return replaceFieldsInTStmts(stmts, varName, fields.map(f => ({
|
|
1090
|
+
fieldName: f.name,
|
|
1091
|
+
newName: matchBinder(f.name, varName),
|
|
1092
|
+
fallbackTy: f.type ?? parseTsType(f.tsType),
|
|
1093
|
+
})));
|
|
776
1094
|
}
|
|
777
1095
|
// ── Pure function generation ─────────────────────────────────
|
|
778
1096
|
function transformPureBody(stmts, typeDecls) {
|
|
@@ -795,24 +1113,19 @@ function transformPureBody(stmts, typeDecls) {
|
|
|
795
1113
|
}
|
|
796
1114
|
case "if": {
|
|
797
1115
|
// Optional narrowing: if (x === undefined) → match x { None => ..., Some(x_val) => ... }
|
|
798
|
-
const
|
|
799
|
-
if (
|
|
800
|
-
|
|
801
|
-
const noneBranch = optCheck.negated ? s.then : (s.else.length > 0 ? s.else : rest);
|
|
802
|
-
if (someBranch.length === 0)
|
|
803
|
-
someBranch = rest;
|
|
804
|
-
const bound = matchBinder(`${optCheck.varName}_val`);
|
|
805
|
-
const someExpr = transformPureBody(someBranch, typeDecls);
|
|
1116
|
+
const optMatch = prepareOptionalMatch(s, rest);
|
|
1117
|
+
if (optMatch) {
|
|
1118
|
+
const someExpr = transformPureBody(optMatch.someBranch, typeDecls);
|
|
806
1119
|
if (!someExpr)
|
|
807
1120
|
return null;
|
|
808
|
-
const noneExpr = transformPureBody(noneBranch, typeDecls);
|
|
1121
|
+
const noneExpr = transformPureBody(optMatch.noneBranch, typeDecls);
|
|
809
1122
|
if (!noneExpr)
|
|
810
1123
|
return null;
|
|
811
|
-
const someReplaced = replaceVar(someExpr,
|
|
1124
|
+
const someReplaced = replaceVar(someExpr, optMatch.check.varName, { kind: "var", name: optMatch.bound });
|
|
812
1125
|
return {
|
|
813
|
-
kind: "match", scrutinee:
|
|
1126
|
+
kind: "match", scrutinee: optMatch.check.varName,
|
|
814
1127
|
arms: [
|
|
815
|
-
{ pattern: `.some ${bound}`, body: someReplaced },
|
|
1128
|
+
{ pattern: `.some ${optMatch.bound}`, body: someReplaced },
|
|
816
1129
|
{ pattern: ".none", body: noneExpr },
|
|
817
1130
|
],
|
|
818
1131
|
};
|
|
@@ -833,21 +1146,21 @@ function transformPureBody(stmts, typeDecls) {
|
|
|
833
1146
|
return null;
|
|
834
1147
|
}
|
|
835
1148
|
function transformPureSwitch(s, typeDecls) {
|
|
836
|
-
const
|
|
837
|
-
if (!
|
|
1149
|
+
const typeName = s.expr.ty.kind === "user" ? s.expr.ty.name : "";
|
|
1150
|
+
if (!typeDecls.find(d => d.name === typeName))
|
|
838
1151
|
return null;
|
|
839
|
-
const
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
let body = transformPureBody(c.body, typeDecls);
|
|
845
|
-
if (!body)
|
|
1152
|
+
const varName = s.expr.kind === "var" ? s.expr.name : undefined;
|
|
1153
|
+
const cases = s.cases.map(c => ({ name: c.label, body: c.body }));
|
|
1154
|
+
const arms = buildMatchArms(cases, varName, typeName, typeDecls, (body, vn, fields) => {
|
|
1155
|
+
let result = transformPureBody(body, typeDecls);
|
|
1156
|
+
if (!result)
|
|
846
1157
|
return null;
|
|
847
|
-
if (fields.length > 0 &&
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
}
|
|
1158
|
+
if (fields.length > 0 && vn)
|
|
1159
|
+
result = replaceFieldAccess(result, vn, fields);
|
|
1160
|
+
return result;
|
|
1161
|
+
});
|
|
1162
|
+
if (!arms)
|
|
1163
|
+
return null;
|
|
851
1164
|
if (s.defaultBody.length > 0) {
|
|
852
1165
|
const body = transformPureBody(s.defaultBody, typeDecls);
|
|
853
1166
|
if (!body)
|
|
@@ -859,22 +1172,21 @@ function transformPureSwitch(s, typeDecls) {
|
|
|
859
1172
|
return { kind: "match", scrutinee: s.expr.name, arms };
|
|
860
1173
|
}
|
|
861
1174
|
function transformPureMatch(chain, typeDecls) {
|
|
862
|
-
const
|
|
863
|
-
const arms =
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
const fields = variant?.fields ?? [];
|
|
867
|
-
const pattern = fields.length > 0 ? `.${c.variant} ${fields.map(f => matchBinder(f.name)).join(" ")}` : `.${c.variant}`;
|
|
868
|
-
let body = transformPureBody(c.body, typeDecls);
|
|
869
|
-
if (!body)
|
|
1175
|
+
const cases = chain.cases.map(c => ({ name: c.variant, body: c.body }));
|
|
1176
|
+
const arms = buildMatchArms(cases, chain.varName, chain.typeName, typeDecls, (body, vn, fields) => {
|
|
1177
|
+
let result = transformPureBody(body, typeDecls);
|
|
1178
|
+
if (!result)
|
|
870
1179
|
return null;
|
|
871
|
-
if (fields.length > 0)
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
}
|
|
1180
|
+
if (fields.length > 0 && vn)
|
|
1181
|
+
result = replaceFieldAccess(result, vn, fields);
|
|
1182
|
+
return result;
|
|
1183
|
+
});
|
|
1184
|
+
if (!arms)
|
|
1185
|
+
return null;
|
|
875
1186
|
// Idiomatic TS often has an unreachable fallthrough after exhaustive if-chains on
|
|
876
1187
|
// discriminated unions. Skip the catch-all arm when all variants are matched,
|
|
877
1188
|
// since Lean errors on redundant match arms.
|
|
1189
|
+
const decl = typeDecls.find(d => d.name === chain.typeName);
|
|
878
1190
|
const allCovered = decl?.variants && chain.cases.length >= decl.variants.length;
|
|
879
1191
|
if (chain.fallthrough.length > 0 && !allCovered) {
|
|
880
1192
|
const body = transformPureBody(chain.fallthrough, typeDecls);
|
|
@@ -896,17 +1208,24 @@ function transformTypeDecl(d) {
|
|
|
896
1208
|
else if (d.kind === "discriminated-union") {
|
|
897
1209
|
return {
|
|
898
1210
|
kind: "inductive", name: d.name,
|
|
1211
|
+
typeParams: d.typeParams,
|
|
899
1212
|
constructors: d.variants.map(v => ({
|
|
900
1213
|
name: v.name,
|
|
901
|
-
fields: v.fields.map(f => ({ name: f.name, type:
|
|
1214
|
+
fields: v.fields.map(f => ({ name: f.name, type: f.type })),
|
|
902
1215
|
})),
|
|
903
1216
|
deriving: ["Repr", "Inhabited"],
|
|
904
1217
|
};
|
|
905
1218
|
}
|
|
1219
|
+
else if (d.kind === "alias") {
|
|
1220
|
+
return {
|
|
1221
|
+
kind: "type-alias", name: d.name,
|
|
1222
|
+
target: d.aliasOfTy,
|
|
1223
|
+
};
|
|
1224
|
+
}
|
|
906
1225
|
else {
|
|
907
1226
|
return {
|
|
908
1227
|
kind: "structure", name: d.name,
|
|
909
|
-
fields: d.fields.map(f => ({ name: f.name, type:
|
|
1228
|
+
fields: d.fields.map(f => ({ name: f.name, type: f.type })),
|
|
910
1229
|
deriving: ["Repr", "Inhabited", "DecidableEq"],
|
|
911
1230
|
};
|
|
912
1231
|
}
|
|
@@ -961,6 +1280,17 @@ function replaceVar(e, name, replacement) {
|
|
|
961
1280
|
});
|
|
962
1281
|
}
|
|
963
1282
|
// ── Top-level transform ──────────────────────────────────────
|
|
1283
|
+
/** Transform for Lean backend — same logic, Lean options. */
|
|
1284
|
+
export function transformModuleLean(mod, specImport) {
|
|
1285
|
+
const prev = _opts;
|
|
1286
|
+
_opts = LEAN_OPTIONS;
|
|
1287
|
+
try {
|
|
1288
|
+
return transformModule(mod, specImport);
|
|
1289
|
+
}
|
|
1290
|
+
finally {
|
|
1291
|
+
_opts = prev;
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
964
1294
|
/** Transform for Dafny backend — same logic, Dafny options. */
|
|
965
1295
|
export function transformModuleDafny(mod) {
|
|
966
1296
|
const prev = _opts;
|
|
@@ -974,6 +1304,8 @@ export function transformModuleDafny(mod) {
|
|
|
974
1304
|
}
|
|
975
1305
|
export function transformModule(mod, specImport) {
|
|
976
1306
|
_forofCounters.clear();
|
|
1307
|
+
_liftCounter = 0;
|
|
1308
|
+
_typeDecls = mod.typeDecls;
|
|
977
1309
|
const typeDecls = mod.typeDecls.map(transformTypeDecl);
|
|
978
1310
|
// Module-level constants
|
|
979
1311
|
const constDecls = (mod.constants ?? []).map(c => ({
|
|
@@ -996,6 +1328,7 @@ export function transformModule(mod, specImport) {
|
|
|
996
1328
|
pureDefs.push({
|
|
997
1329
|
kind: "def",
|
|
998
1330
|
name: fn.name,
|
|
1331
|
+
typeParams: fn.typeParams,
|
|
999
1332
|
params: fn.params.map(p => ({ name: p.name, type: p.ty })),
|
|
1000
1333
|
returnType: fn.returnTy,
|
|
1001
1334
|
requires: fn.requires.map(transformExpr),
|
|
@@ -1046,6 +1379,7 @@ export function transformModule(mod, specImport) {
|
|
|
1046
1379
|
return {
|
|
1047
1380
|
kind: "method",
|
|
1048
1381
|
name: fn.name,
|
|
1382
|
+
typeParams: fn.typeParams,
|
|
1049
1383
|
params: fn.params.map(p => ({ name: p.name, type: p.ty })),
|
|
1050
1384
|
returnType: fn.returnTy,
|
|
1051
1385
|
requires: fn.requires.map(transformExpr),
|
|
@@ -1062,6 +1396,7 @@ export function transformModule(mod, specImport) {
|
|
|
1062
1396
|
return {
|
|
1063
1397
|
kind: "method",
|
|
1064
1398
|
name: fn.name,
|
|
1399
|
+
typeParams: fn.typeParams,
|
|
1065
1400
|
params: fn.params.map(p => ({ name: p.name, type: p.ty })),
|
|
1066
1401
|
returnType: fn.returnTy,
|
|
1067
1402
|
requires: fn.requires.map(transformExpr),
|