lemmascript 0.5.17 → 0.5.18

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lemmascript",
3
- "version": "0.5.17",
3
+ "version": "0.5.18",
4
4
  "description": "A verification toolchain for TypeScript — generates Lean 4 or Dafny from annotated TS",
5
5
  "type": "module",
6
6
  "engines": {
@@ -34,6 +34,7 @@
34
34
  * (early-return, let-cond) run in `walkStmts` so they can consume the rest
35
35
  * of the block.
36
36
  */
37
+ import { isTerminatorKind } from "./typedir.js";
37
38
  import { freshName } from "./names.js";
38
39
  // ── Optional-check detection ────────────────────────────────
39
40
  /** Counter for naming optChain binders. Reset per module. */
@@ -918,8 +919,7 @@ function parseNegativeDiscriminantCond(cond) {
918
919
  function isTerminating(stmts) {
919
920
  if (stmts.length === 0)
920
921
  return false;
921
- const last = stmts[stmts.length - 1];
922
- return last.kind === "return" || last.kind === "throw" || last.kind === "break" || last.kind === "continue";
922
+ return isTerminatorKind(stmts[stmts.length - 1].kind);
923
923
  }
924
924
  /** Rule (list-level): consecutive `if (x.kind === "v") ...` chain → tagMatch.
925
925
  * Walks consecutive top-level ifs on the same discriminator var; the first
@@ -4,7 +4,7 @@
4
4
  * Uses linked environments (Scheme-style) for lexical scoping.
5
5
  * No mutation — each let extends the chain, lookup walks it.
6
6
  */
7
- import { isBigInt, tyEqual } from "./typedir.js";
7
+ import { isBigInt, tyEqual, isTerminatorKind } from "./typedir.js";
8
8
  import { parseTsType, tyToCanonical } from "./types.js";
9
9
  import { parseExpr } from "./specparser.js";
10
10
  import { freshName } from "./names.js";
@@ -1263,9 +1263,12 @@ function resolveBlock(stmts, ctx) {
1263
1263
  env = nextEnv;
1264
1264
  // Flow narrowing: if (x === undefined) { return } narrows x for rest of block.
1265
1265
  // Also handles compound: if (x === undefined || y === undefined) { return }
1266
+ // Any terminator counts (return/throw/break/continue — same set as narrow's
1267
+ // isTerminating): each exits the current block, so the rest of the block
1268
+ // only runs when the guard was false.
1266
1269
  // Field chains are excluded — resolve can't substitute in statement lists;
1267
1270
  // transform's emitOptionalMatch handles field chains in statement contexts.
1268
- if (s.kind === "if" && s.then.length > 0 && s.then[s.then.length - 1].kind === "return" && s.else.length === 0) {
1271
+ if (s.kind === "if" && s.then.length > 0 && isTerminatorKind(s.then[s.then.length - 1].kind) && s.else.length === 0) {
1269
1272
  const narrowings = collectEarlyReturnNarrowings(s.cond, withEnv(ctx, env));
1270
1273
  for (const n of narrowings) {
1271
1274
  env = extend(env, n.varName, n.innerTy);
@@ -44,3 +44,9 @@ export function tyEqual(a, b) {
44
44
  case "unknown": return true; // no payload
45
45
  }
46
46
  }
47
+ /** Statement kinds that unconditionally leave the enclosing block. Shared by
48
+ * resolve (block-tail narrowing) and narrow (isTerminating); works on raw and
49
+ * typed IR alike since both use these kind strings. */
50
+ export function isTerminatorKind(kind) {
51
+ return kind === "return" || kind === "throw" || kind === "break" || kind === "continue";
52
+ }