Lean is the proof-export backend for Aver.
Use it when you want:
- Lean 4 artifacts for pure Aver code
- proof obligations for classified effectful code via Oracle lifting
- executable proof obligations from colocated
verify - universal theorems for supported
verify lawshapes, with explicit sampled/domain fallback for the rest - a path from Aver code to formal verification
This is not a second execution runtime for effectful programs.
aver proof examples/formal/law_auto.av --verify-mode auto -o /tmp/law-auto-lean
cd /tmp/law-auto-lean && lake buildRequires a local Lean 4 toolchain (lean + lake). Aver generates the project, but does not bundle Lean itself.
Generates a Lean 4 project:
out/
lakefile.lean
lean-toolchain
<Project>.lean
- exports pure core logic: types, pure functions, and decisions
- emits lifted pure forms for classified effectful functions used by Oracle laws
- skips unclassified effectful functions and
main - turns colocated
verify/verify lawintent into Lean proof artifacts
Oracle-lifted laws make effects explicit as theorem parameters. For example,
given rnd: Random.int = [fairDie] becomes a proof-side oracle argument with
the derived Random.int oracle signature. See oracle.md for the
classified effect set, stub signatures, Oracle law syntax, and trace assertions.
verify blocks become Lean proof obligations:
- default (
--verify-mode auto):example : <lhs> = <rhs> := by native_decide - fallback (
--verify-mode sorry):example : <lhs> = <rhs> := by sorry - theorem stubs (
--verify-mode theorem-skeleton): namedtheorem ... := by sorry
verify ... law ... always emits expanded sample theorems from given domains:
theorem ..._sample_n := by native_decide
When Aver can auto-prove the universal law shape, it also emits:
theorem <fn>_law_<name> : ∀ ..., lhs = rhs := by ...
when clauses translate to extra theorem premises in Lean. Generic auto-proof
strategies still run on those guarded laws, but unmatched shapes fall back to
domain-guarded theorems over the explicit given cases. Parser/render
roundtrip laws such as parse(render(x)) = x currently live in that fallback
bucket unless some other generic shape discharges them first.
When law <ident> names an existing pure function and the law body compares foo(args) against fooSpec(args), Aver treats that as a canonical spec reference:
- the generated theorem/comment uses the canonical
<fn>_eq_<spec>naming aver contextalso recordsfibSpecas a spec forfib- in
--verify-mode auto, Aver emits the universal theorem only when the law shape is supported by a generic auto-proof strategy; otherwise it omits that theorem and leaves a comment instead
Example:
fn fibSpec(n: Int) -> Int
match n
0 -> 0
1 -> 1
_ -> fibSpec(n - 1) + fibSpec(n - 2)
verify fib law fibSpec
given n: Int = [0, 1, 2, 3, 4, 5]
fib(n) => fibSpec(n)
This is the intended proof style in Aver:
- the author writes a simple pure spec function
- the author writes
verify impl law implSpec - the proof backend tries to connect implementation and spec
The goal is to avoid making the surface language proof-engineer-first.
Invariants still exist as a proof concept, especially for optimized implementations such as tail-recursive helpers, parsers with state, or accumulator-heavy code. But Aver tries to push those invariants down into the proof backend whenever possible, instead of making users write them first.
In short:
- user-facing Aver should prefer explicit specs
- the proof backend should absorb invariants where it can
- dropping to explicit invariant reasoning should be the exception, not the default workflow
If Aver cannot auto-prove the universal law shape in --verify-mode auto, it omits that theorem and leaves a comment instead of emitting a fake sorry proof.
Conservative auto-proofs currently cover:
- reflexive law shape (
lhsandrhssyntactically identical) →rfl - commutative law on simple
Intbinary wrappers (a + b,a * b) - associative law on same wrapper shape (
f(f(a,b),c) = f(a,f(b,c))) - identity law on same wrapper shape (
f(a,0)=a,f(0,a)=a,f(a,1)=a,f(1,a)=a) - direct structural induction on recursive sum types whose recursive fields are bare self-references, not container-wrapped occurrences
- canonical implementation-vs-spec laws when the backend can discharge them structurally
- canonical implementation-vs-spec laws that become definitionally equal after conservative
simp-style identity cleanup (for examplex * xvsx * x + 0) - canonical implementation-vs-spec laws for second-order linear
Intrecurrences with a pair-state tail-recursive worker - canonical implementation-vs-spec laws over linear
Intarithmetic, proved viachange ...andomega
The generated Lean prelude also includes one-character separator lemmas such as
AverString.split over String.join(_, sep) ++ sep for separator-free parts.
Those helper lemmas support exported code, but they are not themselves a claim
that delimiter-based parser/render laws are universally auto-proved.
Recommended mode:
aver proof my_module.av --verify-mode auto -o out/That combination means:
- regular
verifycases become executable Lean checks vianative_decide - supported
verify lawshapes get real universal proofs - unsupported
verify lawshapes stay as sample theorems and checked-domain theorems, with the universal theorem omitted - recursive pure code inside the supported proof subset is emitted as total Lean defs
- unsupported recursive pure functions are called out explicitly and emitted with
partialfallback
The current proof export supports:
- single-function
Intcountdown on anIntparameter (n -> n - 1) - single-function second-order affine
Intrecurrences withn < 0guard,0/1case split, and a matching pair-state tail worker, emitted via a privateNathelper - single-function structural recursion on any
List<_>parameter - single-function
String + posrecursion on(String, Int)signatures - mutual recursion SCC with first-parameter
Intcountdown - mutual recursion SCC with ranked
String + posprogress - mutual recursion SCC with ranked structural descent over recursive parameters
These examples are currently smoke-tested end to end with
aver proof --verify-mode auto plus lake build:
examples/formal/law_auto.avexamples/data/fibonacci.avexamples/data/quicksort.avexamples/data/rle.avexamples/data/json.avexamples/core/grok_s_language.av
Other modules have unit coverage for proof-subset classification and generated Lean snippets, but are not currently listed here as end-to-end smoke cases.
Lean codegen does not silently mask unresolved compiler internals:
Expr::Resolvedin codegen input is a hard codegen errorType::Unknownin codegen input is a hard codegen errorsorrycan be emitted only when explicitly requested with--verify-mode sorry