Skip to content
v0.5.18

LemmaScript — Dafny Backend Specification

Version: 0.5.18 Date: July 2026

This document covers what is unique to the Dafny backend. See Specification for the shared annotation language, translation rules, type mapping, and pipeline.


For each verified TS function foo.ts, there are two Dafny files:

FileWho writes itPurpose
foo.dfy.genlsc genGenerated from TS. Always regeneratable. Merge base.
foo.dfyLLM / UserStarts as copy of gen. Proof annotations added here. Source of truth.

The .dfy.gen extension prevents Dafny tooling from auto-verifying it.

The diff between gen and dfy must be additions only — the LLM may insert helper lemmas, ghost predicates, assert statements, and loop invariants, but may not modify generated lines.


Pure TS functions become Dafny function declarations (no wrapper, no namespace). requires and ensures are emitted directly. If the function has ensures, a companion lemma is generated as a proof target for the LLM:

function clamp(v: int, lo: int, hi: int): int
requires lo <= hi
{
if v < lo then lo
else if v > hi then hi
else v
}
lemma clamp_ensures(v: int, lo: int, hi: int)
requires lo <= hi
ensures clamp(v, lo, hi) >= lo
ensures clamp(v, lo, hi) <= hi
{
}

Non-pure functions become Dafny method declarations.


lsc regen --backend=dafny foo.ts:

  1. Read old foo.dfy.gen before overwriting
  2. Regenerate foo.dfy.gen
  3. If foo.dfy doesn’t exist → create from gen, verify, done
  4. If gen changed → three-way merge (git merge-file) using old gen as base
  5. Check additions-only invariant
  6. Verify merged foo.dfy
  7. On success, delete .dfy.base (gen is now the anchor)

On merge conflict, the original foo.dfy is restored and the merged result is saved as foo.dfy.merged for manual inspection.


The Dafny emitter auto-injects helper functions when needed. Each is emitted at most once, only when a construct that requires it appears (registry: PREAMBLE_CODE in dafny-emit.ts).

Core:

HelperWhenPurpose
Option<T>Map.get, optional typesdatatype Option<T> = None | Some(value: T)
SetToSeqfor (x of set), map/record iterationConvert set to sequence for iteration
SetFromSeqnew Set(arr)Build a deduplicated set from a sequence (set x | x in s)

Numeric:

HelperWhenPurpose
JSFloorDivMath.floor(a/b) (int args)JS-compatible floor division
FloorReal / CeilRealMath.floor(x) / Math.ceil(x) (real arg)real → int via .Floor
MathAbs / MathMin / MathMaxMath.abs/min/max(a, b)Scalar abs/min/max
MaxOfSeq / MinOfSeqMath.max(...s) / Math.min(...s)Aggregate over a sequence (requires |s| > 0)
Pow2 / BitAnd<< / >> / & on bigintBitwise ops as arithmetic
NatToString / IntToString`${n}` template literal (nat / signed int)Number-to-digit-string for interpolation (IntToString prefixes -)

Sequence:

HelperWhenPurpose
SeqIndexOfarr.indexOf(x)First-index search (-1 if absent)
SeqFindIndexarr.findIndex(f)Predicate first-index search
SeqFindLastarr.findLast(f)Predicate last-match search
SeqFilterSomefilterMap pattern (§3.7)Drop Nones and unwrap to seq<T>
SeqFlattenarr.flat()Flatten one level
SeqJoinarr.join(sep)Join into a string
SafeSlicearr.slice(lo, hi) under //@ safe-sliceBounds-clamping slice
Permperm(a, b) (spec-only)predicate Perm<T(==)>(a, b) { multiset(a) == multiset(b) }

String:

HelperWhenPurpose
StringIndexOfs.indexOf(sub), s.indexOf(sub, from), s.includes(sub)Recursive string search (also provides StringIndexOfFrom)
StringSplits.split(d)Axiomatic split (1 <= |res| <= |s| + 1)
StringTrims.trim() / s.trimEnd() / s.trimStart()Trim (also provides StringTrimRight / StringTrimLeft); strips the full ECMAScript whitespace set via IsJSWhitespace, not just ' '
StringToLower / StringToUppers.toLowerCase() / s.toUpperCase()Case folding

lsc check --backend=dafny foo.ts:

  1. Generate foo.dfy.gen + seed foo.dfy
  2. Check additions-only invariant
  3. Run dafny verify foo.dfy

Standard libraries are auto-detected: if foo.dfy contains import Std., the --standard-libraries flag is added.

The shared --time-limit=<seconds> flag (SPEC.md §7) maps to Dafny’s --verification-time-limit; --extra-flags=<string> is forwarded verbatim to dafny verify.