Added
scripts/check_doc_counts.pypins FAQ.md's headline test count. The "by the numbers" line in FAQ.md carried a total-test figure no gate checked — only the conformance half of the sentence was pinned — and it drifted silently through two releases before being caught by hand both times. The oracle now reads the number the same way it reads README.md's status row, so the next drift fails pre-commit and CI instead of shipping.
Fixed
-
vera runno longer executes a different function when the entry was dropped (#1183). When the[E620]skip propagation droppedmain— or an explicit--fntarget — and any public sibling survived,execute()fell through toresult.exports[0]: the sibling's body ran, its result printed, exit 0, nothing on stderr. A regression of the #1178 review class, and the one outcome the loud-skip design exists to prevent, since the user could not tell the answer came from a different function. A dropped entry is now a refusal:vera runexits nonzero and names both the requested function and the root[E602]/[E620]diagnostic that removed it (--jsonreportsok: falsewith the same text). Auto-selection survives only for the never-declared case — nomainanywhere in the source — and announces itself with a one-lineNote:on stderr naming the function it picked, so the choice is never invisible. TheCompilation notes:block is no longer gated on an empty export list; it prints on every run that has skip/drop diagnostics, which is exactly the case (a surviving sibling) where the user was least likely to notice something went missing. The same review's sibling surfaces are closed alongside:vera compileexits nonzero when a program declares a public non-generic function and the module ends up exporting nothing (a file of private helpers, or a cross-module generic library, still compiles clean — neither has an entry point to lose), andvera compile --target browserrefuses to write a bundle without amainexport — whethermainwas declared and dropped (the refusal quotes the E620 chain) or never declared at all (it names what is exported instead) — since the generatedindex.htmlcallsmain()on load either way.CompileResultgainsdropped_fns, mapping each dropped user function to the diagnostic that explains it, so the refusal quotes the root cause rather than re-deriving it. -
[E602]diagnostics for imported function bodies locate in their own module (#1186). The root skip for a function compiled through the Pass 2.5 / 2.6 import doors carried the MAIN file's path with the MODULE's line and column, so the rendered source line quoted whatever happened to sit at that line in the importer — a stray}in the reported repro. It also kept a branch of_drop_dangling_callerspermanently dark: the[E620]caller message prefixes the root location with its file when the root came from elsewhere, but the comparison was against a path that always matched. Imported bodies now compile under the module's own file and source, so the coordinates and the file agree and a cross-file drop reads… (see the [E602] warning at path/to/module.vera, line 5, column 5). Relatedly,vera testreported a PUBLIC function that codegen had dropped asnot exported (private)— advice to fix a visibility modifier that was already correct, behind a# pragma: no coverclaiming the branch only saw private functions. It now names the actual[E602]/[E620]root and where to find it. -
Runtime traps inside imported functions name the module's file (#1189).
fn_source_map— the tablevera runresolves a wasmtime trap frame against — was populated by_register_fn, which stamps every entry with the file the generator was constructed for, and that pass runs before the per-module source scope is entered. An imported non-generic function never reached the main generator's registration at all (Pass 0.5 registers module declarations into a throwaway generator), so its frame printedin scaled (<unknown>); themod$…emission of a locally-shadowed import fared the same way, since the resolver's rightmost-$strip yields a base that is nobody's entry. A monomorphized clone of an imported generic was registered, and so came out worse: the importer's path paired with the module's line range, coordinates that in the reported repro named a real-but-unrelated function in the importer — a backtrace that reads as correct and is not. The Pass-0.5 registrar is now given the module's own file, its source map is harvested (and mirrored onto the mangled name for a shadowed import), and clone registration runs under the same_module_source_scopePass 2.5/2.6 already use, so registration and emission agree on which file a body belongs to. Only the file component moves; the line and column were already module-local, and main-file entries are unchanged. This completes the class #1186 opened: PR #1190 fixed the[E602]/[E620]diagnostic locations, this fixes the source maps behind runtime traps. -
old(...)andnew(...)applied to an expression report a dedicated diagnostic (#1173),[E030]and[E031], instead of a generic[E005]unexpected-token error. Vera'sold/newtake an effect reference —old(State<Int>), spec §7.9.2 — so a model reaching for Dafny'sold(<expression>)wroterequires(old(@Int.0) > 0)and got a caret on the@inside the argument, "Expected one of: UPPER_IDENT", boilerplate fix text about missing delimiters, and a pointer to the formal-grammar chapter. Nothing namedold, and nothing said what its argument has to be. Found by the VeraBench v0.0.18 sweep (VB-T5-009).The caret now lands on
old/newitself. The message names the construct, states that the argument is an effect reference and that the call belongs in anensures()clause, and the rationale gives the reason both rules exist: Vera has no mutable variables, so a slot holds one value for the whole call and effect state is the only thing a call can change. The fix shows both repairs — drop the wrapper for a parameter's value, or name the effect insideensures().The diagnostic is raised at parse time, where the failure occurs. Letting the grammar accept
old(<expression>)and rejecting it in the checker is not available:old_expr: "old" "(" expr ")"stopsold(State<Int>)parsing at all (the<reads as a comparison), and carrying both alternatives is a reduce/reduce collision betweeneffect_refandfn_callonUPPER_IDENT. The detector fires only when the parse failed on the first token of anold(/new(argument, soold(State<Int> > 0)— whose real fault is the missing)— still reports[E005]rather than being blamed onold. -
[E174]and[E175]explain why a precondition cannot hostold()/new()(#1173). The rationale now says that arequires()ordecreases()clause is itself evaluated before the body runs, so every expression in it already observes the pre-state and the after-statenew()names does not yet exist. The fix adds the fact that closes off the obvious retry: a precondition cannot constrain effect state at all, because contract predicates must be pure andold()/new()are the only contract forms that name state. -
Redeclaring a built-in effect is now a compile error,
E152(#1149). Aneffect IO { ... }block — orState<T>,Exn<E>,Http,Random,Inference,DB,Diverge,Async,HttpServer— used to override the built-in at checker level, which spec §9.5.1 sanctioned "for backward compatibility". Code generation never honoured it: a qualifiedIO.print(...)is lowered to the fixed host import selected by the qualifier name, and the declaration is never read. So a block whose operation signature diverged from the built-in (op print(String, String -> Unit),op query(String -> ...)) passed bothvera checkandvera compilewith exit 0 and no diagnostic, then trapped atvera runon structurally invalid WebAssembly. An ordinary typo in the idiomatic-but-optional declaration reached it.The gate is name-keyed and unconditional — a faithful redeclaration is rejected too, because it is a second textual spelling of the same program (DESIGN.md, spec §0.2 design goal 3, one canonical form). It is the sibling of
E151for built-in functions, and it reads the name set from the live effect registry rather than a hand-list, so a future built-in effect is covered the moment it is registered; a differential test pins that set equal to whatvera effects --jsonpublishes. The rejected block is not registered, so the built-in stays canonical and call sites resolve against it instead of cascading arity errors. A module redeclaring a built-in effect surfacesE152into its importer, asE151already did.Exn<E>becomes a real entry in the effect registry as part of this. It was recognised only by code generation (handle[Exn<E>]), sohandleandthrowneeded the veryeffect Exn<E> { op throw(E -> Never); }block the new rule forbids; it is now in scope with no declaration, likeIOandState<T>.vera effects --jsonreports itstype_paramsas["E"], matching spec §7.7.2, where the hand-written entry it replaces said["T"].Spec §9.5.1 is amended: the backward-compatibility override sentence is deleted and the strict rule stated in its place. §7.7 opens with the rule as one blanket statement over the whole registered set — including the operationless markers
Diverge,HttpServerandAsync, where having nothing to declare does not make the block legal — rather than a per-effect sentence or a name list that would drift from the registry. §7.2 gains it as a numbered constraint on effect declarations and its examples move to user-defined effect names; §7.7 and §9.5 present each built-in's operations as a table rather than in declaration form, since that form is no longer writable. Theeffect IO {}/effect Exn<E> {}blocks are removed from 12 examples and 20 conformance programs, and new conformance programch09_builtin_effect_redefinition_rejected(170, was 169) pins the divergent-printrepro as anE152negative. The #309 SQL literal-provenance gate is untouched: it still keys on the codegen routing axis rather than built-inOpInfoidentity, and its tests now assert thatE207fires alongsideE152on a usereffect DBshadow — defence in depth behind the new rule, not a replacement for it. -
fn old/fn newdeclarations are rejected at the declaration site,E153(#1181). The grammar reservesold(andnew(in expression position for the contract state forms —old_expr/new_exprinvera/grammar.lark, each of which demands an effect reference — so a bare callold(5)is always read as a malformed state reference ([E030]/[E031], #1173) and never resolves to a function — not in the declaring file, and not inside the declaring module either. One route did reach such a function: a module-qualifiedmod::old(...)parses through the module-call rule, so a module export namedoldwas callable cross-module (and only cross-module) — adversarial review of the fix confirmed the shape checked and ran. The declaration is now refused outright, reserving the whole identifier rather than leaving it a trap in every unqualified position, the sibling ofE151(built-in functions) andE152(built-in effects) under the same one-canonical-form rule. Breaking: a module export namedoldornewthat was called via the qualified route must be renamed. The gate covers top-level andprivatefunctions, genericforall<T>functions,where-helpers (called in expression position exactly like top-level functions), and modules — a module declaringfn oldsurfacesE153into its importer, asE151andE152already do. The reservation is on the whole identifier, soolder/renew/newsstay legal.This half of the gate covers the two contract state forms; the keyword class the contextual lexer also admits as a function name is reserved separately, by #1187 below. The probe record lives in the test docstrings. Spec §5.2 states the rule; new conformance program
ch05_reserved_fn_name_rejected(172, was 171) pins it as anE153negative. Mutation-validated: emptying the reserved set, dropping thewhere-helper recursion, dropping the module surfacing, and matching on prefix rather than whole identifier each flip their targeted tests RED. -
A function named after a grammar keyword is rejected at the declaration site,
E153(#1187). Lark's contextual lexer re-lexesassert,assume,forall,exists,match,if,let,fn,trueandfalseas ordinary identifiers afterfn, so each declares cleanly — and none can be written in expression position, where the spelling is always the keyword: a barematch(3)does not parse at all ([E005]), andassert(3)/assume(3)are read as the statement forms and collide ([E121]plus[E172]/[E173]). Every one is a declarable trap, so the reservation refuses the mistake at its source rather than letting it surface as whichever call-site error the spelling happens to produce — the same one-canonical-form rule that already covers the contract state forms (#1181), built-in functions (E151) and built-in effects (E152). Breaking: a module-qualifiedmod::match(...)parses through the module-call rule rather than any keyword rule, so a module export under one of these names was callable cross-module (and only cross-module) — probed on the pre-fix tree, the shape checked and ran. Such an export must be renamed; the breakage is loud and located at the module's declaration.handleis carved out and stays legal:public fn handle(@Request -> @Response)is the entry point the host invokes undervera serveandwasi:http(spec §9.5.6,examples/http_server.vera), so being uncallable from Vera source does not make it dead code. It lives in a named_HOST_INVOKED_FN_NAMESset subtracted from the reservation, so a future host-invoked entry point joins it deliberately rather than by editing a flat list. TheE153rationale branches with the reason — a keyword is not described as a contract state form — while the fix stays "rename" on both. The gate inherits the #1181 shape: top-level,privateand genericforall<T>functions,where-helpers, and modules (a module declaringfn matchsurfacesE153into its importer, carrying the module's own file path). Matching is on the whole identifier, somatched/letter/iffystay legal, andop <keyword>(...)inside aneffectblock never reaches the gate — the lexer refuses that spelling at parse ([E005]), pinned so a grammar change that admits it shows up as a failure to widen. Spec §5.2 states both halves of the rule; new conformance programch05_reserved_keyword_fn_rejected(176, was 175) pins it as anE153negative. Mutation-validated: emptying the keyword set flips the ten keyword tests RED with theold/newtests still green, and emptying the carve-out flips thehandlecontrol RED (and breaksexamples/http_server.veraandch09_http_server). -
Vera-prefixed type names are reserved for the prelude,E154(#1184 review). The prelude's combinators resolve their parameter types through generated declarations in that namespace (VeraOptionMapFn; type parametersVeraA/VeraB, #869), andinject_preludeskips any of its declarations whose name a user program already spells — sotype VeraOptionMapFn = Int;silently re-typed the prelude's own signatures: check-green, then a WebAssembly validation failure at run. Declaring a type or alias whose name begins withVeraplus an uppercase letter or digit is now refused at the declaration, with module declarations surfacing the error into their importer as the E151/E152/E153 family does. Ordinary names merely containing the letters (Veranda,MyVeraThing) are unaffected, and shadowing the unprefixed prelude aliases (OptionMapFn) remains legal. New conformance programch08_reserved_vera_prefix_rejected(175, was 174) pins the rule. -
scripts/check_site_assets.pygates the factsdocs/index.htmlanddocs/index.mdboth state (#1154). The Markdown companion agents fetch viarel="alternate"andllms.txtis emitted bybuild_index_md()inscripts/build_site.py, which holds the landing page's substance as a hand-maintained f-string. The staleness check called that generator and compared the result against the committed file — the generator on both sides of the comparison — so a generator that missed an edit to the hand-designed HTML produced a committed asset stale in exactly the same way, and the gate passed. A v0.0.7-era benchmark section survived every intervening release that way.check_fact_coherence()now extracts the load-bearing facts from each file independently and fails when they diverge: the VeraBench version and the Vera release it was measured against, the landing-page version badge, the problem/tier/model/provider counts, the headline perfect-Vera count, the nine-row results table (model name, tier and all three figures per row, plus each file's row count against its own stated model count), and the three editor names. An error names the fact, both values and both paths. A fact that cannot be located is itself a failure rather than a silent skip, so a reworded sentence cannot switch its own check off. -
Type aliases are module-local in codegen, matching spec §8.4.1 (#1111). Codegen merged every imported module's type aliases into one flat bare-name map (
setdefault, first module won) and let the main file's aliases overwrite it, so two modules legally reusing one alias name for different targets — or a main-file alias sharing a module's alias name — re-typed one side's declarations through the other's namespace: wrong WASM signatures, invalid modules atrun, an import-order-dependent victim, and (where representations coincide) silently wrong values. Each module's aliases are now captured in a per-module namespace and its declarations — Pass 2.5/2.6 bodies and monomorphized clones of its generics alike — compile and register under{prelude, module's own}; harvested return types are canonicalized against the defining module's maps before entering the shared registries, so no consumer can re-resolve them against the wrong namespace. -
A user type alias named after one of the prelude's fn-type aliases no longer disables the combinator it names (#1184). The prelude spells its closure-taking combinators' parameters through type aliases —
option_map(@Option<VeraA>, @OptionMapFn<VeraA, VeraB>)— because a slot reference needs a type name, and those names were injected into the same namespace user code declares into. So atype OptionMapFn = Int;(orOptionBindFn, orResultMapFn) re-typed the prelude's declaration: the mirror of the #1111 defect spec §8.4.1 forbids in the other direction. The two namespaces then disagreed about it, and neither was right. In the main file the user's alias won the flat maps outright, sooption_mapwas emitted with anInt(i64) parameter where the call site passes a closure funcref, andvera rundied at WASM validation. In an imported module the same collision was silent: Pass-1.5 instantiation discovery ran under the flat maps and createdoption_map$Int_JInt, while the module body — compiled under{prelude, module's own}, where the module's alias won — bound the combinator's return type parameter to the phantom-var default and calledoption_map$Int_JBool. A missing call target is an[E602]warning, so the function was skipped and its caller dropped, andcheck,verifyandcompileall reported success over a program with empty exports.The prelude's own combinators now resolve through reserved
Vera-prefixed twins of those aliases (VeraOptionMapFn,VeraOptionBindFn,VeraResultMapFn), derived mechanically from the public declarations rather than restated, and injected with the bodies that need them rather than with the user-facing block a program can suppress. This is #869's remedy — reserved names no ordinary user declaration spells, keeping prelude internals invisible to user namespace decisions — applied to the alias names rather than the type-parameter names. The user-facingOptionMapFn,OptionBindFn,ResultMapFn,ArrayMapFn,ArrayFilterFnandArrayFoldFnnames stay injected and stay the user's to shadow: a colliding alias keeps meaning exactly what the user wrote, in both namespaces, and the combinator keeps working alongside it. The per-module alias scope also overlays its two maps as a pair in the same change, so a module alias shadowing a parameterized prelude alias with a non-parameterized one can no longer inherit the prelude's stale type-parameter list. Spec §8.4.1 states the rule. -
decreasesclauses are enforced at run time — E525's promise is no longer empty (#1172). A Tier-3 termination obligation warned that the metric "will be checked at runtime", butdecreaseshad no runtime lowering at all: a non-terminating recursion passedcheck, passedverify, and hung atrun(found by the VeraBench v0.0.18 sweep,VB-T4-006). A function with adecreasesclause now carries an entry guard (whenever the backend can express its measure — the honest limits below): on re-entry, the measure — scalars by value, ADT measures by structural size (a generated$dec_size_<T>helper), lexicographic tuples componentwise per spec §5.6.1 — must be strictly less than the previous activation's and non-negative, or the program traps through the contract-violation channel with a message naming the function. Guard state is per function and restored at every exit, and tail-call optimization is preserved for self-recursion — a self-recursivereturn_callcarries a call-site check (arguments captured, the measure evaluated over them against the live chain state, the activation's guard state closed out before transfer), so the documented pure-iteration idiom keeps its constant-stack depth (#517's 1M-iteration property is regression-tested); only mutual-tail recursion between guarded functions falls back to plain calls, also closing #1176 in the same change. Alongside: the checker now rejects measures with no well-founded ordering (Float64/String/Booland friends, newE127, previously accepted silently as decorative), and spec §5.6.1's own lexicographic Ackermann example is corrected — as printed its permuted arguments made it non-terminating, exactly the class itsdecreasesclause was meant to rule out. The guard also caughtexamples/gc_pressure.veradeclaring its accumulator as the measure (it grows every hop) — corrected to the counter. A measure of a parameterized ADT type is not yet runtime-ranked (the registered generic layout does not describe concrete construction) and stays honestly Tier-3-disclosed, as does any function declaringExn— an unwindingthrowwould bypass the exit restores and leave a stale baseline that traps a later terminating call — and any measure the backend cannot translate. An independent adversarial review then hardened the tier contract: tail calls from a guarded function to an unguarded one are demoted to plain calls (an unguarded trampoline could re-enter with the chain zeroed and loop unchecked), and the verifier's decreases call-walker now descends into handler clauses, closure bodies, constructor/qualified/module-call arguments, index expressions, array literals, interpolation, and assert/assume — a measure-violating recursive call hidden there previously rode a false Tier-1 proof that the new guard exposed as a verify-green run-time trap. -
A codegen skip now propagates to its (transitive) callers instead of surfacing a raw wasmtime error (#1100). An
[E602]-class skip drops a function from the emitted module, but every caller'scall $f/return_call $fwas still emitted, so a check- and verify-clean program whose skipped construct sat in a called helper failed atcompile/runwithWAT compilation failed: unknown func: failed to find name $f— loud and never a wrong answer, but a WAT internals dump instead of a Vera diagnostic (found in the #1098 adversarial review, whose negative test sidestepped it by putting the skipped construct directly inmain). A new pre-assembly pass (_drop_dangling_callers,vera/codegen/core.py) now walks the emitted WAT — the exact symbol stream wasmtime resolves, so mono-mangled (f$Int), module-qualified (mod$f), and where-helper call targets are matched without re-deriving any renaming logic — and drops the whole doomed caller subgraph to a fixed point: each dropped caller gets its own new[E620]warning ("Caller of a skipped function", registered invera errors) naming the ROOT skipped function and its skip location plus the direct call edge (calls function 'mid', which was dropped because function 'sunk' was skipped … at line 2, column 34), so the module always assembles and the user is pointed at the construct to fix rather than at generated WAT (DESIGN.md principle 1). Propagation follows the same graceful-degradation semantics as the root[E602]skip itself (spec §11.4.1): warnings, not errors — a caller-of-skipped program now behaves exactly like the skipped-construct-directly-in-mainshape always did (clean compile, the subgraph absent from the exports,vera runreporting the missing export with the explanatory notes). Lifted closures participate on both sides: a closure body holding the onlycall $skippeddooms its parent through an explicit construction edge (the parent's WAT carries only a function-table index, invisible to a symbol scan), and a doomed closure's body is replaced by anunreachablestub rather than removed, because later closures'closure_id↔ table-index correspondence depends on every earlier(elem …)slot staying occupied. Functions outside the doomed subgraph — including their exports — are untouched, and call-graph cycles (mutual recursion) terminate at the fixed point. Pinned bytests/test_codegen_skip_propagation_1100.py(the repro, depth-2 transitive drops in BOTH declaration-order permutations so a single-sweep propagation goes RED, an untouched-sibling run, mutual-recursion termination, the closure shape, root-cause naming with location, and a no-callers control), with four mutations — single-sweep, dropped closure edge, dropped stubbing, hop-instead-of-root threading — each killed by a named test; the CLI legs (tests/test_cli.py) pin the clean text and JSON envelopes, and the #1004 codegen-error-path warning flush keeps coverage via a typed-hole fixture, since the original dangling-caller fixture now takes the clean-drop path. -
A
call_indirectis never emitted without a function table to dispatch on (#1185). The unclosed half of the #1100 class: an indirect call names no symbol, so the caller-drop pass — which scans the emitted WAT forcall $f— could not see it. When an[E602]skip swallowed a module's only closure, the lift rolled back and module assembly suppressed the(table)/(elem)sections, but every surviving carrier kept itscall_indirectinto a table that no longer existed. The result was an uninstantiable module emitted with zero error diagnostics: running any unrelated export — a function with nothing to do with closures — raised a rawWasmtimeError: … unknown table 0: table index out of bounds. Two emission sites reproduce it, since a carrier need hold no closure of its own: theapply_fnspecial form, which lowers a closure-typed parameter tocall_indirectunconditionally, and a monomorphized clone of a prelude combinator such asoption_map. The drop now propagates to the carriers exactly as it does to ordinary callers — with no table in the module a carrier's indirect call can only have targeted a dropped closure, so the carrier is seeded into the same fixed point and dropped with its own[E620]naming the[E602]root that emptied the table, and its own callers drop transitively behind it. Emitting an empty table instead was rejected: an instantiable module whosecall_indirecttraps at call time with an opaque wasmtime error is strictly worse than a located refusal (DESIGN.md principle 1, fail loud). A program that applies a closure-typed parameter while never writing a closure at all — no skip, no[E602], previously no diagnostic of any kind — is the same absent table and drops the same way, with an[E620]that explains the absence on its own terms. The invariant is enforced as a differential over the two sides that must agree, the instruction stream and the table section:tests/codegen_helpers.py::_assert_no_orphan_call_indirectruns on every compile in the codegen suite, so a future desync between them cannot hide behind a green unit test the way this one did for the whole #1100 cycle. #1100's acceptance helper is hardened alongside: it checked the diagnostics plus a non-emptywasm_bytesand never handed the module to wasmtime, which is precisely why it passed on this shape — it now loads the module, so the helper catches the class itself.
Documentation
-
The VeraBench section carries the v0.0.18 sweep (#1169), the first in which all 60 problems are graded — v0.0.17 took the gradeable set from 36 to 46 and v0.0.18 closed it. One problem is now worth 1.7 percentage points rather than 2.8. Six of the nine models solve every Vera problem, and Vera is highest or level with it for six of the nine. Measured against Vera v0.1.8. The section also gains the reading the wider gradeable set supports: Python is dynamically typed and TypeScript is not, Vera sits with TypeScript and goes further, and sorting the three by how much they constrain the model rather than by how much of them it has read puts the two constraining languages ahead — TypeScript with training data behind it, Vera without.
Every figure was cross-checked cell by cell against vera-bench#120, the pending results rewrite in the benchmark repo; all 27 published cells agree, as do both headline counts. The landing page's numbers and the benchmark repo's are the same measurement, not two independent transcriptions of it.
Propagated to every surface that carries the figures, which the HTML edit alone does not reach:
build_index_md()inscripts/build_site.py— the generator that isdocs/index.md, since that file is not derived from the HTML — plusREADME.mdandFAQ.md, thendocs/index.mdanddocs/llms-full.txtregenerated. This is the drift class #1154 describes:check_site_assets.pyregenerates from the same function it compares against, so a stale generator validates as up to date and only a reader notices.