lemmascript 0.3.1 → 0.3.3
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 -0
- package/package.json +3 -2
- package/tools/dist/dafny-emit.js +70 -12
- package/tools/dist/extract.js +182 -3
- package/tools/dist/lean-emit.js +2 -0
- package/tools/dist/resolve.js +128 -37
- package/tools/dist/transform.js +307 -58
package/tools/dist/resolve.js
CHANGED
|
@@ -108,7 +108,9 @@ function wrapSome(value, optionalTy) {
|
|
|
108
108
|
}
|
|
109
109
|
/** Detect `v !== undefined` or `undefined !== v` where v: optional<T>.
|
|
110
110
|
* Handles simple variables, field access chains, and arbitrary expressions.
|
|
111
|
-
* When `fieldExpr` is returned
|
|
111
|
+
* When `fieldExpr` is returned for a simple field chain (obj.field), callers
|
|
112
|
+
* can narrow via `narrowedFields` context. For complex expressions (calls, etc.),
|
|
113
|
+
* callers should fall back to `substituteRawExpr`.
|
|
112
114
|
*
|
|
113
115
|
* Does NOT recurse into `&&` — callers that need to detect optional checks
|
|
114
116
|
* inside `&&` conditions should check `cond.left` explicitly. */
|
|
@@ -141,6 +143,19 @@ function detectOptionalCheck(cond, ctx) {
|
|
|
141
143
|
}
|
|
142
144
|
return null;
|
|
143
145
|
}
|
|
146
|
+
/** Collect all optional narrowings from an early-return condition.
|
|
147
|
+
* Handles single checks (x === undefined) and compound || chains
|
|
148
|
+
* (x === undefined || y === undefined). */
|
|
149
|
+
function collectEarlyReturnNarrowings(cond, ctx) {
|
|
150
|
+
if (cond.kind === "binop" && cond.op === "||") {
|
|
151
|
+
return [...collectEarlyReturnNarrowings(cond.left, ctx), ...collectEarlyReturnNarrowings(cond.right, ctx)];
|
|
152
|
+
}
|
|
153
|
+
const narrowed = detectOptionalCheck(cond, ctx);
|
|
154
|
+
if (narrowed && !narrowed.inThen && !narrowed.fieldExpr) {
|
|
155
|
+
return [{ varName: narrowed.varName, innerTy: narrowed.innerTy }];
|
|
156
|
+
}
|
|
157
|
+
return [];
|
|
158
|
+
}
|
|
144
159
|
/** TS reference types that become value types in Dafny/Lean — const bindings need mutable var. */
|
|
145
160
|
function isRefMutableInTS(ty) {
|
|
146
161
|
return ty.kind === "array" || ty.kind === "map" || ty.kind === "set";
|
|
@@ -293,6 +308,8 @@ function inferMethodReturnTy(fn, args, ctx) {
|
|
|
293
308
|
else if (objTy.kind === "array") {
|
|
294
309
|
if (fn.field === "includes")
|
|
295
310
|
return { kind: "bool" };
|
|
311
|
+
if (fn.field === "indexOf")
|
|
312
|
+
return { kind: "int" };
|
|
296
313
|
if (fn.field === "shift")
|
|
297
314
|
return objTy.elem;
|
|
298
315
|
if (fn.field === "push" || fn.field === "concat")
|
|
@@ -387,15 +404,28 @@ function resolveExpr(e, ctx) {
|
|
|
387
404
|
fn.obj.ty.elem.kind === "user") {
|
|
388
405
|
argCtx = { ...ctx, returnTy: fn.obj.ty.elem };
|
|
389
406
|
}
|
|
390
|
-
|
|
391
|
-
|
|
407
|
+
// Propagate parameter types to arguments for record literal resolution
|
|
408
|
+
// (enables inline discriminated union construction in function arguments)
|
|
409
|
+
const paramTypes = fn.kind === "var" && ctx.fnParams.has(fn.name) ? ctx.fnParams.get(fn.name) : null;
|
|
410
|
+
const args = coerceCallArgs(rawArgs.map((a, i) => {
|
|
411
|
+
let aCtx = argCtx;
|
|
412
|
+
if (paramTypes && i < paramTypes.length && paramTypes[i].kind === "user") {
|
|
413
|
+
aCtx = { ...aCtx, returnTy: paramTypes[i] };
|
|
414
|
+
}
|
|
415
|
+
return resolveExpr(a, aCtx);
|
|
416
|
+
}), fn, ctx);
|
|
417
|
+
let ty = inferMethodReturnTy(fn, args, ctx);
|
|
418
|
+
// For same-file function calls, use the known return type
|
|
419
|
+
if (ty.kind === "unknown" && fn.kind === "var" && ctx.fnReturns.has(fn.name)) {
|
|
420
|
+
ty = ctx.fnReturns.get(fn.name);
|
|
421
|
+
}
|
|
392
422
|
return { kind: "call", fn, args, ty, callKind: classifyCall(e.fn, ctx) };
|
|
393
423
|
}
|
|
394
424
|
case "index": {
|
|
395
425
|
const obj = resolveExpr(e.obj, ctx);
|
|
396
426
|
const idx = resolveExpr(e.idx, ctx);
|
|
397
427
|
const idxTy = obj.ty.kind === "array" ? obj.ty.elem
|
|
398
|
-
: obj.ty.kind === "map" ? obj.ty.value
|
|
428
|
+
: obj.ty.kind === "map" ? { kind: "optional", inner: obj.ty.value }
|
|
399
429
|
: { kind: "unknown" };
|
|
400
430
|
return { kind: "index", obj, idx, ty: idxTy };
|
|
401
431
|
}
|
|
@@ -403,16 +433,24 @@ function resolveExpr(e, ctx) {
|
|
|
403
433
|
const obj = resolveExpr(e.obj, ctx);
|
|
404
434
|
let isDiscriminant = false;
|
|
405
435
|
let ty = { kind: "unknown" };
|
|
406
|
-
|
|
436
|
+
// Check narrowed field context (from conditional optional checks on field chains)
|
|
437
|
+
if (obj.kind === "var" && ctx.narrowedFields.length > 0) {
|
|
438
|
+
const nf = ctx.narrowedFields.find(n => n.objName === obj.name && n.fieldName === e.field);
|
|
439
|
+
if (nf)
|
|
440
|
+
ty = nf.narrowedTy;
|
|
441
|
+
}
|
|
442
|
+
if (ty.kind === "unknown" && e.field === "length" && (obj.ty.kind === "array" || obj.ty.kind === "string")) {
|
|
407
443
|
ty = { kind: "nat" };
|
|
408
444
|
}
|
|
409
|
-
else if (e.field === "size" && (obj.ty.kind === "map" || obj.ty.kind === "set")) {
|
|
445
|
+
else if (ty.kind === "unknown" && e.field === "size" && (obj.ty.kind === "map" || obj.ty.kind === "set")) {
|
|
410
446
|
ty = { kind: "nat" };
|
|
411
447
|
}
|
|
412
|
-
else if (obj.ty.kind === "user") {
|
|
413
|
-
|
|
448
|
+
else if (ty.kind === "unknown" && obj.ty.kind === "user") {
|
|
449
|
+
// Strip generic args for type lookup: "Result<Model, Err>" → "Result"
|
|
450
|
+
const baseTyName = obj.ty.name.includes("<") ? obj.ty.name.slice(0, obj.ty.name.indexOf("<")) : obj.ty.name;
|
|
451
|
+
if (getDiscriminant(ctx, baseTyName) === e.field)
|
|
414
452
|
isDiscriminant = true;
|
|
415
|
-
const decl = findDecl(ctx,
|
|
453
|
+
const decl = findDecl(ctx, baseTyName);
|
|
416
454
|
if (decl?.kind === "record") {
|
|
417
455
|
const f = decl.fields?.find(f => f.name === e.field);
|
|
418
456
|
if (f)
|
|
@@ -440,11 +478,20 @@ function resolveExpr(e, ctx) {
|
|
|
440
478
|
// Clear returnTy for field values — it applies to THIS record, not nested ones
|
|
441
479
|
const fieldCtx = recordTy ? { ...ctx, returnTy: { kind: "unknown" } } : ctx;
|
|
442
480
|
const fields = e.fields.map(f => {
|
|
443
|
-
let value = resolveExpr(f.value, fieldCtx);
|
|
444
481
|
const fieldDecl = decl?.fields?.find(df => df.name === f.name);
|
|
482
|
+
// Propagate declared field type into context so nested records resolve
|
|
483
|
+
// their union variant correctly (e.g., { kind: 'Idle' } → EffectMode.Idle)
|
|
484
|
+
const valueCtx = (fieldDecl?.type?.kind === "user")
|
|
485
|
+
? { ...fieldCtx, returnTy: fieldDecl.type }
|
|
486
|
+
: fieldCtx;
|
|
487
|
+
let value = resolveExpr(f.value, valueCtx);
|
|
445
488
|
if (fieldDecl) {
|
|
446
489
|
const declTy = fieldDecl.type;
|
|
447
490
|
value = coerceStr(value, declTy);
|
|
491
|
+
// Empty {} for map-typed fields → empty map (arrayLiteral with map type → emptyMap in transform)
|
|
492
|
+
if (value.kind === "record" && value.fields.length === 0 && !value.spread && declTy.kind === "map") {
|
|
493
|
+
value = { kind: "arrayLiteral", elems: [], ty: declTy };
|
|
494
|
+
}
|
|
448
495
|
// Coerce non-optional to optional: wrap in Some (only when value type is concrete)
|
|
449
496
|
if (declTy.kind === "optional" && value.ty.kind !== "optional" && value.ty.kind !== "void" && value.ty.kind !== "unknown") {
|
|
450
497
|
value = wrapSome(value, declTy);
|
|
@@ -493,7 +540,6 @@ function resolveExpr(e, ctx) {
|
|
|
493
540
|
case "conditional": {
|
|
494
541
|
const cond = resolveExpr(e.cond, ctx);
|
|
495
542
|
let narrowedVar;
|
|
496
|
-
let narrowedExprResolved;
|
|
497
543
|
let thenCtx = ctx;
|
|
498
544
|
let rawThen = e.then;
|
|
499
545
|
// Phase 1: Optional truthiness — cond itself is optional (e.g. opt ? X : Y)
|
|
@@ -509,23 +555,56 @@ function resolveExpr(e, ctx) {
|
|
|
509
555
|
thenCtx = withEnv(ctx, extend(ctx.env, narrowedVar, innerTy));
|
|
510
556
|
}
|
|
511
557
|
}
|
|
512
|
-
// Phase 2: Explicit check — v !== undefined
|
|
513
|
-
//
|
|
558
|
+
// Phase 2/3: Explicit check — v !== undefined, or && with optional check.
|
|
559
|
+
// Resolve only narrows the type environment; transform handles all structural
|
|
560
|
+
// narrowing (match generation, variable binding, && splitting).
|
|
561
|
+
let narrowedExprResolved;
|
|
562
|
+
let elseCtx = ctx;
|
|
514
563
|
if (!narrowedVar) {
|
|
515
564
|
const narrowed = detectOptionalCheck(e.cond, ctx)
|
|
516
|
-
// Phase 3: && with optional check — (v !== undefined && ...) ? ... : ...
|
|
517
565
|
?? (e.cond.kind === "binop" && e.cond.op === "&&" ? detectOptionalCheck(e.cond.left, ctx) : null);
|
|
518
566
|
if (narrowed && narrowed.inThen) {
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
567
|
+
if (!narrowed.fieldExpr) {
|
|
568
|
+
// Simple var: extend env only — transform will detect and generate match
|
|
569
|
+
thenCtx = withEnv(thenCtx, extend(thenCtx.env, narrowed.varName, narrowed.innerTy));
|
|
570
|
+
}
|
|
571
|
+
else if (narrowed.fieldExpr.kind === "field" && narrowed.fieldExpr.obj.kind === "var") {
|
|
572
|
+
// Simple field chain (obj.field): narrow via field context — no substitution
|
|
573
|
+
thenCtx = {
|
|
574
|
+
...thenCtx,
|
|
575
|
+
narrowedFields: [...thenCtx.narrowedFields, {
|
|
576
|
+
objName: narrowed.fieldExpr.obj.name,
|
|
577
|
+
fieldName: narrowed.fieldExpr.field,
|
|
578
|
+
narrowedTy: narrowed.innerTy,
|
|
579
|
+
}],
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
else {
|
|
583
|
+
// Complex expression (call result, deep chain, etc.): transform can't detect these,
|
|
584
|
+
// so keep old behavior — substitute + narrowedVar + narrowedExpr
|
|
585
|
+
narrowedVar = narrowed.varName;
|
|
522
586
|
narrowedExprResolved = narrowed.narrowedExpr ?? resolveExpr(narrowed.fieldExpr, ctx);
|
|
523
587
|
rawThen = substituteRawExpr(e.then, narrowed.fieldExpr, { kind: "var", name: narrowed.varName });
|
|
588
|
+
thenCtx = withEnv(thenCtx, extend(thenCtx.env, narrowed.varName, narrowed.innerTy));
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
else if (narrowed && !narrowed.inThen && !narrowed.fieldExpr) {
|
|
592
|
+
// v === undefined: narrow v in the else branch
|
|
593
|
+
elseCtx = withEnv(elseCtx, extend(elseCtx.env, narrowed.varName, narrowed.innerTy));
|
|
594
|
+
}
|
|
595
|
+
// Compound || with === undefined: narrow all checked vars in else branch
|
|
596
|
+
// e.g. if (a === undefined || b === undefined) then X else Y → narrow a,b in Y
|
|
597
|
+
// TODO: resolve-time narrowing works but transform doesn't emit match unwrap
|
|
598
|
+
// for || conditions yet — decompose || into nested matches in transform.
|
|
599
|
+
// Workaround: split || into separate if guards in user code.
|
|
600
|
+
if (!narrowed && e.cond.kind === "binop" && e.cond.op === "||") {
|
|
601
|
+
for (const n of collectEarlyReturnNarrowings(e.cond, ctx)) {
|
|
602
|
+
elseCtx = withEnv(elseCtx, extend(elseCtx.env, n.varName, n.innerTy));
|
|
524
603
|
}
|
|
525
604
|
}
|
|
526
605
|
}
|
|
527
606
|
let then_ = resolveExpr(rawThen, thenCtx);
|
|
528
|
-
let else_ = resolveExpr(e.else,
|
|
607
|
+
let else_ = resolveExpr(e.else, elseCtx);
|
|
529
608
|
then_ = coerceStr(then_, else_.ty);
|
|
530
609
|
else_ = coerceStr(else_, then_.ty);
|
|
531
610
|
let ty = then_.ty.kind !== "unknown" ? then_.ty : else_.ty;
|
|
@@ -537,8 +616,7 @@ function resolveExpr(e, ctx) {
|
|
|
537
616
|
ty = { kind: "optional", inner: then_.ty };
|
|
538
617
|
}
|
|
539
618
|
// When narrowedExpr is set AND a branch is void, the match produces Optional
|
|
540
|
-
|
|
541
|
-
if (narrowedExprResolved && hasVoidBranch && ty.kind !== "optional") {
|
|
619
|
+
if (narrowedExprResolved && (then_.ty.kind === "void" || else_.ty.kind === "void") && ty.kind !== "optional") {
|
|
542
620
|
ty = { kind: "optional", inner: ty };
|
|
543
621
|
}
|
|
544
622
|
return { kind: "conditional", cond, then: then_, else: else_, ty, narrowedVar, narrowedExpr: narrowedExprResolved };
|
|
@@ -579,12 +657,13 @@ function resolveBlock(stmts, ctx) {
|
|
|
579
657
|
result.push(typed);
|
|
580
658
|
env = nextEnv;
|
|
581
659
|
// Flow narrowing: if (x === undefined) { return } narrows x for rest of block.
|
|
660
|
+
// Also handles compound: if (x === undefined || y === undefined) { return }
|
|
582
661
|
// Field chains are excluded — resolve can't substitute in statement lists;
|
|
583
662
|
// transform's emitOptionalMatch handles field chains in statement contexts.
|
|
584
663
|
if (s.kind === "if" && s.then.length > 0 && s.then[s.then.length - 1].kind === "return" && s.else.length === 0) {
|
|
585
|
-
const
|
|
586
|
-
|
|
587
|
-
env = extend(env,
|
|
664
|
+
const narrowings = collectEarlyReturnNarrowings(s.cond, withEnv(ctx, env));
|
|
665
|
+
for (const n of narrowings) {
|
|
666
|
+
env = extend(env, n.varName, n.innerTy);
|
|
588
667
|
}
|
|
589
668
|
}
|
|
590
669
|
}
|
|
@@ -593,8 +672,13 @@ function resolveBlock(stmts, ctx) {
|
|
|
593
672
|
function resolveStmt(s, ctx) {
|
|
594
673
|
switch (s.kind) {
|
|
595
674
|
case "let": {
|
|
596
|
-
const
|
|
597
|
-
|
|
675
|
+
const declTy = resolveTsType(s.tsType, ctx.overrides, s.name);
|
|
676
|
+
// Propagate declared type as returnTy so nested record expressions
|
|
677
|
+
// resolve union variants correctly (e.g., EffectState → mode: EffectMode → { kind: 'Idle' })
|
|
678
|
+
const initCtx = declTy.kind === "user" ? { ...ctx, returnTy: declTy } : ctx;
|
|
679
|
+
const init = coerceStr(resolveExpr(s.init, initCtx), declTy);
|
|
680
|
+
// Map indexing: TS says T, but access can fail → use Optional<T> from init
|
|
681
|
+
const ty = (declTy.kind !== "optional" && init.ty.kind === "optional") ? init.ty : declTy;
|
|
598
682
|
// const collections are mutable in value-semantics world (TS mutates in place, Dafny/Lean reassign)
|
|
599
683
|
const mutable = s.mutable || isRefMutableInTS(ty);
|
|
600
684
|
return [{ kind: "let", name: s.name, ty, mutable, init }, extend(ctx.env, s.name, ty)];
|
|
@@ -825,6 +909,8 @@ function collectCallsStmts(stmts, fns, out) {
|
|
|
825
909
|
}
|
|
826
910
|
function computePureFns(functions) {
|
|
827
911
|
const allFnNames = new Set(functions.map(fn => fn.name));
|
|
912
|
+
// //@ pure functions are always considered pure — never taint callers
|
|
913
|
+
const forcePure = new Set(functions.filter(fn => fn.pure).map(fn => fn.name));
|
|
828
914
|
// Build call graph: fn → set of same-file functions it calls
|
|
829
915
|
const callGraph = new Map();
|
|
830
916
|
for (const fn of functions) {
|
|
@@ -832,8 +918,8 @@ function computePureFns(functions) {
|
|
|
832
918
|
collectCallsStmts(fn.body, allFnNames, calls);
|
|
833
919
|
callGraph.set(fn.name, calls);
|
|
834
920
|
}
|
|
835
|
-
// Seed: syntactically non-pure functions
|
|
836
|
-
const nonPure = new Set(functions.filter(fn => !isSyntacticallyPure(fn.body)).map(fn => fn.name));
|
|
921
|
+
// Seed: syntactically non-pure functions (skip //@ pure)
|
|
922
|
+
const nonPure = new Set(functions.filter(fn => !forcePure.has(fn.name) && !isSyntacticallyPure(fn.body)).map(fn => fn.name));
|
|
837
923
|
// Build reverse graph: fn → set of functions that call it
|
|
838
924
|
const callers = new Map();
|
|
839
925
|
for (const name of allFnNames)
|
|
@@ -842,12 +928,12 @@ function computePureFns(functions) {
|
|
|
842
928
|
for (const callee of callees)
|
|
843
929
|
callers.get(callee).add(caller);
|
|
844
930
|
}
|
|
845
|
-
// Propagate impurity through reverse call graph
|
|
931
|
+
// Propagate impurity through reverse call graph (skip //@ pure)
|
|
846
932
|
const worklist = [...nonPure];
|
|
847
933
|
while (worklist.length > 0) {
|
|
848
934
|
const fn = worklist.pop();
|
|
849
935
|
for (const caller of callers.get(fn) ?? []) {
|
|
850
|
-
if (!nonPure.has(caller)) {
|
|
936
|
+
if (!nonPure.has(caller) && !forcePure.has(caller)) {
|
|
851
937
|
nonPure.add(caller);
|
|
852
938
|
worklist.push(caller);
|
|
853
939
|
}
|
|
@@ -880,7 +966,7 @@ function containsReturn(stmts) {
|
|
|
880
966
|
return false;
|
|
881
967
|
}
|
|
882
968
|
// ── Resolve function / module ────────────────────────────────
|
|
883
|
-
function resolveFunction(fn, typeDecls, pureFns, fnParams = new Map(), opts) {
|
|
969
|
+
function resolveFunction(fn, typeDecls, pureFns, fnParams = new Map(), fnReturns = new Map(), opts) {
|
|
884
970
|
const overrides = new Map(fn.typeAnnotations.map(a => [a.name, a.type]));
|
|
885
971
|
const params = fn.params.map(p => ({ name: p.name, ty: resolveTsType(p.tsType, overrides, p.name) }));
|
|
886
972
|
const returnTy = resolveTsType(fn.returnType, overrides, "\\result");
|
|
@@ -889,7 +975,7 @@ function resolveFunction(fn, typeDecls, pureFns, fnParams = new Map(), opts) {
|
|
|
889
975
|
env = extend(env, opts.thisBinding.name, opts.thisBinding.ty);
|
|
890
976
|
for (const p of params)
|
|
891
977
|
env = extend(env, p.name, p.ty);
|
|
892
|
-
const baseCtx = { env, typeDecls, overrides, allowResult: false, returnTy, pureFns, fnParams, inSpec: false, inLambda: false };
|
|
978
|
+
const baseCtx = { env, typeDecls, overrides, allowResult: false, returnTy, pureFns, fnParams, fnReturns, inSpec: false, inLambda: false, narrowedFields: [] };
|
|
893
979
|
const requiresCtx = { ...baseCtx, inSpec: true };
|
|
894
980
|
const ensuresCtx = { ...baseCtx, allowResult: true, inSpec: true };
|
|
895
981
|
// Apply type parameter constraints from //@ type T (==) annotations
|
|
@@ -901,17 +987,19 @@ function resolveFunction(fn, typeDecls, pureFns, fnParams = new Map(), opts) {
|
|
|
901
987
|
name: fn.name, typeParams, params, returnTy,
|
|
902
988
|
requires: resolveSpecs(fn.requires, requiresCtx),
|
|
903
989
|
ensures: resolveSpecs(fn.ensures, ensuresCtx),
|
|
990
|
+
decreases: fn.decreases ? resolveSpec(fn.decreases, requiresCtx) : null,
|
|
904
991
|
isPure: opts?.forcePure !== undefined ? opts.forcePure : pureFns.has(fn.name),
|
|
992
|
+
forcePure: fn.pure,
|
|
905
993
|
body: resolveBlock(fn.body, baseCtx),
|
|
906
994
|
};
|
|
907
995
|
}
|
|
908
|
-
function resolveClass(cls, typeDecls, pureFns, fnParams = new Map()) {
|
|
996
|
+
function resolveClass(cls, typeDecls, pureFns, fnParams = new Map(), fnReturns = new Map()) {
|
|
909
997
|
const fields = cls.fields.map(f => ({ name: f.name, ty: parseTsType(f.tsType) }));
|
|
910
998
|
// Create a synthetic record type for 'this' so field access resolves
|
|
911
999
|
const thisType = { kind: "user", name: cls.name };
|
|
912
1000
|
const thisDecl = { name: cls.name, kind: "record", fields: cls.fields.map(f => ({ name: f.name, tsType: f.tsType, type: parseTsType(f.tsType) })) };
|
|
913
1001
|
const allTypeDecls = [...typeDecls, thisDecl];
|
|
914
|
-
const methods = cls.methods.map(fn => resolveFunction(fn, allTypeDecls, pureFns, fnParams, {
|
|
1002
|
+
const methods = cls.methods.map(fn => resolveFunction(fn, allTypeDecls, pureFns, fnParams, fnReturns, {
|
|
915
1003
|
thisBinding: { name: "this", ty: thisType },
|
|
916
1004
|
forcePure: false, // class methods are never pure (they access this)
|
|
917
1005
|
}));
|
|
@@ -933,15 +1021,18 @@ function precomputeFieldTypes(typeDecls) {
|
|
|
933
1021
|
}
|
|
934
1022
|
}
|
|
935
1023
|
export function resolveModule(raw) {
|
|
1024
|
+
_synVarCounter = 0;
|
|
936
1025
|
precomputeFieldTypes(raw.typeDecls);
|
|
937
1026
|
const pureFns = computePureFns(raw.functions);
|
|
938
|
-
// Pre-compute function parameter
|
|
1027
|
+
// Pre-compute function parameter and return types
|
|
939
1028
|
const fnParams = new Map();
|
|
1029
|
+
const fnReturns = new Map();
|
|
940
1030
|
for (const fn of raw.functions) {
|
|
941
1031
|
const overrides = new Map(fn.typeAnnotations.map(a => [a.name, a.type]));
|
|
942
1032
|
fnParams.set(fn.name, fn.params.map(p => resolveTsType(p.tsType, overrides, p.name)));
|
|
1033
|
+
fnReturns.set(fn.name, resolveTsType(fn.returnType, overrides, "\\result"));
|
|
943
1034
|
}
|
|
944
|
-
const emptyCtx = { env: null, typeDecls: raw.typeDecls, overrides: new Map(), allowResult: false, returnTy: { kind: "int" }, pureFns, fnParams, inSpec: false, inLambda: false };
|
|
1035
|
+
const emptyCtx = { env: null, typeDecls: raw.typeDecls, overrides: new Map(), allowResult: false, returnTy: { kind: "int" }, pureFns, fnParams, fnReturns, inSpec: false, inLambda: false, narrowedFields: [] };
|
|
945
1036
|
const constants = (raw.constants ?? []).map(c => ({
|
|
946
1037
|
name: c.name,
|
|
947
1038
|
ty: parseTsType(c.tsType),
|
|
@@ -951,7 +1042,7 @@ export function resolveModule(raw) {
|
|
|
951
1042
|
file: raw.file,
|
|
952
1043
|
typeDecls: raw.typeDecls,
|
|
953
1044
|
constants,
|
|
954
|
-
functions: raw.functions.map(fn => resolveFunction(fn, raw.typeDecls, pureFns, fnParams)),
|
|
955
|
-
classes: (raw.classes ?? []).map(cls => resolveClass(cls, raw.typeDecls, pureFns, fnParams)),
|
|
1045
|
+
functions: raw.functions.map(fn => resolveFunction(fn, raw.typeDecls, pureFns, fnParams, fnReturns)),
|
|
1046
|
+
classes: (raw.classes ?? []).map(cls => resolveClass(cls, raw.typeDecls, pureFns, fnParams, fnReturns)),
|
|
956
1047
|
};
|
|
957
1048
|
}
|