lemmascript 0.5.21 → 0.5.22

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.21",
3
+ "version": "0.5.22",
4
4
  "description": "A verification toolchain for TypeScript — generates Lean 4 or Dafny from annotated TS",
5
5
  "type": "module",
6
6
  "engines": {
@@ -2028,6 +2028,17 @@ export function extractModule(sourceFile) {
2028
2028
  // here), deduped by qualified name.
2029
2029
  _externs.clear();
2030
2030
  _externSigTypes.length = 0;
2031
+ // Match a `//@ <kw>` directive only as the first non-whitespace on a line,
2032
+ // so prose mentioning an annotation does not activate it.
2033
+ function hasLineDirective(text, kw) {
2034
+ return new RegExp(String.raw `^[ \t]*//@ ${kw}\b`, "m").test(text);
2035
+ }
2036
+ // Declaration-level directives must be attached as leading comments. Do
2037
+ // not scan a function's whole body: a statement-level `//@ skip` inside it
2038
+ // must not omit the enclosing function.
2039
+ function hasLeadingDirective(node, kw) {
2040
+ return node.getLeadingCommentRanges().some(r => hasLineDirective(r.getText(), kw));
2041
+ }
2031
2042
  // Share the module's ts-morph Project with parseTsType (scratch source file
2032
2043
  // for type-string parsing). Done before declare-type parsing so any
2033
2044
  // parseTsType call downstream uses the same Project.
@@ -2124,6 +2135,8 @@ export function extractModule(sourceFile) {
2124
2135
  const constants = [];
2125
2136
  for (const stmt of sourceFile.getStatements()) {
2126
2137
  if (Node.isVariableStatement(stmt)) {
2138
+ if (hasLeadingDirective(stmt, "skip"))
2139
+ continue;
2127
2140
  for (const decl of stmt.getDeclarationList().getDeclarations()) {
2128
2141
  if (stmt.getDeclarationList().getFlags() & 2 /* const */) {
2129
2142
  const init = decl.getInitializer();
@@ -2206,11 +2219,9 @@ export function extractModule(sourceFile) {
2206
2219
  // regex — but its callers should still be verifiable against an
2207
2220
  // uninterpreted predicate. Parallel to auto-extern for cross-file calls,
2208
2221
  // and emitted the same way (`function {:axiom} foo(...)` in Dafny).
2209
- // Match a `//@ <kw>` directive only as the first non-whitespace on a line, so
2210
- // a mention mid-line in prose or inside a block/JSDoc comment (e.g. "the
2211
- // `//@ extern` annotation", or ` * //@ extern`) doesn't falsely trigger it.
2212
- function hasLineDirective(text, kw) {
2213
- return new RegExp(String.raw `^[ \t]*//@ ${kw}\b`, "m").test(text);
2222
+ function hasSkip(f) {
2223
+ return hasLeadingDirective(f.parentStmt ?? f.node, "skip")
2224
+ || (!!f.parentStmt && hasLeadingDirective(f.node, "skip"));
2214
2225
  }
2215
2226
  function hasExtern(f) {
2216
2227
  if (hasLineDirective(f.node.getFullText(), "extern"))
@@ -2243,7 +2254,7 @@ export function extractModule(sourceFile) {
2243
2254
  return null;
2244
2255
  }
2245
2256
  for (const f of allFns) {
2246
- if (!hasExtern(f))
2257
+ if (hasSkip(f) || !hasExtern(f))
2247
2258
  continue;
2248
2259
  const qualified = externName(f) ?? f.name;
2249
2260
  const flat = qualified.replace(/\./g, "_");
@@ -2280,8 +2291,9 @@ export function extractModule(sourceFile) {
2280
2291
  }
2281
2292
  return false;
2282
2293
  }
2283
- const hasVerifyDirective = hasLineDirective(sourceFile.getFullText(), "verify");
2284
- const nonExternFns = allFns.filter(f => !hasExtern(f));
2294
+ const nonExternFns = allFns.filter(f => !hasSkip(f) && !hasExtern(f));
2295
+ const hasVerifiedClassMethod = sourceFile.getClasses().some(cls => !hasLeadingDirective(cls, "skip") && cls.getMethods().some(method => !hasLeadingDirective(method, "skip") && hasLineDirective(method.getFullText(), "verify")));
2296
+ const hasVerifyDirective = nonExternFns.some(hasVerify) || hasVerifiedClassMethod;
2285
2297
  const fnsToExtract = hasVerifyDirective ? nonExternFns.filter(hasVerify) : nonExternFns;
2286
2298
  // `//@ autohavoc` — enable the auto-havoc abstraction (see autohavoc.ts).
2287
2299
  // File-level: a directive at column 0 (top of file) enables it for every
@@ -2795,8 +2807,12 @@ export function extractModule(sourceFile) {
2795
2807
  // Extract classes with //@ verify methods
2796
2808
  const classes = [];
2797
2809
  for (const cls of sourceFile.getClasses()) {
2810
+ if (hasLeadingDirective(cls, "skip"))
2811
+ continue;
2798
2812
  const methods = [];
2799
2813
  for (const method of cls.getMethods()) {
2814
+ if (hasLeadingDirective(method, "skip"))
2815
+ continue;
2800
2816
  if (!method.getFullText().includes('//@ verify'))
2801
2817
  continue;
2802
2818
  methods.push(extractFunction(method));
@@ -2525,7 +2525,8 @@ export function transformModule(mod, specImport, moduleBaseOverride) {
2525
2525
  };
2526
2526
  });
2527
2527
  // Def file: Velvet methods
2528
- // Pure functions get a thin wrapper that calls Pure.fnName
2528
+ // Pure functions get a thin wrapper. Lean keeps pure definitions in the
2529
+ // `Pure` namespace; Dafny flattens that namespace, so its call is unqualified.
2529
2530
  // def-by-method functions also skip their method wrappers
2530
2531
  const pureDefNames = new Set([...pureDefs.map(d => d.name), ...defByMethods.map(d => d.name)]);
2531
2532
  const methods = mod.functions.map(fn => {
@@ -2539,7 +2540,7 @@ export function transformModule(mod, specImport, moduleBaseOverride) {
2539
2540
  }
2540
2541
  _forofCounters.clear();
2541
2542
  let body = pureDefNames.has(fn.name)
2542
- ? [{ kind: "return", value: { kind: "app", fn: `Pure.${fn.name}`, args: fn.params.map(p => ({ kind: "var", name: p.name })) } }]
2543
+ ? [{ kind: "return", value: { kind: "app", fn: _opts.backend === "lean" ? `Pure.${fn.name}` : fn.name, args: fn.params.map(p => ({ kind: "var", name: p.name })) } }]
2543
2544
  : promoteAssignedLets(transformStmts(fn.body, mod.typeDecls));
2544
2545
  // Lean-only method-body rewrites (Velvet can't WP-synthesize monadic matches
2545
2546
  // and forbids `return` in loops):