All notable changes to Aver are documented here. Starting with 0.10.0, minor releases get a codename — short, evocative, and it tells you what the release was really about.
Intis now arbitrary-precision (ℤ) on every backend — VM, Rust, and wasm-gc. Integer arithmetic no longer overflows or wraps: a result that exceeds 64 bits keeps going instead of silently truncating, soIntis the mathematical integer everywhere, matching what proofs andverifyalready assumed. This closes the last place a verified Aver program could disagree with its proof at runtime. Small values keep a native-integer fast path; only genuinely large magnitudes pay for big-integer storage.
- Bounded integers recover native speed and code size under the ℤ default. A value the compiler can prove stays in a fixed range — an opaque or module-private record whose smart constructor bounds its field — is stored and computed as a native 64-bit integer rather than the big-integer representation on wasm-gc, comparisons against integer constants lower to a direct machine compare, and the big-integer runtime is shared across helpers instead of copied into each. Size-sensitive programs (the games corpus) shed most of the cost the ℤ default would otherwise add.
aver proof --discover --emit-lawswrites the laws it discovers as real Aver laws — but only the ones that survive an adversarial check, and it never claims a law is proven unless the kernel agrees. Until now a discovered helper law lived only in a generated Lean side-file, invisible toaver verify, to the adversarial--hostilerun, and to anyone reading the source. With--emit-laws, each candidate the discovery enumerator finds is rendered as an ordinaryverify <fn> lawblock and written to a sidecar<file>.aux.avnext to your source. Your source file is never touched: the sidecar is a separate, self-contained, runnable file (a verbatim copy of the source plus the proposed laws under a provenance header), so you review and merge the laws as a deliberate diff. From there a discovered law flows through exactly the same pipeline as a law you wrote by hand —aver check,aver verify, the cross-file lemma pool, andaver proof <sidecar> --check --gate. The honesty rule is the point: a candidate is emitted only if it survives a hostile-widened verify — the law is checked over a distinguishing, distinct-per-binder, type-boundary domain (the adversarial--hostilesemantics), not over the discovery sampler's own narrow, symmetric samples. That distinction is load-bearing: when two arguments of the same type are drawn from one small symmetric set, plain verify cannot tell a true law from its swapped/commuted twin, so a false "concatis commutative" or a swapped homomorphism (decode(a ++ b) == decode(b) ++ decode(a)) can sneak through. The hostile-widened check feeds genuinely distinguishing values and refutes those twins; where it cannot breach a guard (a property over a user-defined type it can't synthesize distinct witnesses for), the candidate is treated as inconclusive and dropped rather than emitted as if verified. Every dropped candidate is named with its reason. The provenance header is honest about strength: each emitted law is labeled "verified (bounded), kernel proof pending" — it passed the hostile check but is not claimed proven. To actually prove them, runaver proof <sidecar> --check; kernel credit there is gated on#print axioms, so a law whose proof transitively rests onsorry(for example by citing an unproven lemma as a rewrite rule) earns no "kernel-proved" credit and is reported as still unproven — no false law and no laundered credit. Law names carry a reservedaver_discovered_prefix and are de-duplicated against your existing law names; tuple-typed domains are rendered in the parser'sTuple<...>form and any shape that would not parse or typecheck is dropped, so the sidecar alwaysaver checks clean. The sidecar is byte-for-byte reproducible and is written atomically. An optional--emit-laws-to <path>overrides the sidecar location, and it refuses to write if the target is — or aliases (via symlink/./..) — your source or any input.av, so the override can never overwrite a hand-written program. This first version covers the discovery enumerator's equational candidates; the existing Lean side-file path is unchanged.- The
--emit-lawssidecar is now<file>.aux.av(was<file>.discovered.av) and tags each law with the proposer that originated it. The default sidecar name no longer presumes discovery: it is the provenance-neutral<file>.aux.av, so the file does not misname laws once other proposers can write to it. Each emitted law now carries a per-law// provenance: <origin>comment recording its origin — the discovery enumerator labels its lawsenumerated. This origin axis is separate from the existing strength note ("verified (bounded), kernel proof pending"); the strength header is unchanged. The--emit-laws-to <path>override and its source-aliasing safety check are unchanged. - A proof can now build on a law proven in a module it depends on. Previously a
verify ... lawin a dependency module was dropped on the floor — a consumer that imported the module could not use the law. Now a dependency's law becomes a lemma a consumer's proof can cite, but only when it earns it on two independent counts: it must be exposed by its module, and it must be proven there. A law about a private helper — one omitted from the module'sexposeslist, or named with a leading underscore — never crosses the boundary, exactly as a private function cannot be called from another module; a consumer cannot lean on a dependency's internal lemma even when its own proof happens to touch that helper. And a law that is exposed but left unproven (its proof falls to asorry) earns no credit: a consumer that cites it inherits the gap and reportsuniversal: falserather than a false pass, and a dependency law no consumer cites is never emitted into the consumer's build at all, so it cannot quietly add to the consumer's unproven count. A citation is offered only when the dependency law is genuinely relevant — inside the consumer proof's scope (its own functions, identified by their fullModule.functionname so an unrelated module that happens to reuse a name is never mistaken for it). For example, a module provingqrev(x, y) = rev(x) ++ ylets a consumer that wrapsrevprovemyRev(x) = qrev(x, [])universally by citing it. Single-module proofs are byte-for-byte unchanged. Lean backend. aver proof --discovernow shows the counterexample for the candidates it drops. When the VM-filter refutes a conjectured helper law, the report no longer prints only a count — it names the candidate and the exact sample assignment that falsified it, with both sides' values:refuted x0 == List.concat(x0, x0): falsified at x0=["a"]: LHS=["a"] RHS=["a", "a"]. The witnesses are listed in a stable order (capped, with an… and N more refutedtail when many candidates fall), and the output is identical across reruns, so the discovery loop can tell a genuinely false lemma from one the prover merely needs help with.aver proof --minimizerewrites each exported proof to the tactic that actually closed it — and can only ever tighten a proof, never break one. The auto-prover cannot know statically which tactic a given law needs, so at every law it pins a portfolio of alternatives —first | (simp …) | (… <;> omega) | … | sorry— ending in asorryfloor so a shape no strategy recognizes degrades to a caught gap rather than a hard build error that would poison the whole module. That hedge is robust but noisy: the shipped proof carries alternatives that never fired, plus asorryfloor even where the proof closed for real.--minimize(Lean-only; implies--check) resolves the hedge against a real build. It prefixes every branch with a marker and builds the project ONCE; Lean tries a portfolio's branches left to right and commits to the first that closes, tracing each it reaches, so the winning branch is the highest-numbered marker that surfaces (the branches tried and discarded before the winner trace too — the markers are not rolled back). Each portfolio is then collapsed to its winner and the project is RE-VERIFIED; if a theorem no longer closes — a mis-parsed winner — it keeps its original portfolio. So minimization is fail-safe (it never ships a proof that does not build) and status-preserving (it keeps exactly the branch Lean committed to): a law proven for real drops its alternatives and itssorryfloor and reads like a hand-written proof, while a law that only closed via its floor collapses to a baresorry— the honest gap is kept, never silently dropped, so thesorries/universalnumbers from--checkare unchanged. It is an opt-in polishing pass for a proof you are about to ship or review (it costs two extralake builds), not part of the normal verify loop. Lean backend.aver proof --gate <baseline.json>fails if a previously-proven law is removed or weakened. The gate records every proven law in a per-law manifest — its identity, the strength it was proven at (universal, bounded, sampled), the kernel axioms its proof depends on, and the backend that proved it — and compares a fresh run against a committed baseline. It exits non-zero, naming the offending law, when a baseline law is missing, drops to a weaker tier (for example a silent slide from a universal proof to a sampled one), starts depending on any axiom not already in that law's own baseline record (whether or not the axiom is a standard one — addingClassical.choiceorQuot.soundwhere the baseline had onlypropextis a regression), or changes backend. Adding new laws is always allowed. To accept a legitimate removal or weakening, regenerate the baseline withaver proof --write-baseline <path>so the change lands as a reviewable diff. The baseline is a committed, code-reviewed file: CI runs--gateagainst it, never--write-baseline(regenerating the baseline is a deliberate human acknowledgement, visible in the git diff of the baseline file). A corrupt or truncated baseline, or twoverify ... lawblocks sharing one law identity, are setup failures rather than silent passes. Exit codes: 0 clean, 1 on a regression, 2 on a setup failure (unreadable/corrupt baseline, duplicate law identity, verifier not installed). Lean backend.- Conditional laws proven universally now export a ready-to-use plain-logic form — alongside the universal proof of a
when-law, the Lean export derives a companion statement of the same fact with its premises and conclusion stated as ordinary propositions, so a later proof can cite it directly without re-deriving the boolean-to-proposition bridges. - A universally-proven
when-law can build on earlier ones — the Lean export can have one law's side proof reuse an earlier law's, and editing the earlier law now re-checks every law that depends on it (no stale results survive an upstream change). - Proofs of conditional laws can now build on earlier proven laws in the same file — when a later
when-law's claim contains the shape of an earlier provenwhen-law's conclusion and carries all of its premises, the Lean export proves the later law universally by applying the earlier one, and the proof report shows the dependency. A law missing one of the premises simply keeps its sampled-domain check — nothing fails. First proof family on board: floored division is stable across a finer-grid cell (a / (b*d) = w / dwhenw*b < a < (w+1)*b). - The proof corpus now includes a floor-grid task — it checks that floor stability inside a finer cell can be reused to prove agreement on a coarser cell.
- A law whose generated name clashes with another theorem in the same proof is skipped with a clear note instead of silently costing a neighboring law its universal credit. The clash is reported in the per-law proof report rather than failing quietly.
- Large conditional law domains export in smaller Lean proof pieces so broad sampled grids no longer fail just because the generated proof statement is too large for Lean to elaborate as one theorem. A law with thousands of cases now emits at most 512 evenly-spaced per-case sample checks; when some are dropped the Lean file says so explicitly (
-- aver:samples-capped <emitted>/<total>), and the full domain is still covered by the partitioned checked-domain theorems. - wasm-gc strings count characters, not bytes —
String.len,String.charAt,String.slice, andString.charsnow use Unicode character (scalar) counts and indices, matching the VM and the documentation on every multi-byte string.String.byteLengthstill counts UTF-8 bytes.examples/data/json.avpassesaver verify --wasm-gcin full, including its emoji cases. - wasm-gc verifies tuple results containing generic constructors — functions that return values like
(Option.None, n)or compare expected tuples like(Result.Ok(2), Result.Ok(0))now keep the concrete payload types and run like they do on the VM. As a side effect, a tuple literal in a driven position (a declared return, an annotated binding, or a verify expectation) whose element doesn't fit the annotation is now rejected when the file is checked, instead of failing later at run time. Known residual: a hand-written==between two such tuples outside a verify block still loses the payload types on wasm-gc. - wasm-gc
Char.toCodereturns Unicode scalar values — multi-byte characters such asż,…, and😀now report the same code points as the VM and roundtrip throughChar.fromCode. - Comparing
Resultvalues no longer traps on wasm-gc when one module wraps two different record types inResult<…, String>—Result.Ok/Result.Errnow build the instantiation the type checker resolved at the site instead of guessing from the payload type.examples/core/order_total.avpassesaver verify --wasm-gcin full. aver auditcounts every check error in its total and exit code —error[verify-rhs](a verify case calling the function under test on the right side of=>) was printed but excluded from both, so a file with only such errors audited green.
- CI gates the shipped example corpus —
examples/coreandexamples/datamust passaver checkon every pull request; a merge can no longer silently break a shipped example.
The release where the prover learned to be steered by lemmas — named after the proof methodology of ACL2's Kaufmann & Moore, whose 30-year-old terrain this release kept rediscovering.
- The JSON example proves end-to-end on Lean. The escaped-string parse/serialize roundtrip laws — the example's last two caught
sorrys — now close kernel-genuine, no Mathlib, nonative_decide. The strategy recognizes any escaper/parser pair of that shape, not just this one. - Exact-rational arithmetic ships as a worked example (
examples/data/rational.av) — its full ring algebra proves universally on both backends. Any all-Intrecord with pure+/-/*laws qualifies. - Floor-division laws prove universally on both backends — halving recursion (binary exponent search, digit extraction) gets genuine well-founded termination instead of falling outside the proof subset, and power-of-two window laws close as true universal proofs.
- Conditional
whenlaws over sign and equality premises prove universally — for every input satisfying the premise, not just the sampled domain;--check-jsonreports the count aswhen_universal. - Your proven laws help prove your later laws — decomposing a hard law into helper laws in the same file is a working proof strategy; opt-in
aver proof --discoveradditionally conjectures and kernel-proves helpers automatically. - Recursive functions over your own types prove termination structurally — no more fuel wrappers for tree-shaped data; mutual recursion (e.g. quicksort) gets genuine well-founded termination.
- More laws close push-button: finite given-domains (
Bool, field-less enums), decimal render/parse roundtrips, built-in string/int facts, ground enum laws,Int.max/Int.minarithmetic, general-keyMaplaws, and?pipelines. aver proof --check-jsonreportsuniversal_lawsandbounded_laws— CI budgets can pin exact law counts instead of re-deriving them.
universal: truenow means genuinely universal — boundedwhen-law checks still pass, they just no longer count as proven-for-all-inputs.aver checkcatches compound expected-side type mismatches in verify cases at check time instead of leaving them as guaranteed runtime failures.
whenlaws export honestly to both backends: Dafny samples respect the premise, sample assertions stop pushing Z3 into minutes-long unfolding, Lean premises pin their numerals toInt(no false-as-stated theorems), and nonlinear laws degrade to honest caughtsorrys instead of failing the build.- Proof exports decline instead of guessing: a law over an unknown helper name degrades to a caught
sorry(not a broken build), large given-domain grids split into bounded chunks in every export mode, and Dafny no longer guessesdecreasesmeasures that fail verification on correct functions. - Bounded proof checks compare against actual program results, and any model panic during a proof is a hard check error (
model_panickedin--check-json). - Tuple-carrying functions get correct termination fuel in Lean exports — deeply nested values no longer make the exported model disagree with the running program.
- wasm-gc runs much more of the verify corpus:
Bool == Boolcompiles (so every=> truecase works), Option-returning functions verify — including nested and compound payloads — and backend compile failures are reported as backend errors instead of "type errors" on files that type-check. Known residuals are documented:Vector<Option<T>>, Option inside tuple-typed results, and two pre-existing VM↔wasm-gc divergences (String.lenbytes vs scalars; aResult-equality trap). - wasm-gc:
String.split/String.joininside string interpolation compile correctly. aver verifylabelslawblocks aslaw— they printed asspec.- Example corpus honesty: overstated laws are plain verify blocks or trace checks now, and every example under
examples/coreandexamples/datapassesaver checkclean.
Patch release on top of "Divide" — a correctness fix in the optimizer and broader aver check hints.
- Constant folding no longer drops a side effect or a non-terminating computation. When the optimizer collapsed a
match/Result.withDefault/?over a statically-knownResult/Option, or anInt.div/Int.modby a literal zero, it could discard a sub-expression that still had to run — silently dropping its effects (e.g. aConsole.print), or turning a program that should loop forever into one that returns; one wildcard case could also crash the VM. Folded expressions now always run their effects, in source order.
aver checkflags more redundant work — a loop invariant built directly inside a recursive call's arguments, and a pure function computed more than once with the same arguments in a single expression.
One middle-end under every backend; the last operator that could crash is now a function.
- Integer
/is removed; useInt.div(a, b) : Result<Int, String>. It was the last partial operation posing as a total operator.a / bon twoInts is now a type error pointing atInt.div, which returnsResult.Erron a zero divisor or thei64::MIN / -1overflow.Int.divis Euclidean — the partner ofInt.mod(Int.div(-7, 2) == -4). Float/stays total. Migration:match Int.div(a, b), orResult.withDefault(Int.div(a, b), <fallback>)when the divisor is known non-zero.
aver compile --emit-ir-after=mirdumps the textualMirProgram— the executable middle-end the VM runs, after HIR → MIR lowering and the optimize pipeline.aver compile --explain-mir-coveragereports how much of a program lowers to MIR (per function, with the dominant blocker);--target wasm-gcretargets the meter at the wasm-gc backend's reach.
Fn(...)is allowed only as a function-parameter type. Aver has no closures, so a function value can only be a named fn / builtin / constructor passed as an argument. UsingFn(...)as a return type, a field, a collection element, or a local binding is now a type error — keeping the concrete callee (and its effects) statically known at every call. To choose between functions dynamically, branch at the call site or model the choice as a sum type.
- wasm-gc: a tuple binding that follows
_is no longer dropped.match pair { (_, value) -> value }returned the field's zero default instead of the bound element; tuple fields are now paired with their binding by position regardless of where the wildcards sit. aver run --self-hostkeeps pace with the language. The self-hosted interpreter now handlesInt.div, float literals inside string interpolation, and Unit-returningmainunder record/replay — three places it had lagged the VM. (Higher-order functions and multi-module programs are still being brought across.)
- MIR is now the only runtime middle-end. The VM compiles exclusively through Core MIR (
src/ir/mir/); the old ~2200-line HIR tree-walking compiler is gone — a function that can't lower to MIR is a hard error, not a silent second path. wasm-gc and wasip2 emit from MIR too (resolved-HIR fallback for shapes they don't yet cover) and run the sharedir::mir::optimize()passes, so one optimizer improves every backend at once. - Owned
Vector/Mapare mutated in place. The MIR last-use pass updates a uniquely-held collection without copying it first, turning build-and-fill loops from O(n²) into O(n) — roughly 20× faster on the VM and 26× on native (--target rust) versus 0.23; wasm-gc is unchanged (it leans on the engine's GC). matchonInt.div/Int.mod'sResultlowers on every backend. The directly-consumed (boxed) form now builds theResult<Int, String>on wasm-gc and exports faithfully to Lean/Dafny (guarding the zero divisor so theErrarm is reachable), so the Ok/Err idiom is no longer VM-only.- A constant divisor compiles
Int.div/Int.moddown to bare division. The MIR const-folder rewritesInt.div(a, k)for a literalk ∉ {0, -1}toResult.Ok(a div_euclid k), then folds the consumer (withDefault/match) over the now-literal constructor — somatch Int.div(a, 10) { Ok / Err }andResult.withDefault(Int.div(a, 10), d)lower to a plain Euclidean division on every backend. The explicitResult-returning function disappears when it provably can't fail. - wasm-gc modules are byte-reproducible across builds. Carrier type slots are now registered in sorted order instead of
HashMap-iteration order.
What
aver shapesees,aver proofnow uses.
- All commands now use the same module-root default: explicit
--module-rootwins, otherwise current working directory. Previouslyaver run,aver run --wasm-gc, andaver run --wasip2walked the entry file's parent chain to find a directory where everydepends [...]resolved, whileaver verify/aver check/aver context/aver compilealready used cwd. After this change, every command treats the same arguments the same way. Migration: if you relied onaver run projects/foo/main.avfrom the repo root findingprojects/foo/depends/*, add--module-root projects/foo(orcdinto that directory).
- MVP — architectural smell radar (CLI + LSP). Per-fn archetypes (14 labels),
ModuleShape5-dim vector with derivedKind(ServiceClient,Orchestration,SmartConstructor,DataModule,PureHelpers,Library,EffectfulLibrary,EffectfulShell), and a histogram-based Layer guess (Domain/Parse/Command/AiStrategy/RenderUi/Infra) with confidence and runners-up.aver shape <dir>walks corpora;--lintchecksaver.toml's[[shape.expected]]. LSP surfaces the verdict via CodeLens, hover, and document symbol. - Module patterns are now first-class typed facts. Five shapes by name:
RefinementSmartConstructor,WrapperOverRecursion,ResultPipelineChain,RendererFormatter,MatchDispatcherFold. Surfaced in a newModule patterns:section in the CLI output and as apatternsarray in--json. Same data feeds the LSP andproof_lower. - Output is flat + colored — section headers at column 0, ANSI palette (bold headers, cyan Kind / Layer, yellow counts, magenta pattern variants). Auto-disables on non-TTY pipes;
NO_COLORrespected.
- Three new
ProofStrategyvariants close universal laws on Lean and Dafny. Each consumes aModulePattern:WrapperOverRecursion— monoidal-accumulator wrapper (sum(xs) == sumDirect(xs)). Demo:examples/data/sum_acc.av.ResultPipelineChain—?-chain ≡ nestedmatch Result.Err -> Err. Demo:examples/core/result_chain.av.MatchDispatcherFold— two list folds equal by structural induction. Demo:examples/data/list_length_fold.av.
aver proof --checkruns the backend verifier and gates regressions.--error-budget=N(Dafny) and--sorry-budget=N(Lean) tolerate up to N residual failures so CI can pin a budget;--check-jsonemits{backend, errors|sorries, budget, passed}for external harnesses. Exit codes: 0 within budget, 1 over, 2 on harness failure.- Playground Audit panel gains a Shape section — Kind · Layer summary; expand to see module-shape vector, histogram, recognized patterns, and per-fn archetypes. Same payload as
aver shape --json; no new toolbar button. aver verify --hostile: opaque runtime handles can be fabricated inside verify-trace context for system-handle effects (today:Tcp.Connection), so Oracle stubs can feed a deterministic conn into the SUT without round-tripping throughTcp.connect. Opt-in per type — user-defined opaques (Refinement.Natural, …) stay protected.examples/services/redis.avexercises this viaverify ping/set/get traceblocks.
aver verifyworks on multi-module programs. 0.22.0 shipped with a regression: runningaver verify <entry>on any program that declareddepends [...]failed with"missing VM symbol for exposed function Foo.Bar.baz". Every multi-module example underexamples/refinement/(including the refinement-via-opaque flagship that Lift introduced),examples/apps/notepad/, andprojects/payment_ops/hit it;aver run,aver compile, andaver verify --wasm-gcwere unaffected. The VM verify path now loads dep modules the same way the other paths already do (both the disk-loader CLI shape and the pre-loaded playground/LSP shape).
Aver source stays ordinary. The proof export lifts it to the backend's native mathematical shape — refinements become subtypes, mutual recursion becomes a structural block, every verify-law passes through one classifier, and Dafny closes more obligations on its own.
- Aver recovers proof-language refinements from ordinary validated code. A single-field
Intrecord with a validating smart constructor (e.g.Natural { v: Int }withv >= 0) now exports as a native subtype on Lean ({ v : Int // P v }) and subset type on Dafny (type X = v: int | P v witness W); the predicate travels with the type. A public function that guardsn >= 0before calling a private worker similarly refines that worker's domain in the export. Universal laws likeadd_commutative(a: Natural, b: Natural)close in the backend's natural proof shape, no per-law tactic plumbing. Conservative and source-compatible — no new Aver annotation, andwhenclauses stronger than the recovered invariant are kept as theorem premises rather than silently dropped. Recovered refinements survive module boundaries.
- Mutual-recursion SCCs over
List/Vector/Stringparameters export natively. Lean emits a singlemutual ... termination_by ... endblock keyed off the structural measure; Dafny emits adecreases <measure>, <rank>tuple per member. The fuel-bounded helper-and-wrapper indirection is gone for these groups. Bounded-∀ universal laws over the SCC verify as real proofs (BigInt'sadd_commutativemoves fromassume {:axiom}on trust to a real theorem). Sample assertions no longer exhaust Lean's synth budget on compound predicates.
- Both proof backends read every verify-law strategy from one classifier. Each
verify <fn> lawlowers to one of fourteen algebraic shapes (commutative / associative / identity / induction / library axiom / map update / linear arithmetic / four spec-equivalence flavours / linear recurrence) before Lean or Dafny emits anything. Visible viaaver compile --emit-ir-after law_lower. Effectful impl-vs-spec laws classify on the canonical post-Oracle-Lift shape;fib(n) == fibSpec(n)closes as a real proof on both backends (Nat-helper bridge + worker-shift lemma) where prior releases emittedsorry/ empty-body.
dafny verifycloses 25 more proofs across the flagship suite (160 errors → 135). Smarterinfer_decreasespicks the actually-moving recursion measure across self-call sites (catchesrepeat(char_, n - 1)andscanExpTail(s, pos + 1, start)). List-induction hints case-split|xs| == 0and recurse onxs[1..], detecting recursive fns nested underMap.*/Option.*helpers.examples/data/map.avverifies clean;fibonacci,rle,quicksort,json,grok_s_languageall improve without regression.String.slicelowers via a clamp-to-empty helper instead of raws[from..to]. Aver's runtime semantics — negative or out-of-range indices collapse to an empty slice — now travel into the Dafny export.examples/data/date.avverifies clean (theparseIntSlice(s, from, to)shape no longer leaves uncloseable range obligations on every caller);examples/data/json.avcloses 24 more proofs at the same time (113 → 89 errors), entirely from the parser's slice-heavy lookahead. Source-compatible — no Aver-side change, the helper is emitted only whenString.sliceactually appears.- Float
/lowers via aFloatDivhelper that mirrors Aver's IEEE-754 semantics. Aver float division never crashes —1.0/0.0isInfinity,0.0/0.0isNaN. Dafny's exact-rationalreal, in contrast, makesa / bimposeb != 0on every caller. The new helper returns a defined value (0.0) when the divisor is zero, so callers likegoldenApprox(n) = Float.fromInt(fib(n + 1)) / Float.fromInt(fib(n))no longer need to provefib(n) ≥ 1just to compute the ratio.examples/data/fibonacci.avverifies clean. - Negative-domain guard recognition broader. Recursive
Intfunctions whose author writesmatch n <= 0 { true -> base; false -> recur(n - 1) }(or the equivalentmatch n < 1 { ... }) are now treated as self-guarded byinfer_decreases, so the lowerer emitsdecreases if n >= 0 then n else 0without arequires n >= 0precondition the user never wrote.examples/data/rle.av'srepeat(char_, n)shape closes (theexpandRun(run)caller no longer has to proverun.count ≥ 0).
tests/proof_specgatesdafny verifyon the IR-clean examples and tracks per-examplesorryand Dafny-error budgets across both backends. Lean: three examples carry honestsorrybudgets (json.av13 sampled-domain laws,rle.av2,quicksort.av2). Dafny: the flagship examples whose proofs Dafny still can't auto-discharge —fibonacci(1),rle(4),quicksort(5),date(2),json(113) — are now gated by an error-count budget instead of being silently unverified. Drift either way fails the test, so a new shape regressing or an old gap closing both surface in CI.aver compile --emit-ir-after={refinement_lower,contract_lower,law_lower}exposes the three new proof-lower stages — when a law falls through tosorry/empty-body,law_lowershows whether the lowerer pinned a strategy or fell back toBackendDispatch.
- Every backend (Rust, Lean, Dafny, wasm-gc) now consumes one typed view of the program. Pre-Lift each backend carried its own string-keyed fn-signature side-channel (
ctx.fn_sigs) and re-derived types by parsing source-annotation strings on the fly during emit; identity decisions (whichShapedoes this constructor refer to? which dep module'sBoxis at this slot? is this callee pure?) lived in fragile bare-name lookups that could silently drift between backends. Lift consolidates the codegen substrate around one resolver-produced view of the post-name-resolution program — typed(name, Type)parameter lists, typed return types, opaqueFnId/TypeId/CtorIdidentity, and a.ty()slot on every reachable body expression — and the bare-string fn-signature cache is gone. A guardrail test (tests/identity_guardrails.rs) blocks any new backend code from regressing to the old patterns. The most visible payoff: two dep modules can each declarerecord Box { value: Int }and compile cleanly onaver compile --target wasm-gc(pre-Lift the post-flatten type registry collided them under one slot andLeft.Box(value = 5).value + Right.Box(value = 10).valuefailed wasm validation), and==/!=on those records dispatches to the right per-type equality helper rather than silently picking the last writer. Single-declarer dep types stay on the legacy bare-key path with no behavior change; the next major IR step (MIR / Core IR for per-expression effects + ownership + cross-scope optimisation) lowers from this substrate, separate epic. String.fromFloatonaver compile --target wasm-gcnow matches VM and wasip2 to the last shortest-roundtrip digit. The wasm-gc WAT helper capped the fractional-digit loop at 15 to avoid ani64.trunc_f64_strap on large-magnitude inputs, but the cap also chopped one digit off values in the[0.1, 10)range where the multiplication doesn't overflow —goldenApprox(n) = Float.fromInt(fib(n + 1)) / Float.fromInt(fib(n))fromexamples/data/fibonacci.avprinted1.618181818181818on wasm-gc vs1.6181818181818182on VM. The cap is now 17 (the IEEE 754 f64 maximum); the overflow guard above it still bails before the trap on large magnitudes, so behaviour for values outside[0.1, 10)is unchanged.Console.print/Console.error/Console.warnonaver compile --target wasip2now append the trailing newline that VM and--target wasm-gcalready shipped. Pre-Lift wasip2's lowering wrote only the string bytes to the wasi output stream, so a sequence ofConsole.print("a"); Console.print("b")came out asabonaver run --wasip2anda\nb\neverywhere else — multi-line programs on wasip2 produced one jammed line. Theprintln!semantic now lives in a dedicated__rt_println_to_lmbridge helper called by all three Console.* methods; other consumers of the bridge (Disk.writeText,Http.*marshalling) stay on the no-newline__rt_string_to_lmand are unaffected.
- New
examples/refinement/collects the canonical refinement-via-opaque demos:Natural,Positive,IntRange,NonNegFloat,Email,BigInt. Each exercises a different point in the design space.
- Functions with wildcard
_parameters (fn f(_: Int)) compile and run cleanly on every backend. Pre-Lift the resolver short-circuited_and never claimed a slot for it, solocal_countstayed at zero while callsites still pushed one value per source-level param —aver verifyon a program likefn ignore(_: Int) -> Int = 42thenignore(7) => 42deterministically panicked inside the VM dispatch loop. The resolver now allocates a slot for every param regardless of name; wildcard params still skip the scope map so the body cannot read them, but the frame layout matches what callsites push. Found byfuzz_verify_runnerAFL nightly.
aver verify --hostilenow exercises a third axis: execution order. Everyverify <fn> lawcase whose fn contains an(a, b)!independent-product gets a twin run in which the branches execute right-to-left, with each result placed back into its source position. A pure law claims its independent products commute, so the twin's tuple must match the forward run; a mismatch surfaces asverify-hostile-mismatchwith origin+reverse-eval. Catches the class of bug where the runtime, the stub map, or a compiler optimisation has snuck a hidden ordering dependency into code Aver was treating as order-invariant. The most common shape it catches: a law that pins a trace event by flattrace.event(k)(which indexes the global emission sequence, so reverse-eval moves it) instead of structuraltrace.group(g).branch(b).event(k)(which addresses by source position and stays order-invariant). Same--hostileflag — no new CLI surface, no source-language change. New worked example atexamples/formal/hostile_order_axis.av; the diagnostic's repair message names the right rewrite when it fires.
Iron in the frame — the type checker stops lying to itself about negation, recursion, and identity. A new fuzz harness shakes the rest of the toolchain until things fall out.
- Duplicate
fnnames in one module are a type error. Pre-Iron the second definition silently replaced the first — a typo could swap a function's body with no signal. - Polymorphic recursion that would need
T := F<...T...>is rejected. A shape likefn nest(v: A) -> Unit; nest([v])now surfaces as a normal type-incompatibility error instead of silently typechecking with a circular binding that later confused backends. - Two same-named types from different modules don't conflate. A project importing
A.Shape(Circle | Square) andB.Shape(Triangle | Hexagon) used to silently accept aB.Shapevalue whereA.Shapewas expected. Now rejected withexpected A.Shape, got B.Shape; the legitimate same-name-same-type case still type-checks. - One source error stops at one diagnostic.
let z = add(unknownFn(1), unknownFn(2))reported the two real errors plus two follow-onexpected Int, got Invalidcascades; the recovery sentinel is now treated as a wildcard so callers see only the originating diagnostic. - Unreachable
matcharms are rejected. A second_ -> ...(or a repeated0 -> ...) used to compile silently — the second arm could never fire because Aver match is first-arm-wins, but nothing flagged it. The type checker now reportsUnreachable match arm: pattern X is already covered by an earlier arm at line N, naming the covering arm so you can pick which one to delete. Catches duplicate wildcards, identical literal patterns, identical constructor patterns, and structurally-equivalent tuples.
- Deeply-nested expressions return a parse error instead of crashing
aver. Sources with 2500+ nested(...),Option.Some(...),{...}, chained-, chained?, or deeply-nested match patterns used to abort the process withfatal runtime error: stack overflow. The parser now caps recursion at 64 levels and surfacesExpression too deeply nestedas an ordinaryparse-error.
-0.0keeps its sign bit on every backend. Pre-Iron-xdesugared to0 - x, and0.0 - 0.0collapses to+0.0under IEEE 754. Unary minus is now a first-class AST node and every backend negates at the float bit level, so-0.0round-trips through compile + eval + replay.-xon tight numeric loops is faster. Typed unary-minus dispatch on the VM joins the typed-arith pipeline (so-xruns at the same shape as+/*//for known-Int/ known-Floatoperands).- Hand-edited compiler inputs surface as compile errors, not panics. Sources that registered two meanings under one symbol (duplicate variant constructor with conflicting payload, constant rebound under a function name, empty namespace path) used to panic the VM compiler; they now surface as
Compile error: VM symbol 'X' already exists as ...with a source span.
aver replayaccepts every recording shapeaver run --recordwrites. Three places where replay refused or panicked on legitimate input got fixed: recordings touchingVectorvalues, single-string-keyMaprecordings (collided with the marker shape), and any JSON containing multi-byte UTF-8 in an unexpected position (the JSON keyword parser sliced&strand panicked onbyte index N is not a char boundary).
String.fromFloat(x)doesn't trap at1e4–1e18magnitudes. The shortest-roundtrip loop overflowedi64.trunc_f64_sand the module crashed withwasm trap: integer overflow. The loop now bails one iteration before overflow, accepting fewer fractional digits at very large magnitudes instead of a crash.Result<Unit, X>andTuple<Unit, X>compile. The carrier-equality codegen path had noUnitarm and rejected typecheck-clean programs that used these shapes (e.g.fn animate() -> Result<Unit, String>).Console.printfrom in-process embedders is captured correctly. When the host called wasm-gc-compiled code inside a thread-local stdout capture,Console.printwrote directly to fd 1 and bypassed the buffer — programs looked silent from the host's view even though they did print. The wasm-gc Console import now routes through the same capture path the VM uses.
aver verifywon't hang on a non-terminating case. Averifyblock whose function had no terminating recursive base case (an easy shape to write by accident, and the one the fuzz harness reproduces every nightly) used to spin in the VM forever. The runtime now caps per-case work at 10M VM opcodes (mirrored as a wasmtime fuel budget onaver verify --wasm-gc); over-budget cases surface asRuntimeError: VM step limit exceeded, not a hang.
- Iron ships a multi-target fuzz harness. Eight AFL++ targets run nightly against
main: frontend (lex + parse + typecheck), codegen on three backends (wasm-gc / wasip2 / Rust), VM-vs-wasm-gc parity (in-process, ~300 execs/s), verify runner, replay record→replay roundtrip. Plus a custom AST-aware mutator that produces typecheck-clean inputs reaching the codegen + verify layers byte-havoc rarely touches. Every "the parser used to crash" / "the backend rejected a typecheck-clean program" bullet above was found by this harness during Iron development. PR-side cost is zero — fuzz only runs nightly + on manualworkflow_dispatch. aver verifyruns on--wasm-gc. Cross-target check — catches divergence between VM and wasm-gc on equality. Pure value-givens and value-hostile cases supported; trace projections, classified-effect Oracle stubs, andBranchPathcases reject upfront with an actionable pointer back to the VM verify path.- Snapshot regression suites per backend.
examples/corpus walks under wasm-gc + wasip2 codegen on every PR (≈0.1s each), Rust codegen + Lean + Dafny build under nightly. A compiler refactor that breaks codegen on a vetted example fails a named test instead of an obscure end-to-end break.
The same Aver source that opened HTTP both ways in 0.19 now opens raw TCP — connect, send, receive, ping. One pool, one handle shape, every backend the same.
- TCP client on
--target wasip2. All six methods —Tcp.connect,Tcp.writeLine,Tcp.readLine,Tcp.send,Tcp.close,Tcp.ping— compile and run as components againstwasi:sockets/*. Long-livedconnect/writeLine/readLine/closeshare a 256-slot pool keyed by"tcp-N"(same id shape every other backend uses, so cross-backend code stays portable);sendandpingare ephemeral (no pool slot), so a program holding 256 live connections can still issue either. Run withwasmtime run -W gc=y -W tail-call=y -S inherit-network=y -S allow-ip-name-lookup=y -S tcp=y, or viaaver run --wasip2. Seedocs/wasip2.mdfor the per-method status table.
Aver speaks HTTP both ways now — same source can call out as a client and answer back as a server, the same
.component.wasmruns on Cranelift and V8.
- HTTP client on
--target wasip2. All six methods —Http.get,Http.head,Http.delete,Http.post,Http.put,Http.patch— now compile and run as components. Response headers surface asMap<String, List<String>>; multi-value headers (e.g.Set-Cookie) keep server emit order. Failure messages name the wasi:http error variant (http: connection-refused,http: DNS-timeout, …) instead of a generic string. HttpServer.listenon--target wasip2 --world wasi:http/proxy. Write a handlerfn(HttpRequest) -> HttpResponse, name it via--handler <fn>at compile time (same flag the wasm-gc + Cloudflare path uses), compile to a.component.wasm, and serve it with any wasi-http host —wasmtime serve --addr=:N, Spin, NGINX Unit, wasmCloud. Request method / path / query / body / headers surface inside the handler as ordinary Aver values; the response's status code, body bytes, andMap<String, List<String>>headers round-trip to the client. The same source keeps working underaver runbecauseHttpServer.listen(port, handler)inmainis still a real call under VM — it just lowers to a no-op when the wasip2 proxy codegen takes over (which reads the handler identity from the flag, not frommain).HttpServer.listenWith(per-instance context handler) stays deferred for one more iteration.
Cross the Component Model boundary the same way Aver crosses the source/wasm one — typed effects in, canonical-ABI imports out.
--target wasip2. WASI 0.2 / Component Model output. Wraps a wasm-gc core module viawit-component, lowers Aver effects directly to canonical-ABI WASI imports (no preview-1 adapter, no--bridgeshim). Emits.component.wasm+ sibling.wit. Effect surface:Console.{print,error,warn,readLine},Args.get,Env.get,Time.{unixMs,now,sleep},Random.{int,float}, all 7Disk.*methods (exists/readText/writeText/appendText/delete/deleteDir/makeDir/listDir).Env.setandTerminal.*reject at compile time as structurally absent from WASI 0.2;Http.*/Tcp.*/HttpServer.*deferred to 0.19+. Seedocs/wasip2.md.aver run --wasip2— embedded wasmtime +wasmtime-wasirunner. CWD preopened as.for filesystem effects (matches VM target's path resolution semantics).tests/wasip2_stress.rs— six regression tests covering 100× write+read+delete (resource leak), 50KB write/read roundtrip (chunked-write boundary), 5KB Console.print (chunked-write on stdout side), 200-entry listDir, Random distribution sanity, Time.sleep precision.
--target wasmdeleted. The pre-2024 NaN-boxed wasm32 backend (src/codegen/wasm/, ~9.5 kLoC) is gone, plus thewasm-legacyCargo feature, the--bridge {wasip1,fetch,none}flag, theaver wasm-runtimesubcommand, and the legacy bundling code insrc/main/commands.rs. Modern hosts run--target wasm-gc; standalone runtimes use--target wasip2.--target edge-wasmwent too — it depended on the deletedcodegen::wasm::emit_wasm_with_adapter.BenchTarget::WasmLocalremoved — bench targets are nowvm/wasm-gc/wasm-gc-v8/rust.
module.rs/builtins.rs/types.rssplit.wasm_gc/module.rs5984 → 4197 (extractedwasip2_helpers.rs);body/builtins.rs2351 → 1740 (extractedbody/builtins_wasip2.rs);wasm_gc/types.rs2778 → 1934 (extractedtypes_discovery.rsforcollect_*AST walkers). Pure code movement, no behaviour change.
aver proofBranchPath prelude gating. Files with classified effects (Time.unixMs,Random.int,Disk.readText) emit Oracle subtype predicates that referenceBranchPath(abbrev TimeUnixMsOracle := BranchPath → Int → Intin Lean,predicate IsTimeUnixMsNonneg(f: (BranchPath, int) -> int)in Dafny). Theunion_bodysubstring check that decides whether to ship theBranchPathstructure / datatype in the prelude ran BEFORE the subtype block was appended —AverCommon.leanandcommon.dfyended up missing the declaration and the build failed withunknown identifier 'BranchPath'(Lean) /Type or type parameter is not declared in this scope: BranchPath(Dafny). Fold the subtype block intounion_bodybefore computingneeded_helpers. Same one-line reorder in both backends.
aver run --verifyremoved. Run and verify are two different commands; bundling them forced a guard for every flag combination (--verify --record,--verify --expr,--verify --wasm-gcnot yet wired). Callers compose:aver run x.av && aver verify x.av. Pre-1.0; no users to deprecate against.
aver verify --wasm-gcruns verify cases via the wasm-gc backend instead of the VM. Cross-target check — catches divergence between VM and wasm-gc codegen on equality. Synthesizes a__verify_X_check() -> Boolhelper per case and lets wasm-gc lower==natively via the per-type eq_helpers registry; the host decodes a single i32 per case. Cross-moduledepends [...]supported. Failure diagnostics render the actual runtime value for primitive return types (Int/Float/Bool/Str) via synthesized__verify_X_repr() -> Stringhelpers + the__rt_string_to_lmdecode bridge. Trace projections (.trace.*), classified-effect Oracle stubs (given X: Time = stub), and case bodies mentioningBranchPathare rejected upfront with an actionable pointer back to VM verify — those depend on namespace-value dispatch and runtime override that the wasm-gc backend doesn't have yet.aver bench --target wasm-gc-v8alongsidewasm-gc-wasmtime— runs the wasm-gc bytes under V8 (Node 22+ viatools/wasm-gc-bench-v8.mjs) so alloc-heavy workloads aren't pinned to wasmtime's GC engine speed.tests/cross_backend_stress.rs::cross_vector_aliasing_pin_*— pins theVector.setaliasing corruption repro across all four backends (VM, legacy wasm, wasm-gc, self-host). Futureir::aliasrelaxation must keep this green or it ships unsound.
- VM ~1.46× faster (geo-mean). Hoist bytecode pointer out of the dispatch loop, fuse
match n { LIT -> … }into oneMATCH_INT_LITERAL, typed arith + compare forInt/Floatoperands, builtin invoke takessymbol_iddirectly (no per-call hash lookup), profile flag cached. countdown ▼-71%, newtype ▼-42%, match_dispatch ▼-39%, fractal_seahorse ▼-12%.
--target wasm-gc --handler X(and--preset cloudflare). Three compounding wasm-gc bugs broke every handler-mode compile since 0.16: missingcaller_fn_idxin handler synthesis, duplicate user/builtin record entries in the rec group, and a data-count snapshot taken before the handler wrapper registered its caller_fn name.tools/edgedeploys cleanly again.- Cloudflare worker stubs match the wasm-gc ABI (trailing
_calleri32,console_*stubs).
aver bench --target wasm-gcalongsidevm/wasm-local/rust.
- Paren-tuple types
(A, B)no longer valid. Type position usesTuple<A, B>only; value literals ((1, 2)) and patterns ((a, b) -> …) unchanged. Old form errors withparen-tuple types removed — use 'Tuple<A, B>'. Migrated ~30 files acrossexamples/,self_hosted/,docs/.
- Vector / Map aliasing —
Vector.set/Map.setno longer rewrite shared rows. Newir::aliaspass flags every slot reachable fromVector.get/Map.get/Vector.new(_, compound)(orVector<…>/Map<…>param) onFnResolution.aliased_slots. VM skips thelast_usemem::takepath on flagged slots; wasm-gc skips clone-on-write when the slot is non-aliased + last-use, falls back toarray.copy+array.seton a fresh array otherwise. Plus wasm-gc now registers nestedVector<Vector<T>>fromVector.newcall sites. Legacy--target wasm,--self-host, Rust codegen unaffected. Int.modEuclidean across every backend. Result in[0, |b|)regardless of signs —Int.mod(-7, 3) = 2. Rust codegen already had it; VM + wasm-gc (per-instantiation__int_mod_euclidhelper) + legacy wasm (inlinerem_euclidsequence) now match. Legacy--target wasmstill traps onb == 0(pre-existing); VM / wasm-gc / Rust returnResult.Err("division by zero"). FusedResult.withDefault(Int.mod(a, b), default)sidesteps the trap on every backend.
Invalid indentation levelincludes a hint about the most common cause (wrapped fn signatures, multi-line argument lists — both unsupported) and points at the fix (one-line declaration or named helper).- Skill /
llms.txtclarified for three high-bias-from-other-languages mistakes:()is not a Unit literal (writeUnit),?is Result-only (useOption.withDefaultfor Option),Int.modis Euclidean and returnsResult<Int, String>. PlusTuple<A, B>is the type spelling, paren stays for value/pattern. Skill is the source of truth fortools/website/llms.txt(regenerated viatools/website/build_llms.sh).
What was never used is no longer in the way.
Map.empty()— use{}.Int.parse,Float.parse,Int.rem— unreachable aliases.Int.toString,Float.toString— useString.fromInt/String.fromFloat.Float.toInt,Int.toFloat— useInt.fromFloat/Float.fromInt.String.concat(a, b)— usea + b. Convention:Target.fromSourcefor conversions, literals for literals, operators for composition, interpolation"{x}"for rendering.
Vector.toList(v)→List.fromVector(v).
- Structural eq + commutative hash for
Map<K, V>under wasm-gc. Insertion-order-invariant, matches the VM. Works as map K / V, as record/sum field, and as list/vector element. - caller_fn i32-table replaces per-fn String-ref globals; one
global.getper call, zero alloc on the hot path. Synthetic__entry__()carries--exprrecordings end-to-end with no JS-side argument encoder.
Record/replay correctness across all three backends — and a tidier wasm-gc imports tree along the way.
- wasm-gc independent products (
?!/!) record/replay parity. Codegen now emitsenter_group/set_branch(i)/exit_grouphost calls around independent-product literals, so contained effects pick up the same(group_id, branch_path, effect_occurrence)tuple the VM annotates. Cross-backend replay (VM → wasm-gc and wasm-gc → VM) round-trips cleanly on?!programs. Previously wasm-gc traces were flat and group-tagged VM recordings broke under wasm-gc replay. - Self-host real output value comparison. Self-host's CLI plumbing (
runFromFileWithRest/runCliFile/runGuestCliProgram/finishCliRun) now propagates the usermain()'sValup to the wrapping replay scope instead of dropping it to Unit, and the replay-template runtime emits a__aver_return__:stdout marker for the host to parse.aver replay --self-hostnow reports a realMATCH/DIFFERSinstead of always claiming MATCH. - Playground record/replay runs natively under V8 wasm-gc. Trace capture and replay used to bounce through the VM-in-wasm32 bridge; the playground now compiles user source to wasm-gc bytes and drives
--record/--replayon a WebWorker via a JS-sideEffectReplayStatemirror of the CLI host. Trace JSON is byte-compatible withaver run --record, so a downloaded.replay.jsonfrom the playground replays under the CLI replayer (and vice versa). Independent-product (?!) markers are wired so cross-backend traces match end-to-end.--exprper-fn recordings (add(7, 35)) ride the same path: the compiler injects a synthetic__entry__()fn that wraps the call with literal args,_startis wired through it, and the recording'sentry_fnreflects the user-facing target — no JS-side argument encoder, no VM-in-wasm32 fallback. caller_fnstamped on every recorded effect under wasm-gc. Trace events now carry the originating Aver fn name ("caller_fn": "renderRoom"instead of the universal"main"); the playground trace panel and CLI dumps show real per-function labels. One shared global per effect-emitting fn, init at instantiation — hot path is a singleglobal.getper call, zero alloc.- Playground compiler trimmed of dead VM-in-wasm32 paths. Two cuts: the legacy NaN-boxed wasm32 emitter (
codegen::wasm, gated behind a newwasm-legacyCargo feature that the CLI'swasmfeature pulls in butplaygrounddoes not — so--target wasm/--bridge {wasip1,fetch}keep working on the CLI), and the unusedaver_run_record/aver_run_record_entry/aver_replay_runwasm-bindgen bindings plus theirrun_record_project*/replay_run_projectRust hosts (record/replay now runs natively under V8 wasm-gc on a WebWorker — see the playground bullet above).aver_bg.wasmshrinks 4811 KiB → 4556 KiB afterwasm-opt -Oz(-255 KiB / -5.3% on first-load).
run_wasm_gc/imports.rssplit into 13 per-domain submodules (args.rs/console.rs/disk.rs/env.rs/factories.rs/groups.rs/http.rs/lm.rs/numeric.rs/replay_glue.rs/tcp.rs/terminal.rs/time.rs). The 1711-line dispatch monolith is now a 101-line chain that hands off to per-namespacedispatch(...)functions; new effects live next to their decoders and factories.
aver run --wasm-gc --record <dir>+aver replay <recording.json> --wasm-gc— wasm-gc joins the VM and self-host as a first-class record/replay backend. Replay short-circuits real I/O on every effect, compares the entry-fn return against the recordedoutputwith the sameMATCH/DIFFERSshape the VM replayer prints. Recordings are interchangeable across all three backends — a trace written by any one of them replays cleanly under any of the three.aver run --wasm-gc -e 'fn(args)' --record— record a specific function call instead ofmain, parity with the VM--exprflow. Literal args (Int / Float / Bool / String / Unit) are encoded intorecording.inputand re-fed at replay; the recordedoutputcarries the actual decoded return value.
Heat-treat the internals — every backend now agrees what
Console.printis, what's a type, and that engine GC beats hand-rolling.
Console.print/Console.error/Console.warn/Terminal.print/Terminal.setColornow takeString. The previous polymorphic signature (aPrintabletype variable that accepted any value and let the runtime stringify per-type) is gone. Stringification is the caller's job — use interpolation"{x}"for primitives, write a per-type render helper for compound shapes. Programs that calledConsole.print(record)/Console.print(some_list)will fail typecheck withArgument 1 of 'Console.print': expected String, got <Type>and the message points at the call site to migrate. Every backend (VM, legacy wasm, wasm-gc, Rust, self-host) loses its per-type format dispatch; replay/record stores plain string bytes per call; the effect ABI becomes trivial across hosts.
--target wasm-gc— native WebAssembly GC + tail-call output, the recommended WASM target. No NaN-boxing, no boundary GC framing, no inline runtime. Per-instantiation monomorphisation forVector<T>,List<T>,Map<K, V>,Option<T>,Result<T, E>,Tuple<A, B>(incl. nested paren tuples like(Int, (Int, Int))). Full Aver type parity: tuples, cross-collection nesting, sum-type and record map keys, all 12Terminal.*effects, multi-binding variant patterns,?/?!/ Result-Unit / Tuple-Unit shapes, generic constructors driven by bidirectional inference. Wasm-gc wins V8 microbenches on alloc / recursion / collection workloads (vector_ops 269×, map_build 5.67×, record_access 3.37×);--optimize sizewins 19-32 % across the example games.aver run --wasm-gc— embedded wasmtime executor with engine GC + tail calls. Full effect surface wired againstaver_rt::*: Args, Console (incl.readLine), Time, Random, Float math, Terminal, Disk (read/write/append/exists/delete/listDir/makeDir), Env, Tcp (connect/writeLine/readLine/close/send/ping), Http (get/head/delete/post/put/patch). Multi-module entries auto-resolve their module root from the entry file'sdepends [...], soaver run --wasm-gc projects/payment_ops/main.avworks withoutcdor--module-root. Output matches the VM byte-for-byte modulo time/randomness on every audited core / data / games / services / apps / modules / playground sample.- Browser playground migrated to
--target wasm-gc. Engine GC replaces theaver_runtime.wasmsidecar; binaries are self-contained. Modern-browser baseline (Chrome 119+ / Firefox 120+ / Safari 18.2+). --target wasm-gc --optimize size— wasm-opt pipeline passes--enable-gc --enable-reference-types, factory exports + LM transport survivewasm-metadce, per-program binaries shrink 19-32 %.--preset cloudflaremigrated to wasm-gc. Expands to--target wasm-gc --pack cloudflareand requires--handler <fn>. Smaller wasm + smallerworker.jsthan the legacy--target wasmbundle, nowasm-mergedependency.
- Soundier internals across the type system.
Type::Unknown/Type::Anyremoved from the static type enum: generic builtin positions use namedType::Var(K|V|T|E)resolved per call site bymatch_expected_typesubstitution; error recovery usesType::Invalid(never compatible with concrete types). Bidirectional inference drives expected types into generic constructors and collection literals —Map.empty,Option.None,Result.Ok/Err, empty list literals all type without backend recovery shims, including in tail-call argument position.Char.fromCodeaccepts the full Unicode range (Doom Braille, etc.). - Resolver is arm-scoped. Pattern bindings live per arm (
MatchArm.binding_slots) instead of a function-flat name → slot map. Two arms can share a binding name with different field types —recordedAt(event: TaskEvent)reusesatacrossTaskCreated.at: String/CommentAdded(whereatprojects from a record),serializeTaskEventreusesdeadlineacrossTaskCreated.deadline: Option<String>andDeadlineSet.deadline: String— and each arm's slot is allocated independently. The resolver runs as a single-pass walk with a scope stack so ident lookup respects shadowing. - Slot types lifted into IR as
FnResolution.local_slot_types— every backend that needs typed locals consumes the same table instead of re-walking patterns. Sibling sumtypes sharing a bare variant name (e.g.Query.ProviderSummaryvsQueryOutput.ProviderSummaryin payment_ops) are disambiguated by(parent, bare)keys throughout resolver and wasm-gc registry. Multi-arm tuple-of-Constructors matches and chained-Attr value access (Domain.Types.TaskStatus.Blocked) flatten through one shared rewrite path used by both VM and wasm-gc.
- Of the 71 single-file or entry-point examples + project mains, 68 run identically to the VM under
aver run --wasm-gc(everyexamples/{core, data, games, formal, wasm, services, apps, modules}andprojects/*plus 13 of 14 playground samples). The remaining 3 are intentionally bad code that show compile errors (examples/diagnostics/{lint_demo, test_errors}.av,tools/website/playground/sources/examples/effect-violation.av).
- Per-version runtime artifacts (
tools/website/runtime/,averlang.dev/runtime/). Wasm-gc binaries are self-contained — there's no sharedaver_runtime.wasmto fetch and cache, so the versioned CDN tree, thelatest/pointer, and therelease.pybuilder that emitted them all go. The legacy--target wasmbundle inlines its own runtime viawasm-mergeand doesn't rely on the CDN either. --bridgerejected under--target wasm-gc. The legacy--bridge fetch | wasip1 | noneaxis is bound to the legacy NaN-boxed backend. Mixing it with wasm-gc was previously silently ignored — now the compiler errors with a hint pointing at the correct shape:--handler <fn>(and--preset cloudflare --handler <fn>) for HTTP, the planned--target wasip2for standalone-WASI deployments. wasm-gc skips preview 1 by design — porting the legacyaver_to_wasi.wasmshim to GC string types would re-implement an ABI we want to leave behind.
--target wasip2(Component Model output,wasi:http/proxy+wasi:filesystem+wasi:sockets) is the modern wasm-gc companion for standalone-runtime deployments. Lands in 0.17. Once shipped,Http.*/HttpServer.listen/Disk.*/Tcp.*all become ✅ on the standalone-WASI side instead of stubbing.
Every pipeline pass exposes its decisions through one typed shape; bench reports gain the metrics that make regressions catchable; CI gates them per-host without per-runner branching.
aver compile --explain-passes [--json]— typed per-pass diagnostic report (TCO conversions, interpolations lowered, fusion sites + sinks fired, slots resolved, alloc/recursion facts). JSON shape is one{stage, data}block per pass with stage-specific fields;schema_version: 1pins the contract for CI gates (tests/explain_passes_spec.rs).aver benchpopulatescompiler_visible_allocs(IR-level viaNeutralAllocPolicy, same across all three targets) andresponse_bytes(vm: rendered return value viaaver_display;wasm-local:fd_writeiovec sum;rust: subprocess stdout).--comparegates both as exact-match alongside p50/p95.aver bench --baseline-dir DIRauto-picks<host.os>-<host.arch>-<backend.name>.json; silent skip when no match. Directory mode supports--save-baseline(NDJSON, same shape--baseline-dirreads) —bench/baselines/macos-aarch64-vm.jsonships as the reference.- GitHub Actions
Bench Gatejob runsaver bench bench/scenarios/ --target=vm --baseline-dir bench/baselines/ --fail-on-regressionon every PR; results upload as 30-day artifact.
PipelineResult.buffer_buildis now a typedBufferBuildPassReport(sinks, synthesized fns, per-sink rewrite counts) instead of an opaque(usize, usize)tuple.aver checkno longer prints the↻ N buffer-build sink(s) […]summary — same data with richer detail is now inaver compile --explain-passes.
The compiler grew a real pipeline. Every IR transform is a named ordered stage, every consumer reads derived facts from one place, every backend's relationship to the IR is a documented contract.
- Ordered 7-stage pipeline (
tco → typecheck → interp_lower → buffer_build → resolve → last_use → analyze) as the single source of truth, with per-stage public functions and per-stagePipelineConfigflags. Analyzestage centralises five ad-hoc IR analyses (alloc info, mutual-TCO membership, recursive fns, callsite counts, body classification) into onePipelineResult.analysiscodegen reads from.LastUsestage split out of the resolver —<name:last>markers in IR dump show which slots VM/Rust/WASM MOVE instead of COPY.aver compile --emit-ir-after=PASSprints the IR snapshot after any pipeline stage;diff -ubetween two stages shows exactly what each pass rewrote.aver benchscenario harness with three targets (vm/wasm-local/rust), TOML manifests, NDJSON for batch runs,--save-baseline/--compare/--fail-on-regression, andbackend+hostidentity blocks in every report. Thirteen scenarios shared withcargo benchviainclude_str!. See docs/bench.md.
- Proof export traversal leak. Dep modules ignored the proof-export flag and ran deforestation anyway, leaking synthesized
<fn>__bufferedvariants into Lean/Dafny output. - Playground bypassed deforestation. Browser-compiled WASM/Rust artifacts were measurably slower than equivalent CLI compiles. All playground compile / replay / record paths now use the canonical pipeline.
PipelineConfigis per-stage booleans. No bundledapply_traversal_loweringknob;stop_afterremoved. Caller-level proof-vs-runtime distinction translates to per-stage flags at the CLI boundary.pipeline::*is the single entry point for IR transforms and analyses — direct calls totco::transform_program/resolver::resolve_program/run_type_check_fulletc. are gone from production and tests.- Codegen consumers read derived facts from
PipelineResult.analysis/CodegenContextinstead of recomputing. Aver's module DAG makes per-module unions sound (cross-module SCCs are mathematically impossible — pinned insrc/ir/analyze.rsdoc and memory). - VM compile API consolidated. All three entry points take
analysis: Option<&AnalysisResult>as a peer parameter; no_and_analysisvariant.
- WASM and Rust codegen no longer handle
Expr::InterpolatedStr—interp_loweris now a mandatory predecessor stage. ~100 lines of dead code + helper enums deleted, replaced withunreachable!()+ contract comments. - VM keeps its
compile_interpolated_str— the REPL is the only legitimate consumer of pre-lower IR.
The compiler now eliminates intermediate list traversals it can prove are consumed once —
String.joinbuilders, interpolation chains, and external-reverse pipelines fuse to direct buffer writes across all three execution backends.
- Buffer-build deforestation across VM, WASM, and Rust.
String.join(<recursive_prepend_builder>(args, []), sep)no longer materialises the intermediateList<String>. The IR-level pass detects the canonical sink shape (match cond { true -> List.reverse(acc); false -> recurse(... List.prepend(elem, acc)) }), synthesizes a<sink>__bufferedvariant that threads a mutableBufferthrough the tail-call chain, and rewrites matching call sites to__buf_finalize(<sink>__buffered(args.., __buf_new(8192), sep)). Each backend then lowers the four__buf_*intrinsics: VM dispatches to dedicated bytecode opcodes (BUFFER_NEW/APPEND_STR/APPEND_SEP_UNLESS_FIRST/FINALIZE) backed by aVec<Option<String>>pool on the VM struct; Rust usesString::with_capacity+push_str(withBuffer = Stringaliased inaver-rt); WASM uses anOBJ_BUFFERheap object andrt_buffer_*runtime helpers. Local benches: 8× speedup on Rust (buildLines(500_000, []) → String.join), 2.7× on VM, 24% on WASM under workerd. - String interpolation lowers through the same buffer pipeline.
"a${x}b${y}c"no longer chainsstr_concatcalls — every interpolation goes through a new IR pass (src/ir/interp_lower.rs) that desugars to__buf_finalize(__buf_append(... __buf_new(N), __to_str(x))). Coercion of non-string parts uses a new__to_strintrinsic (VM:CONCATagainst empty, Rust:aver_rt::aver_display, WASM: existingemit_value_to_strhost bridge). Universal speedup — every Aver program with f-strings benefits, not just the niche of recursiveString.joinbuilders. Old O(N²) chained-concat shape (each step allocates a string of cumulative length) becomes O(N + total_len) buffer-write. - External-reverse builder shape. The buffer-build detector now also matches sinks shaped
match list { [] -> acc; [head, ..rest] -> recurse(rest, List.prepend(elem, acc)) }— common in payment_ops / workflow_engine codebases under the*Intonaming convention (serializeEntriesInto,filterSubjectInto,renderEventItemsInto). Call sites spelledString.join(List.reverse(<sink>(args, [])), sep)fuse against these. Per-shape kind alignment + acc-position empty-list precondition checked the same way in detection and rewrite, soaver checkcan't over-report sites the rewrite would refuse. - Antipattern lints (
aver checktraversal warnings). Three patterns the deforestation pass deliberately doesn't fuse get surfaced as warnings instead — the fuse-vs-warn split says: silently fuse only when Aver has no idiomatic alternative; warn when it does. Flagged:Vector.fromList(<sink>(args, []))(suggestVector.new(N, default)+Option.withDefault(Vector.set(v, i, x), v)owned-mutate fast path),Map.fromList(<sink>(args, []))(suggestMap.empty()+Map.setchain), and standaloneList.reverse(<sink>(args, []))whose result isn't fed straight into aString.join(the fusion path covers that case; the lint catches sites where the wrapper is something else likerenderLinesand fusion can't chain through). Acc-position-empty-list precondition checked the same way the rewrite checks it, so the warning never offers broken advice.
- Dead HAMT pointer walk in WASM boundary GC. The HAMT runtime was replaced by the flat
OBJ_MAP(kind=12) earlier, but the kind=13/15 pointer-walks forHAMT_NODE/HAMT_COLLISIONstayed incollect_endandretain_i32. They never fired on main because no live object used those kinds — but Phase 2c reuses kind=13 forOBJ_BUFFER, and the stale walk was treating buffer payload bytes as inner pointers, callingrt_retain_i32/rt_rebase_i32on garbage. On wasmtime the pseudo-pointers happened to land outside the collect range and were silently passed through; on Cloudflare Workers v8 the layout differed and they corrupted memory mid-collect, surfacing asaver_http_handle threw: RuntimeError: memory access out of bounds. Removing the dead walks fixes it. Result.withDefault/Option.withDefault/Option.toResultfirst-arg validation. All three combinators were registered withType::Unknownparameters insrc/types/checker/builtins.rs, soResult.withDefault(Vector.get(v, i), 0)(Vector.get returnsOption<T>, notResult<T, E>) compiled cleanly throughaver checkand silently returned the default at runtime — every lookup folded to the fallback value with no error surfaced. The special-case handlers ininfer/expr.rsalready had the right return-type inference but skipped argument validation; they now emit a real type error when the first argument is not the expected wrapper.Type::Unknownstill flows through as escape hatch for genuinely polymorphic returns.HttpServer.listenWithcontext-handler type linkage. The second argument (user-defined context) and the handler's first parameter must share the same type — in theweather.avstyle, both areWeatherContext. The builtin sig leaves context asType::Unknown(Aver builtins don't carry parametric polymorphism), so the linkage is enforced as a cross-arg check after the standard sig-based validation: extract the handler'sType::Fnfirst param, compare with the inferred type of the context arg, emit a type error on mismatch. Same logic coversSelfHostRuntime.httpServerListenWith.SelfHostRuntime.httpServerListen{,With}handler typing. Both aliases declared their handler parameter asType::Unknown, so callers could pass anIntwhere aFn(HttpRequest) -> HttpResponsewas expected and the compile would proceed past type-check. Tightened tohttp_handler()/http_handler_with_context()to mirror the publicHttpServer.listen{,With}shape.
- Pure no-alloc fast path on both WASM and VM. New shared
compute_alloc_infoanalyzer (src/ir/alloc_info.rs) parametrised by a backend-specificAllocPolicytrait classifies every user fn as allocating or not, walking the call graph to fixpoint. The WASM emitter then skips the prologueheap_ptrsave and epiloguert_truncatefor no-alloc bodies; the mutual-TCO trampoline drops its watermark check, per-iter heap_ptr save, and adaptive compaction when every member is no-alloc. The VM compiler tags qualifying chunks soTAIL_CALL_KNOWN/TAIL_CALL_SELFskipfinalize_frame_locals_for_tail_call,CALL_KNOWNparks dummy zero marks, andRETURNshort-circuits the standard fast-return path. Mandelbrot bench (160×96 × 100 reps × 80 iter, mutual-TCOmandelStep ↔ mandelIterwith cardioid + period-2 bulb pre-tests) runs ~4× faster on WASM and ~22% faster on VM. Closes the bulk of the float-tight-loop gap with peer typed-wasm + Cranelift toolchains (down from ~3× slower to ~1.07× slower, apples-to-apples). HttpRequest.query: Stringfield for the fetch bridge.req.queryreturns the URL search string without the leading?(previously dropped silently —req.bodyworked butreq.querywas simply absent from the builtin record). Plumbed throughbuiltin_records.rs,abi.rs(Request.query→request_query), the fetch-bridge field-emit map, the cloudflareworker.jstemplate, and the type-checker's net-request field list. Enables URL-driven server-side state without an extra parser layer.- Friendly compile-time error for HTTP types under non-fetch bridges.
aver compile … --target wasm(no bridge) on a program declaringfn handler(req: HttpRequest) -> HttpResponsepreviously produced invalid bytecode (type mismatch: expected i64, found i32deep in the record-shape codegen). The emitter now rejects HttpRequest/HttpResponse parameters/returns under any non-fetch adapter before emit, naming the offending fn and pointing at--preset cloudflare/--bridge fetch. /fractalonedge.averlang.devbecomes interactive. Replaces the previous static Braille pair with a single-panel 200×120 half-block Mandelbrot driven by URL query params (cx,cy,w). Per-pixel 16-step colour palette (each cell<i c=XX>▀</i>packs top/bot palette indices into one 2-hex attribute, ~38% smaller HTML than per-cell two-class form). Pan / zoom / reset buttons + six landmark presets (seahorse, mini-Mandelbrot, double spiral, elephant valley, tendrils, eastern bulb) all encode their target view in the URL — every navigation fires a fresh handler invocation withcache-control: no-store, no precomputed images. Iter cap auto-scales (100 / 150 / 250) so deep zooms stay sharp at modest extra compute. Production warm TTFB ~35-40 ms (keep-alive); first hit ~70 ms with TLS handshake.
- WASM mutual-TCO trampoline collapses to a flat shared slot row when every member has an identical typed signature and is no-alloc (
mandelStep ↔ mandelIterstyle). Per-tail-call args reshape skips itseval → tmp → targetdouble-copy under those conditions and writes args directly into target slots in reverse stack order. Eliminates ~10local.setper iter from the trampoline hot loop.
docs/wasm.md— reframedaver.tomlpolicy as a deliberate host concern, not a missing feature. New "Policy is the Host's Job" section cites wasmtime/WASI--allow-net, Cloudflare Workersservices/fetchbindings, browser CSPconnect-src, and Fastly Compute backend whitelists as the actual enforcement layers; build-time concerns (effect declarations, deterministic mocks, independence invariants) stay with us.
tools/release.py— GitHub release titles now carry the codename for thematic releases (Aver 0.14.0 "Edge"); patches stay plain. Cascade-bump logic tightened to fire only for genuine publish-blockers (aver-rt → aver-memory);aver-lspno longer auto-bumps just becauseaver-langdoes.
Headerrecord. 0.14 standardised HTTP headers asMap<String, List<String>>across every backend; the per-entryHeader { name, value }record left over from the oldList<Header>shape was unreachable from any built-in HTTP type and is now retired. User code that constructedHeaderliterals must now build aMap<String, List<String>>directly.
Http.post/Http.put/Http.patchoracle signature now reports the headers parameter asMap<String, List<String>>instead of the staleList<Header>from the pre-0.14 shape. Verify-mode mocks and effect classification see the correct type.- Self-host codegen template (
--with-self-host-support) emittedheaders: AverList<Header>for HTTP request/response handling. Now emitsheaders: HttpHeaders(=AverMap<AverStr, AverList<AverStr>>), matching the runtime shape every other backend already used.
docs/services.md:Http.*signatures andHttpRequest/HttpResponserecords updated to theMap<String, List<String>>headers shape.
Aver's WASM backend becomes a deployable edge target: small user modules, a shared runtime, and explicit host bridges.
--target edge-wasmemits a thinuser.wasmthat imports a separately hostedaver_runtime, so browser and edge deployments can cache the runtime once.- Host bridges:
--bridge fetchfor JS/Workers-style hosts and--bridge wasip1for standalone WASI preview 1 execution. - Cloudflare Workers pack output (
--preset cloudflare) — dropsworker.js(ES-module bootstrap that wiresaver/*host imports againstconsole.*/Date.now()/Math.random()/ Fetch + JSPI forHttp.*) andwrangler.tomlnext to a single bundleduser.wasm. Cloudflare Workers rejectWebAssembly.instantiate(bytes, …)from runtime-fetched bytes, so the preset uses--target wasm(runtime inlined viawasm-merge) for static-import shape;--target edge-wasmstays for browsers / Deno / Bun where the runtime CDN ataverlang.dev/runtime/works. Runtime artifacts, checksums, and manifest are published independently under/runtime/. - WASM host coverage for
Env.*,Console.warn,Http.*, request/response headers, and multi-value header flow. - WASM
Mapruntime — flat hashtable with structural hashing/equality across all hashable key types (Int, Float, Bool, String, heap), O(1)Map.len, owned-mutate fast path onMap.setwhen last-use analysis proves sole ownership.map build 5kbench: WASM 963 µs vs VM 1.33 ms. Replaces the prior placeholder linked-list shape that was O(N²) on TCO build patterns. --optimize size|speedfor the WASM optimization pipeline.- Runtime artifacts at
/runtime/:aver_runtime.wasm,aver_to_wasi.wasm, WAT companions, checksums, versioned URLs, andlatest/.
- HTTP headers are
Map<String, List<String>>across request/response records andHttp.post/Http.put/Http.patch. - WASM map ABI is polymorphic, with key kind and value pointer flags passed explicitly.
- WAT is the source of truth for the standalone runtime, and emit-time WASM validation is part of the compile path.
- WASM
Vector.setis O(1) in TCO build loops via owned-mutate dispatch — fusedOption.withDefault(Vector.set(v, i, x), v)wherevis a last-use slot lowers to an inline bounds check +i64.store. Same trick the VM uses; matches its perf profile onvector get/set 5k.
--adapter→--bridgeand--wasm-opt oz|o3→--optimize size|speed.--target watand--target wasm+wat; use standard WASM tooling for WAT output.
- WASM
Map<Int|Float|Bool, V>validation and lookup issues. - VM map structural equality for heap keys.
- Cloudflare/fetch bridge response headers no longer get dropped.
Http.*under--bridge wasip1returns a transport error; real WASI HTTP belongs in a later Component Model target.
Pure core. Explicit shell. Auditable boundary. Aver learns to say no — at the module's edge, to extra calls, to hostile worlds.
aver verify --hostile/aver audit --hostile— runs everyverify <fn> law <name>block under an adversarial expansion of itsgivenclauses. Typed value domains (given x: Int = [3]) get augmented with the per-type boundary set (0,1,-1,i64::MIN,i64::MAX; forFloatadd±InfandNaN; forStradd empty / NUL-embedded / multi-byte / 1024-char). On top of that, classified effects get multiplied by adversarial profiles (frozen / fast-forward / backward clocks, always-min / always-max / alternating random, network-down responses, empty / always-error filesystem, …) —givenfor an effect is just the worlds you listed, hostile mode adds more on top, andlawform already quantifies universally over every stub. Failures that surface only here mean the law isn't universal — either weaken it withwhen <precondition>to scope it to the worlds where it actually holds, downgrade fromlawtoverify <fn>cases-form when the claim is really stub-specific, or accept the profile as a real production world the impl should handle.- Module-level effect boundary —
effects [...]declaration on the module header. Every function's! [Effect]must be covered by the module's declared surface, enforced at type-check. A namespace entry likeDiskadmits anyDisk.*method; method-levelDisk.readTextadmits only that one. Modules with functions but noeffects [...]get a soft warning nudging them to declare the boundary; you opt in when you want the enforcement. whenover oracle stubs (oracle assumptions). Inverify <fn> trace law <name>, thewhenpredicate may now reference an effect-given oracle directly:when clock(BranchPath.Root, 1) > clock(BranchPath.Root, 0)declares "this law assumes a monotonic clock". Hostile profiles that violate the assumption are skipped; profiles that respect it run normally. Read-your-writes, conservation, idempotent fetch, protocol order — invariants you used to leave in comments now live in source. Skipped count is shown alongside passed / failed; if every adversarial profile is skipped, you get a vacuous-under-hostile warning so the law isn't silently uncovered..trace.count(method)— quantitative trace assertion. Returns the number of trace events whose method matches the argument (Console.print,Http.get(...), etc.). Complements.contains(boolean any-match) with the count form so laws can pin "this fn calls the API exactly once" or "no extra disk reads under hostile profiles".verify-hostile-mismatchdiagnostic slug — distinct from the regularverify-mismatch, so CI gates can route declared-world regressions and adversarial-world surfacings to separate channels:jq '.diagnostics[] | select(.slug == "verify-hostile-mismatch")'. Each carriesfrom_hostile: trueand anoriginfield with the profile label (hostile effect profile: Time.unixMs/saturated,hostile boundary expansion).- Oracle invariants in proof export — documented and enforced. The trust header in every
aver proof --backend lean/--backend dafnyartifact spells out the per-effect bounds the runtime guarantees:Random.intstays in[min, max],Random.floatin[0.0, 1.0],Time.unixMs ≥ 0,Disk.existsis total. Lifted theorems quantify over subtype-encoded oracle types (RandomIntInBounds,RandomFloatInUnit,TimeUnixMsNonneg) — the bound is a constrained quantifier at the lemma level, not a free side-condition you have to discharge separately. - Playground hostile toggle — checkbox next to the Audit button. When checked, audit runs the hostile pipeline; the Verify panel and the structured JSON output carry the same dual-run breakdown (declared vs hostile, with the per-block scorecard).
- Showcase example: hostile clock.
examples/formal/hostile_clock.av(and the Oracle dropdown in the playground): a deadline check that passes under real time and breaks under the saturated-clock profile — concrete demo of what--hostilefinds that plainverifymisses. - Dual-run breakdown in TTY and JSON. Per-block summary line shows
(1/1 declared, 11/35 hostile, 3 skipped bywhen)so you can see at a glance whether a regression is in the declared world or only surfaces under adversarial profiles. JSON output adds four counters onverify_summary.blocks[]—declared_passed,declared_failed,hostile_passed,hostile_failed— for tooling that wants to split "law regression" from "hostile coverage gap". - Cartesian cap. Hostile expansion stops at 10,000 cases per block (same ceiling the parser uses for declared
givenranges). Over-budget blocks get a clear error pointing at the law and listing the projected size; tighten thegiven, add awhen, or run that block without--hostile.
Multi-module Aver projects export to Lean and Dafny end-to-end. Generated proofs shrank by ~85% on pure-math examples. All 42 canonical examples now pass both proof backends.
- Multi-module proof export. Projects with
depends [...]produce one.lean/.dfyfile per module, plus a sharedAverCommonlibrary carrying only the helpers your code actually references. Submodule paths likeModels.Userland atModels/User.{lean,dfy}. Works on rogue, doom, tetris, checkers, notepad — every multi-module example in the repo, including those mixing same-named fns across modules and modules sharing a name with a record. ?operator now works in pure proof export. Previouslycmd = parseCommand(x)?; <rest>produced uncompilable proof code; now correctly lowers tomatch … { Result.Ok(cmd) -> <rest>; Result.Err(e) -> Result.Err(e) }before reaching Lean / Dafny. Fixes mission_control.- Concrete
Floatevaluation in Lean proofs.Float.floor,Float.round,Float.ceil,Float.toIntnow match the runtime exactly (IEEE 754 + saturating cast, including NaN → 0 and ±∞ → i64 bounds).verifycases over Float values get realnative_decideevaluation in Lean instead of opaque stubs. Edge-case behaviour is asserted bynative_decideproofs that re-run on every Lake build. Float.sin,Float.cos,Float.atan2are now proof-exportable across both backends, not just runtime. doom's raycaster compiles to Lean / Dafny.Terminal.sizeis now a verifiable effect. Oracle signature() -> Terminal.Size, same shape asArgs.get/Env.get. Example:examples/formal/terminal_size_snapshot.av.- Playground download menu. The ⬇ button now expands into Aver source / WASM binary / Rust project / Lean 4 proof / Dafny proof — your in-browser source compiles and ships as
.av/.wasm/.zip.
- Generated proof files shrank dramatically. Lean and Dafny now emit only the helpers, records, and trust-assumption headers your code actually references — no more 1500-line preamble for a 20-line pure-math file. Calculator and pure-laws examples shrank ~85-90% in Lean, ~70-90% in Dafny; effectful examples 25-40%.
- Shared backend infrastructure. Built-in records, helpers, and per-module emission logic live in single shared modules consumed by Lean / Dafny / WASM. Adding a new built-in record shape is one line in
codegen::builtin_records. aver contextoutput is denser (schema_version: 7, breaking). Markdown signatures now use the Aver source formname(args) -> Ret ! [Effects]instead of a separateeffects:line. JSONrecords.fieldsandtypes.variantsare pre-formatted strings (["id: String", ...],["IngestWebhooks(String, String)", ...]) instead of nested objects, and arrays are rendered inline ([a, b, c]) while objects stay multi-line. Net effect on the showcase projects: workflow_engine fits 78/131 elements at 24kb (was 47/131); payment_ops fits 98/98 at 32kb with room to spare. Anything parsing CONTEXT.json should switch fromfields[i].name/fields[i].typeaccess tosplit(": ")on the string.
Effectful functions get verified now — bind an oracle with
given, check the trace, or export the universal law to Lean & Dafny. Dafny caught up with Lean across every recursive shape.
- Oracle law and trace docs/example —
docs/oracle.mdnow separates proof-orientedverify <fn> lawover explicit oracles from cases-formverify <fn> tracefor.result/.trace.*assertions. Addedexamples/formal/oracle_trace.avas the short runnable example. - Broader Oracle effect classification — Oracle now covers CLI input (
Console.readLine), disk operation/result effects, one-shot TCP (Tcp.send/Tcp.ping),Time.sleep, and terminal trace/input calls that do not depend on modal terminal state. - Dafny proof backend reaches feature parity with Lean on recursion. Shared recursion classifier (
codegen::recursion::detect) + AST transform now feed both backends. Dafny emits mutual-recursion SCCs as fuel-guardedfunction fn__fuel(fuel: nat, …) decreases fuel { … }pairs with plan-specific metrics, parallel to Lean'sdef fn__fuel (fuel : Nat) …. Shapes that admit no total default or use?that doesn't lower fall back tofunction {:axiom}— the Dafny analogue of Lean'spartial def. Lemmas over opaque fns short-circuit toassume {:axiom} <ensures>;, the Dafny analogue of Lean'ssorry. Across the 23 canonical examples: 12 are clean on both backends (full proof), 9 have matching proof gaps (Dafny axiom/assume vs. Lean sorry), 2 are pre-existing codegen gaps orthogonal to the recursion story.
- Effectful verification story — README, language guide, and proof-backend docs now distinguish Oracle verification for classified effects from record/replay for ambient state, persistent protocols, terminal modes, and server callbacks.
codegen::recursionmodule —RecursionPlanenum,ProofModeIssue, recursion classifier, andrewrite_recursive_calls_{body,expr}AST transform pulled out ofcodegen::leaninto the shared module. Lean and Dafny both consume it.RecursionPlan::IntAscendingnow holds the bound as an Aver AST node (Spanned<Expr>) instead of a Lean-rendered string — each backend renders it in its own idiom.
aver run --expr '<call>'/--input-file PATH— record or run any function, not justmain. Pass a call likeaver run src/tax.av -e 'loadTaxRate("PL")' --record dir/and the recording'sentry_fnandinputare populated from the call;aver replaypicks it up unchanged. Repeat-eto batch. Supports literal, list/tuple, and ADT-constructor arguments (Result.Ok(5),Shape.Circle(1.0), nested). Function calls / arithmetic / variables in arg position stay out of scope — wrap them in a helper function and call that instead. The same capability is exposed in the playground's Trace panel.
Tooling now shows its work — parse errors point at the exact token with a repair, audit runs three axes in one shot, and every program can be recorded & replayed in the browser.
aver audit— one command that runs static checks, verify blocks, and format compliance together. CI-friendly exit code,--jsonfor pipelines.aver format --check/aver format --check --json— non-mutating format verification. Every rewrite reports a structuredFormatViolationwith a stableruleslug:tab-indent,bad-function-header,effects-unsorted,effects-reshape,verify-misplaced,excess-blank,module-intent-reshape,decision-inline,trailing-whitespace,missing-final-newline. Agents and linters can key off specific rules instead of free-text diffs.- Naming convention checker — flags non-camelCase functions / fields and non-PascalCase types / modules / variants as stable diagnostics (
bad-fn-name,bad-type-name,bad-module-name,bad-variant-name,bad-field-name). Runs as part ofaver checkandaver audit. - Canonical diagnostic bundle across every CLI command —
aver check --json,aver verify --json,aver why --json,aver audit --json,aver format --check --jsonall emitAnalysisReportNDJSON now. One schema,schema_version: 1, documented indocs/diagnostics-schema.md. - Reworked browser playground — multi-file editor, interactive record & replay, full parity with CLI audit / why / context / format. Go play.
- Format engine rewritten around structured violations — the formatter used to report "needs format" as opaque before/after diffs. Every normalization pass now tracks per-line rule violations with original source-line numbers, so
--checkoutput points at the exact line that needs the specific fix instead of dumping a reformatted file.needs-formatstays as the aggregate marker; eachFormatViolationrides alongside with its rule slug. - Effects list in
aver context—signatureis now params + return type only; effects live on the siblingeffectsarray. Lets renderers show them without duplicating! [...]on screen.
- Parse errors landed on line 1:1 with no hint — the formatter stripped the real span and emitted a single red line that didn't tell you where or why. Now: real line/col pulled from the parser, source snippet with
^^^caret under the offending token (clamped to the last char for EOL errors like Unterminated string), and repair hints for common shapes (Expected '[' after '!'→! [Console.print, ...], missingmodule <Name>, map=>syntax, tuple-needs-2-elements, …). Time.sleepon wasm32-unknown-unknown panicked ("can't sleep") — the browser runtime now makes it a no-op; native builds keep real blocking sleep.
- Pre-compiled self-host —
--self-hostno longer generates Rust code and runscargo buildat runtime. The self-host interpreter is compiled as a[[bin]]target alongsideaver, socargo install aver-langprovides both binaries out of the box. No Rust toolchain needed at runtime. - Cargo package cleanup — published crate excludes
self_hosted/,examples/,tools/,editors/, etc. Onlysrc/and essentials ship. Self-host generated code lives insrc/self_host/.
- Release script (
tools/release.py) — automates version bumps, self-host regeneration, playground rebuild, crates.io publish, and GitHub release creation.
--self-hostcrash from crates.io install — the runtime codegen tried to readaver-rt/Cargo.tomlrelative to the installed crate source, which doesn't exist. Eliminated by removing runtime codegen entirely.- Rust codegen: TCO invariant hoisting of variant constructors — enum variant constructors (e.g.,
Val::ValStr) were hoisted as loop invariants, causing move errors in generated Rust. Now excluded alongside builtins.
- Runtime codegen pipeline for self-host (
build_self_host_binary, fingerprinting,cargo build --offlineinvocation).
- VECTOR_SET_OR_KEEP in-place mutation — fused
Option.withDefault(Vector.set(v, i, val), v)always has sole ownership; the opcode now mutates the vector directly at its arena slot. Vector get/set 5k: 17ms → 816µs (20× faster). - Skip promotion rewrite for bulk types without young refs — vectors/maps/tuples of inline ints skip O(n) per-element rewrite during young→yard promotion. Vector get/set 5k: 17ms → 12.6ms (−26%).
- VM arena in-place map rewrite — map promotion rewrites NanValue pairs in-place via
Rc::make_mutinstead of rebuilding the HashMap. Map build 5k: 24ms → 1.3ms (18.9×). COW ops inherit source allocation space, skipping redundant promotion.
- Unified symbol resolution for uppercase dotted paths —
classify_leaf_opnow classifies uppercaseExpr::Attrpaths (Option.None, variant constructors, module function refs) via three newLeafOpvariants instead of returningNone. Eliminates duplicate resolution logic from Rust and WASM backends. WASM backend now routesExpr::Attrthrough the shared IR layer.
- WASM
Option.toResultbuiltin —Some(v) → Ok(v),None → Err(err_value).
- WASM cross-module variant constructor resolution — hierarchical module paths like
Domain.Types.TaskStatus.Blockednow resolve correctly (previously only single-level bases worked). - Rust codegen: TCO invariant hoisting of builtin callees —
List.prependand similar builtin namespace refs were hoisted as standalone value expressions, generating invalid Rust (List.prepend). Now excluded from hoisting since they're compile-time constants.
- Dead
entry_has_young_refsfunction.
- Shared symbol layer — module visibility, type registration, and symbol resolution now go through a single shared layer. All backends consume the same
ModuleExportsandSymbolRegistryinstead of building their own views. - Unified module loader —
load_module_tree()replaces independent loaders in the VM compiler and type checker. Proper circular-import detection and module-name validation everywhere.
- WASM codegen wrong types for private module helpers — dependency modules with private functions returning non-Int types (e.g.
padTworeturningString) caused invalid WASM. Codegen now has full signatures for all emitted functions. - Rust codegen missing
Arc::new()for cross-module recursive types — self-host binary failed to compile from a clean cache. Constructor boxed-position lookup now handles qualified names.
- Suffix-matching heuristic in VM type resolution
- Dual-key registration in type checker (replaced by alias-based lookup)
- Checker's
ModuleSigCacheand cycle-detection stack (handled by shared loader)
- Rust codegen crash with forwarded arguments —
aver compileandaver run --self-hostfailed on programs with forwarded local variables after the unified resolver change in 0.9.3.
- Map/Vector in-place mutation everywhere — collections are now mutated in-place whenever the variable is at its last use, not just inside tail-call loops. VM map build 50K entries: 39s → 0.9s (43× faster). Sequential
Map.setchains without recursion also benefit. - WASM handles large collections — map/list build with 50K+ entries no longer crashes. Previously overflowed at ~5450 entries.
- Unified variable resolution — all backends (VM, Rust codegen, WASM, Lean, Dafny) now share a single resolver and liveness analysis. ~675 lines of duplicated ownership logic removed.
rebuild_playground.py— now also rebuilds the in-browser compiler (aver_bg.wasm), not just game modules.
- Map.set not using fast path with computed keys —
Map.set(m, Int.toString(n), n)was incorrectly falling back to the slow clone path. Now correctly detected as in-place-safe.
- WASM mutual TCO memory leak — dead temporaries (HUD strings, record updates) accumulated across iterations in mutual tail-call loops because nested function calls masked garbage from the yard heuristic. Replaced per-iteration skip threshold with watermark-based adaptive compaction (triggers when garbage exceeds 16KB since last collection). Game of Life editor loop now stable at ~15KB instead of growing to 10+ MB.
$heap_ptrWASM export — modules export the bump allocator position for host-side memory inspection.- Playground memory display — live heap usage shown in status bar, throttled to 500ms updates.
- Playground rogue touch controls — added descend stairs (>) button.
llms.txtimprovements — expanded namespace signatures, explicit "do NOT exist" operator section, verify block rules, qualified constructor examples. Driven by vera-bench LLM evaluation data.
- Aver Playground — write and run Aver in the browser at averlang.dev. In-browser compiler, 7 playable games, source viewer, syntax highlighting.
- WASM variant equality — nullary variants (
Color.White == Color.White) now compare correctly. - WASM variant display — shows type names instead of
Variant#0. - WASM
Console.readLine— properly wrapped inResult.Ok, blocking via SharedArrayBuffer.
- VM is the default backend —
aver run,verify,replay, andreplnow use the bytecode VM directly. The--vmflag is no longer needed and has been removed.
- Tree-walking interpreter — ~7500 lines removed. The VM covers all use cases the interpreter handled.
- WASM backend —
aver compile --target wasmandaver run --wasm. Ownaver/*import ABI with--adapter wasifor standalone wasmtime. Works with built-in host, browser JS shim, or custom host. - Browser WASM runner —
tools/wasm-runner/with terminal canvas rendering and keyboard input. aver-memorycrate — standalone NaN-boxed value representation and arena allocator.
- Root-parallel checkers AI — the checkers example now uses independent products to score root moves in parallel.
- Self-host stability — running
self_hosted/main.avthrough the host interpreter and host VM is much more stable. Fixes cover qualified module calls, constructor pattern matching, resolver slot binding, and VM runtime aliases. - VM parallel map imports — child VMs in independent products no longer silently drop map contents, fixing incorrect fallback behaviour in self-host runs.
- Codegen: record field access — accessing a field of a borrowed record parameter in return position now emits the required
.clone(). - Codegen: memoized recursive functions — call sites for auto-memoized recursive functions now correctly pass arguments by reference, matching the generated function signature.
- Codegen: independent products with cancel mode —
?!expressions no longer produce invalidlet-in-expression Rust code when cancel mode is active. - VM: no-main programs — the VM now finishes silently when a program has no
fn main, consistent with the interpreter.
examples/apps/status_board.av— an offline terminal dashboard that makes independent-product pipeline overlap visible in the VM and compiled Rust backends.
- VM parallel child contexts — child VMs now rebase
MATCH_DISPATCH/MATCH_DISPATCH_CONSTinlineNanValuepayloads when building the static arena for independent products. This fixes crashes when a branch returns or matches on heap-backed constant strings. - VM regressions for child arenas — added coverage for direct child-VM calls, nested string interpolation, and full
CALL_PARexecution so this class of arena-rebasing bugs stays closed.
- Independent products (
?!/!) — a tuple followed by!is a product of independent computations.?!adds Result unwrapping — all must succeed or the first error propagates. Independence is structural: tuple elements cannot reference each other. Composes recursively, giving fan-out parallelism and pipeline overlap with no new language concepts. No async, no futures, no channels — just products and independence. See docs/independence.md. - Parallel execution — compiled Aver programs run
?!/!elements on separate threads. Two HTTP calls that each take 2 seconds complete in ~3 seconds, not 4. Recursive?!over a list fans out the entire tree. - Replay groups — effects inside a
?!/!product are order-independent in replay. Reordering independent code does not break recorded sessions. aver checkindependence hazards — branch-pair warnings for likely unsafe overlaps in independent products, includingConsole/Terminal,Tcp,HttpServer, and mutatingDisk/Http/Enveffects. Warnings can be suppressed via[[check.suppress]]with a mandatory reason.List.take/List.drop— list windowing helpers for bounded fan-out and batching patterns.aver why— justification coverage tracer. Scores every function as justified, partial, or unjustified based on description, verify blocks, and coverage.--verboseand--jsonoutput modes.[[check.suppress]]inaver.toml— suppress specific warnings with a mandatoryreason.
- Thread-safe runtime — all runtime types use atomic reference counting, enabling parallel execution of independent products.
- Structured replay diagnostics — per-error-type diagnostics with source locations. Args diff hint shown even without
--check-args. - Independent-product replay matching — replay now keys grouped effects by
branch_path + effect_occurrence + effect_type + effect_args, so nested and repeated effects replay deterministically across reordering. - VM independent products —
CALL_PARnow carries callable values rather than only statically resolved function ids, so aliases likef = foo; (f(x), f(y))!work. Branch VMs import a thin static arena plus per-branch inputs/outputs instead of cloning the whole parent heap. - Compiled Rust cancellation — generated Rust now uses cooperative cancel checkpoints for
?!inmode = "cancel"and keeps branch closures borrow-checker-safe.
aver verify --json— structured NDJSON output:block-resultper verify block,diagnosticper failure,summaryat end.aver verify --verbose— failure diagnostics with source snippets and full fields (given/law context for specs).aver replay --json— NDJSON output:replay-resultper recording,summaryat end.- Structured verify diagnostics — verify failures use the same diagnostic system as
aver check:fail[verify-mismatch],fail[verify-runtime-error],fail[verify-unexpected-err]withat:,block:,case:,expected:/actual:, source snippets with carets. Normal mode caps to 3 diagnostics per block.
- Inline variants (TAG 14) — single-field variants whose payload is a small int (±268M), bool, unit, or none are now NaN-boxed inline (8 bytes, zero arena allocation). Pattern matching and field extraction skip arena indirection entirely.
- Unified NDJSON format —
check,verify, andreplayall emit{"schema_version":1,"kind":"..."}envelope with summary events. - Verify output redesign — per-file grouping, one-line block summaries with failure type breakdown, streaming output. Skipped files (type errors) show count + hint about
--module-root. - Codegen: let-destructuring — single-arm irrefutable matches (
match x: (a, b) -> expr) now emitlet (a, b) = x; exprinstead of a full match block.
aver check --jsonno longer emits human-readable lines mixed with JSON.aver replayno longer duplicates "Replay:" prefix in error messages.aver run --verifynow shows file path and source snippets in failure diagnostics.
- Structured error messages —
aver checkshows source snippets, repair suggestions, and semantic error categories (type-mismatch,unused-binding,missing-verify). Use--verbosefor full context on warnings. - Unused binding warnings —
aver checkwarns on bindings that are defined but never used. Prefix with_to silence. aver check --json— structured JSON output for editor and CI integrations.Map<T, Unit>as set — Lean codegen emitsFinset T, Dafny emitsset<T>. See docs.- Common Pushback FAQ — docs/pushback.md covers frequent questions and objections about the language.
- Faster compiled code — generated Rust is significantly faster across all benchmarks: pattern matching -66%, maps -13%, records -14%, vectors -19%. The self-hosted interpreter is 7-25% faster depending on workload. Fused IR ops (
IntModOrDefault,ListIndexGet) eliminate intermediate allocations; codegen now skips unnecessary clones on Copy fields, drops&on numeric arithmetic, and matches borrowed params without cloning the subject. - LSP — Vector namespace completions, updated List members,
exposes opaquesupport in document symbols. - Editor highlighting — VSCode and Sublime grammars updated with all current namespaces and keywords.
- Aver formatter keeps medium effect lists on one line when they fit.
Console.error/Console.warnin self-hosted now route to stderr.--with-self-host-supportenforces guest-entry contract.
aver run --self-hostnow caches its generated helper per installed Aver/self-host build instead of per guestmodule_root, so switching projects no longer forces a rebuild.- Self-hosted guest
aver.tomlpolicy is now loaded at runtime from the guest module root and starts only at the guest boundary, matching scoped replay behavior. - Cold
--self-hostruns now print short progress messages while Aver generates and builds the cached helper. aver compilenow exposes runtime policy mode explicitly via--policy embed|runtime; plain codegen defaults toembed, while--with-replaydefaults toruntime.
aver run --self-hostno longer misclassifies qualified user module calls likeMap.generateMaporTime.fooas builtins just because they share a builtin namespace prefix. Self-hosted module programs such asexamples/games/roguenow execute correctly again.
Breaking: List.get and List.append removed. Use Vector for indexed access.
Vector<T>— indexed sequence with O(1) get/set. API:Vector.new,Vector.get,Vector.set,Vector.len,Vector.fromList,Vector.toList.Vector.setreturnsOption<Vector<T>>.- Mutual TCO in codegen — mutually recursive functions compiled to trampoline dispatch loops.
- Namespace effect shorthand —
! [Disk]covers allDisk.*effects. - Self-host CLI path —
aver run --self-hostandaver replay --self-hostnow run through the Aver-in-Aver interpreter compiled to a cached Rust binary. - Scoped generated replay runtime —
aver compile --with-replay --guest-entry <fn>emits replay support that starts record/replay andaver.tomlpolicy at an explicit guest boundary instead of the process boundary.
Listis now purely recursive —prepend,head,tail,concat,reverse,contains,find,any,zip. No indexed access.- Idiomatic pattern: build with
List.prepend→List.reverse→Vector.fromList(zero-copy on Flat lists). - Compiled projects use LTO +
codegen-units = 1for faster release builds. - Self-hosted interpreter ~1.5× faster (COW maps,
Rc<str>strings, Vector env). - Installed
avernow bundles theself_hosted/sources directly, soaver run --self-hostbootstraps its cached helper binary automatically without a separate self-host install step. - Generated Rust projects now target Rust 2024.
aver checkno longer suggests granular namespace effects on wrappers that also require the broad namespace transitively through a callee.benches/comparison_bench.rsnow measures the realaver run --self-hostCLI path instead of a stale standaloneaver-selfbinary from$HOME/.cargo/bin.
List.get,List.append.
Highlights:
- VM is 25–54% faster across benchmarks; interpreter-to-VM speed ratios improved from 5–7× to 7–13×.
- Added
aver run --profilefor opcode/function-level VM profiling. - Fixed several VM correctness and memory issues, including match fallthrough, deep-list return overflow, and request-local stable-space retention.
aver run --profile— VM execution profile with opcode counts, function stats, and opcode-pair analysis.- Game of Life example (
examples/games/life.av) with terminal visualization and FPS counter. - Self-hosted interpreter project in Aver (
self_hosted/).
- Added specialized VM handling for common unwrap/default, boolean branch, and fused-load patterns.
- Added frameless calls for small leaf functions to reduce hot-path call overhead.
- Bool
matchontrue/falsenow compiles to a direct conditional branch. - Refined VM value layout to reduce wrapper overhead and speed up dispatch.
Terminal.sizenow returns a record withwidth/heightfields instead of a tuple. Generated Rust requiresaver-rt >= 0.3.1.aver context --jsonnow usesserdeserialization.- Lean proof export now emits universal theorems with
sorrywhen auto-proof fails.
- Exhaustiveness checker hang on recursive sum types.
MATCH_DISPATCH_CONSTfallthrough causing infinite recursion in patterns likefib(n).- Arena stack overflow on deep list returns.
HttpServercallback stable-space retention across requests.- Lean export reserved-word conflict for
toString.
- Bytecode VM —
aver run --vmcompiles Aver to a stack-based bytecode VM with NaN-boxed values, region-based arena memory (young/yard/handoff/stable), dedicated list opcodes, structural persistent lists, and thin-function fast return paths. 5-9x faster than the tree-walking interpreter on compute-heavy workloads. - Terminal service —
Terminal.*namespace (12 methods) for raw-mode terminal I/O via crossterm: cursor control, colored output, non-blocking key input, screen management. Behindterminalcargo feature (enabled by default). - Terminal guard —
aver runinstalls a drop guard that restores terminal state (cursor, colors, raw mode) on exit, panic, or runtime error. - Bool namespace —
Bool.or,Bool.and,Bool.notpure builtins for logical combinators.
aver-rt::AverListnow packs repeatedappendchains into segmented chunk spines, improving list-heavy workloads in both the interpreter and generated Rust.
- Opaque types —
exposes opaque [TypeName]in module declarations. Types listed as opaque are visible in signatures but cannot be constructed, field-accessed, or pattern-matched from outside the defining module. Enforced at compile time by the typechecker. Seedocs/language.mdfor usage. aver context --focus <symbol>builds context around a specific function's dependency cone (callees, types, verify blocks, decisions)- Priority scoring for
aver contextbudget allocation: elements with more verify coverage, spec references, and focus relevance are included first - Type-aware verify sample selection: scorer uses fn return type to pick diverse cases (Ok + Err for Result fns, true + false for Bool, per-constructor for sum types)
- Granular verify coverage warnings: checker now reports missing Result Ok/Err, Option Some/None, Bool true/false, and sum type variant cases
src/checker.rssplit intosrc/checker/module (coverage, verify, intent, law)
- Dafny verification backend —
aver proof --backend dafnygenerates a.dfyfile with Z3-powered automated proofs forverify lawblocks; complements Lean'snative_decide/ tactic approach - Random service —
Random.int(min, max)andRandom.float()with! [Random]effect, backed byaver_rt::random(OS entropy viarandcrate behind feature flag) - Shared
Builtinenum —codegen/builtins.rsdefines all pure Aver builtins (~80 variants); adding a new builtin forces all backends to handle it via exhaustive match - Shared codegen utilities —
codegen/common.rsnow providesescape_string_literal,split_type_params,escape_reserved_word,parse_type_annotation, andto_lower_firstused by all three backends docs/dafny.mddocumenting the Dafny backend, its two-layer contract (sample assertions + universal lemma), and Lean vs Dafny comparison- Wumpus example (
examples/apps/wumpus.av) — Hunt the Wumpus with dodecahedron topology, effectful random, full verify coverage (30/30)
aver proofnow accepts--backend lean|dafny(default:lean)- Lean auto-proof simplified — removed brittle indirect-recursion and recursive map-presence strategies; the backend now honestly rejects patterns it can't prove instead of generating fragile proofs. Helper-law dependency hints guide users toward layered verification.
- Dafny emits
verify lawas both capped sample assertions (max 5) and a universallemma;verifycases are not emitted (Z3 can't compute deep recursion) - All three codegen backends (Lean, Dafny, Rust) now share reserved-word escaping, string literal escaping, and type annotation parsing via
codegen/common.rs - Rust codegen now gates
aver-rtfeatures (http,random) based on which services the program actually uses
projects/payment_opsas a medium-size dirty-backoffice showcase covering provider normalization, replay, settlement reconciliation, manual-review cases, and audit trail
- Rust codegen ownership for list / tuple / map literals and record updates, so valid Aver programs no longer emit generated Rust that fails with move errors in these patterns
workflow_codegen_benchfor repeatable end-to-end comparisons between interpreter, VM, and generated Rust onprojects/workflow_engine
- interpreter function bodies now lower to shared
ExprId-based runtime nodes, so the evaluation hot path no longer carries cloned AST fragments through continuations aver checknow tells users with non-tail-recursive functions to either rewrite them into tail recursion or make them a spec, and canonical spec functions no longer emit that warning- generated Rust now pins
aver-rt = "=0.2.1"so current codegen matches the shared runtime features it emits
- Rust codegen regressions around nested builtin-argument liveness, same-arity mutual tail calls, and memoized recursive named types, restoring generated builds for examples such as
grok_s_language,red_black_tree, andmysql
- native LSP document formatting via the shared Aver formatter
- richer
aver-lspeditor UX: effect-aware completion, verify/decision code lenses, contract-first hover, and document symbols with nestedverify - publishable
aver-lspcrate metadata and docs for installing the language server separately fromaver-lang
- editor install docs now target
cargo install aver-lspplus editor extension installation, with local source-build setup kept as a development path
Args.get()as an explicit runtime service for CLI arguments (List<String>)- round-trip law coverage for naturally invertible examples, including
json,grok_s_language, andnotepad/store aver context --budgetwithkb/mbsuffixes for prompt-sized exportsaver contextselection metadata in JSON and in the--outputsummary, including included depth and next-depth size- modular Rust code generation that emits
src/aver_generated/...instead of flattening all Aver code into one giantmain.rs - directory inputs for
aver checkandaver verify, so one command can walk a whole example or project tree projects/workflow_engineas a serious medium-sized Aver application core, covering projects, tasks, workflow rules, audit trail, notifications, and CLI/query flowsaver checkwarnings for recursive functions that still contain non-tail recursive callsites after TCO, with accumulator-style guidance- iterative interpreter expression evaluation backed by a heap continuation stack instead of the Rust call stack
- Breaking: effect aliases (
effects X = [...]) were removed; declare concrete method effects directly in! [...] - Breaking: broad namespace declarations such as
! [Http]no longer satisfy child effects likeHttp.get aver verifynow checks only declaredleft => rightexamples; coverage-style diagnostics moved toaver checkaver checknow reports coverage hints as warnings and no longer exits non-zero because of warnings aloneaver contextnow defaults to--depth auto --budget 10kbinstead of walking dependencies without a budgetaver context --jsonstays human/LLM-oriented: compact signatures, short verify strings, omitted empty sections, and skipped long verify cases- examples were reorganized into
core/,data/,formal/,modules/,services/, andapps/under a shared--module-root examples, while standalone showcase apps now live underprojects/
aver verify --depsnow verifies transitive dependencies- exposed sum types and constructors now resolve correctly across module boundaries
- fully-qualified constructor patterns now work consistently in parsing, typechecking, exhaustiveness, and runtime matching
Result<Unit, String>now acceptsUnitcleanly and rendersUnitconsistentlyunused exposesdiagnostics now resolve real symbol usage from AST and point at the module'sexposesline- Rust codegen now resolves module-qualified Aver calls/types without flattening sibling modules into one ambiguous Rust namespace
- Rust codegen now routes
Args.get()throughaver-rt - deep
AverListteardown andappend -> match/tailpaths inaver-rt, removing shared stack-overflow cliffs for both the interpreter and generated Rust String.slicesemantics are now shared between the interpreter andaver-rt, including negative-index clamping
aver proofas a dedicated Lean proof-export commandaver --version- docs for
Unit,mainreturningResult<Unit, String>, and theHttpServer.listen/listenWithcallback model
- Breaking:
aver compilenow targets Rust only - Breaking: Lean export moved from
aver compile -t leantoaver proof - Breaking: Lean CLI flags were renamed from
--lean-verifyto--verify-mode - Breaking: match patterns now reject positional record destructuring such as
User(name, age); bind the record and use field access instead - Breaking: constructor patterns must now be qualified (
Shape.Circle,Result.Ok,Option.None) instead of bareCircle/Some/None - CLI/docs were split around two separate backend intents: deployment (
compile) and proof export (proof)
- Lean proof export now respects qualified cross-module calls such as
Examples.Json.toStringduring function emission ordering - Lean prelude now injects built-in
Header,HttpRequest,HttpResponse, andTcp.Connectionsupport when generated code references those runtime types - Lean
List.getnow preserves Aver'sIntindex semantics, including negative indices returningOption.None examples/notepad/routes.avproof export now builds successfully under Lean withaver proof --verify-mode auto- parser/typechecker/interpreter specs were aligned with the qualified-constructor pattern rules and explicit record binding model
aver-rtas a shared Rust runtime crate for transpiled projects and interpreter adaptersaver check --depsto run contract checks for transitivedepends [...]modules- deterministic replay now walks nested recording directories
- recursion-first list runtime based on persistent
AverList
- Breaking: function bodies now use indentation only;
fn ... = exprshorthand was removed - Breaking:
|>pipe operator was removed - Breaking:
Listwas simplified to a recursion-first API:len,get,prepend,append,concat,reverse,contains,zip - Breaking:
List.push,List.head,List.tail,List.map,List.filter,List.fold,List.find,List.any, andList.flatMapwere removed - Breaking: removed
aver decisions; decision export now lives underaver context --decisions-only - Rust transpilation now depends on the published
aver-rtcrate by default, with optionalAVER_RUNTIME_PATHoverride for local runtime hacking aver checkcontract diagnostics now always include line numbers- Decision
impactsnow accepts both validated symbols and semantic strings input,expect,case,where,effect,service,needsare no longer reserved keywords- README and docs were restructured around quickstart, AI-first positioning, and the current CLI/runtime model
- old
= exprsyntax now fails consistently in parser and formatter with an actionable migration error decisions/*.avupdated to conform to strict impacts validation and namespaced console usage- renamed
examples/type_errors.avtoexamples/test_errors.avwith expanded checker diagnostics coverage
verify ... law ...blocks with typedgivendomains (a..branges and explicit lists)- Lean emission for
verify law: named law theorems, sample theorems, and universal theorem skeletons - Lean verify modes in CLI:
--lean-verify auto|sorry|theorem-skeleton --lean-proof-modefail-fast gate for proof-unsafe Lean transpilation paths- Deeper match exhaustiveness analysis for nested and recursive patterns
aver checknow treats missing verify on pure non-trivial functions as an errorverify lawskips regular case-level target-call heuristics used byverifycase blocks- Decision block fields (
date,author,reason,chosen,impacts, etc.) are contextual (no longer globally reserved keywords) - File-based commands require exactly one
moduledeclaration as the first top-level item
- Exhaustiveness checker stack overflow on recursive sum types with 2+ variants
- Empty
verifyblocks are rejected explicitly - Rust codegen now fails fast on unresolved unknown types instead of panicking
- Lean codegen/parser ordering and mutual-recursion proof-mode integration issues
- Multiple Clippy-level borrow/style issues in builtin dispatcher paths
- Lean transpilation target in CLI:
aver compile -t lean - Lean codegen backend module structure (
src/codegen/lean/*) - Transpilation docs for Lean target in README and
docs/transpilation.md
- Shared deterministic function ordering via call-graph SCC topo order (callee-before-caller) for codegen backends
- Lean forward-reference failures in emitted code (e.g. helper emitted after use)
- Lean prelude: avoid reserved keyword
fromin generatedString.slice - Lean
AverMap.setnow preserves key order when updating existing keys
- HttpServer: skip real TCP server in
--recordmode (return Unit immediately) - Example recordings: console_demo, disk_demo, http_demo, notepad
- Recording snapshots persist on every effect (long-running processes safe)
- Module sum types exported via
exposes - Map literal syntax in examples
- Silent body discard when
=appears after bindings in fn body - Architecture decision output formatting
- README: clarify no Int/Float promotion, document missing Disk methods
- Aver-to-Rust transpiler (
aver transpile) with full service support, module inlining, last-use analysis, and copy-type elision - LSP server and VSCode extension with diagnostics, hover, go-to-definition, and module dependency caching
- Deterministic replay (
--record/aver replay) — record effectful runs, replay without I/O - Static match exhaustiveness checking at compile time
- Compile-time variable resolution —
Ident→Resolved(slot)for O(1) lookup in fn bodies - Auto-memoization of pure recursive functions with memo-safe arguments
- Tail-call optimization — self and mutual recursion without stack overflow
- Typed bindings —
name: Type = exprwith type checker validation - Tuple values and tuple destructuring in match patterns
- Map type with
Map.get,Map.set,Map.keys,Map.values,Map.has,Map.remove,Map.size - Map literal syntax —
{"key" => value, ...} - Record update syntax —
Type.update(base, field = newVal) - Multiline expressions inside
(),[],{}delimiters Charnamespace —Char.toCode,Char.fromCodefor Unicode operationsBytenamespace —Byte.toHex,Byte.fromHexStringadditions —String.charAt,String.toLower,String.toUpperListadditions —List.find,List.any,List.contains,List.zip,List.flatMapResult.withDefault,Option.withDefault,Option.toResultcombinators- Generic type inference for Option/Result combinators
- JSON parser (
examples/json.av) — full RFC 8259 with\uXXXXsurrogate pairs, control char validation - Persistent Tcp connections —
Tcp.connect/writeLine/readLine/closewith opaqueTcp.Connection - Redis RESP client (
examples/redis.av) - HttpServer service —
HttpServer.listenandHttpServer.listenWith(explicit context parameter) - Weather microservice example — HttpServer + Http + Redis cache
- Notepad REST API example — multi-module CRUD app with Disk persistence
- Agent challenge infrastructure — prepare.sh, evaluate.sh, 3 challenges for AI agent testing
- Interactive REPL (
aver repl) — stateful, multi-line, type-checked aver context— project context export for LLM consumption (Markdown +--json)aver decisions— generated architecture decision docsaver check --strictmode- Human-readable parser error messages via
TokenKindDisplay - Editor support: VSCode extension + Sublime Text syntax highlighting
- Prepared for crates.io publication as
aver-lang
- Breaking:
List.get,List.head,List.tailnow returnOption(was raw value / error) - Breaking:
String.lengthrenamed toString.len - Breaking:
val/varkeywords removed — all bindings arename = expr, always immutable - Breaking: flat builtins removed (
print,len,map,filter,fold,str,int,abs, etc.) — use namespaced equivalents (Console.print,List.len,List.map, ...) - Breaking:
Ok/Err/Some/Nonekeywords removed — useResult.Ok,Result.Err,Option.Some,Option.None - Breaking:
Anyremoved from surface syntax —Type::Unknownis internal only - Breaking: colon-only type annotations (
x: Intnotx Int) - Renamed
Networkservice toHttp - Env uses
Rc<Value>with slot-based frames for resolved functions - Closures removed — functions see globals at call time, not capture time
- Pipeline: parse → TCO transform → typecheck → resolve → interpret
- Verify warnings only for pure non-trivial functions
- File size warning raised to 250 lines
- Constructor rules enforced: named fields required for records, positional for sum variants
- Empty list binding rejection without type annotation
- Pipe RHS parsing tightened
- Tuple memo hashing
{{ }}brace escapes in string highlighting- Module function scope and memo collisions
- Match arm body error message after unexpected newline
- LSP UTF-16 position handling
Initial release of the Aver language interpreter.
- Significant indentation (Python-like)
- Immutable bindings (
name = expr) - Functions with descriptions (
?), effect declarations (! [Effect]), and type annotations matchas the only branching construct (noif/elseby design)- No loops —
List.map/filter/foldfor iteration - String interpolation with
{expr} |>pipe operator?error propagation operator
- Static type checker with named types, generics,
Result<T, E>,Option<T> - User-defined sum types and records
- Function types with effect annotations (
Fn(A) -> B ! [Effect]) - List pattern matching (
[],[h, ..t])
Int,Float,String,List— pure operationsConsole— print, error, warn, readLine (! [Console])Http— GET, HEAD, DELETE, POST, PUT, PATCH (! [Http])Disk— readText, writeText, appendText, exists, delete, deleteDir, listDir, makeDir (! [Disk])Tcp— send, ping (! [Tcp])
moduleblocks withintent,depends,exposes- Dot-path imports (
depends [Examples.Foo]) - Named effect sets (
effects AppIO = [Console, Disk])
aver run— execute programsaver verify— run verify blocks as testsaver check— static analysis (types, effects, style)verifyblocks — declarative equality-based test casesdecisionblocks — architectural decisions as code