v0.1.3
Fixed
-
The monomorphizer completeness class: generic instantiation no longer silently produces wrong programs for exotic shapes (#769). Three gaps, shared by codegen and the #732 verifier — both sides agreed on the wrong instantiation, so
vera verifywas green while the compiled program traps or does the wrong thing:- The builtin return tables were a fraction of the registry.
_BUILTIN_PARAMETERIZED_RETURNScovered 9 of the 41 builtins with fully-concrete parameterized returns, sofirst(string_chars("abc"))could not bindT = Stringand fell to theBoolphantom default — a check-green, verify-green program that failed WASM validation at run time. Both tables are now registry-complete (41 parameterized entries incl. nested inners likeOption<Array<Json>>; 160 simple names), enforced by a differential test againstTypeEnv._register_builtins, with thearray_rangespecial cases folded in on both sides. - Nested type-argument unification was one level deep.
Array<Array<E>>matched againstArray<Array<Int>>leftEunbound (theE521warning literally reportedhead_head<Bool>) and the run trapped on the i64/i32 ABI mismatch. A shared recursive unifier (Monomorphizer._unify_type_arg_pair) now binds variables at any depth on BOTH the discovery and WASM call-rewrite sides, and array-literal arguments expose full-depth element names ([[1, 2]]→Array<Array<Int>>,[Some(1)]→Array<Option<Int>>) so there is something to bind against. - The De Bruijn collapse-reindex was parameter-only and name-truncated — the highest-severity gap:
confuse(100, 3)returned3. A bodylet, match-arm binder, or closure parameter of a collapsing type shifts every later slot reference, which a static params-only map cannot express (one type variable suffices:let @Int = 5;before@A.0atA = Intsilently read the5); and the one-level slot-name truncation collapsedArray<Option<A>>/Array<Option<B>>to the same old name, hiding genuine merges entirely. The map is replaced by a scope-aware walker (_compute_scoped_reindex) that resolves everySlotRefagainst the full binding scope at its reference site — parameters,let/destructuring bindings, match-arm binders (arm-scoped), closure parameters, handler clauses, with contracts as a params-only scope,where-helpers as independent scopes, and full-depth canonical names fromvera/slots.py— mirroring exactly the environments the checker, verifier, and codegen build.
- The builtin return tables were a fraction of the registry.
-
Four sibling discovery-vs-rewrite desyncs, found probing the same class, all dropped
mainfrom check-green programs (fixed in this release with #769; each was an E602 dangling clone — instantiation discovery and the WASM call-site rewrite inferred different clone names):apply_fn(closure, …)results in generic-argument position (discovery lacked the chain's closure-return arm — added as_closure_arg_return_te, resolvingFnTypealiases through the sharedresolve_fn_type_alias);async(…)/await(…)results (discovery gains the sameFuture<T>wrap/strip arms the chain has); and generic calls with i32-handle returns (ident(pass(Some(42)))— rewrite WAT-collapsed toBool) or declared-Natreturns (rewrite collapsed i64 toInt) — the rewrite's generic branch now substitutes the callee's declared return exactly as discovery does. -
Three regressions the adversarial review panel and PR review caught before ship, fixed in-PR (PR #972 review round): the scope-aware reindex counted a handler-state binding for
State<T>-handled bodies that codegen never pushes (state lives in host-side cells) — a generic with a stateful handler either failed compile with a dangling-slot E699 or silently read the wrong local; the walker is now codegen-faithful, while the underlying checker/backend scope divergence stays open as #973; the shared builtin-name dicts shadowed user OVERRIDES of E151-exempt prelude combinators (#815:option_map, thejson_*family) — all four consult sites are now gated onfn_ret_typesregistration, the #908 show/hash gate generalised, so a real declaration's type always wins; and the reindex walker only visitedwhere-helpers one level deep —whereblocks nest, and a nested helper's collapsed indices went stale (it read its bodyletinstead of the parameter). Each fixed test-first with the panel's own repro programs, mutation-validated. The review also revived the HISTORY row-hygiene gate:check_doc_counts.py's version-row regex was pinned to thev0.0.xseries, so nov0.1.xrow was being inspected — it now covers everyvX.Y.Nrow and codifies the current template (at most one issue link and one bold-lead-in—separator per row), mutation-checked in both directions. The audit also filed and cataloged four checker-front-end bugs the fixes exposed but did not cause — #969, #970, #971, #973 — now in the KNOWN_ISSUES and SKILL.md bug tables (ending the "no known bugs" run; the tables reflect the tracker, not the trend). A follow-up sweep of all 100 open issues against the same standard reclassified three more — #758 and #820 (verify-clean programs silently violating the@Natinvariant at un-obligated, un-guarded, un-disclosed positions) and #967 (theverify --jsonsummary arithmetic) — fromlimitationtobug, with their ROADMAP rows removed (bugs are burndown material, not stage work) and their guard-deferral siblings (#754, #757, #765: statically obligated, verified programs sound) staying limitations on the merits. -
A false Tier-1-blocking E503 inside monomorphized generic clones is fixed (PR #972 review; pre-existing on
main, but newly reachable on the very programs this release makes runnable): the span-keyed semantic-type side-table returned GENERIC types for clone nodes (clones keep their source spans for diagnostics), sois_some_g<Nat>matchingSome(@T)on anOption<Nat>scrutinee was obligated as an@Int->@Natnarrowing with the impossible counterexampleSome(-1)—vera verifyrejected a programvera runexecutes correctly. Clone verification now substitutes the instance's concrete types into every side-table lookup (sub-pattern binds, source facts, destructures), while genuine clone-path narrowings still emit their obligation.
Changed
- The WASM inference chain's ~130 hand-written builtin return arms are hoisted into the shared
_BUILTIN_VERA_RETURN_TYPESdict (#769). Discovery and the call-rewrite previously kept two independent copies of "what does this builtin return" — the mono-side dict and theinference.pyif-chain — and every difference between them was a dropped-mainbug (e.g.json_getwas dict-only,string_charswas chain-only; each direction desynced). Both consultors now read the ONE dict, so an entry added for a new builtin lands on both sides atomically; the chain keeps only its logic arms (apply_fn,show/hashwith the #908 user-shadow gate,async/await), and values are preserved verbatim (string_lengthstays"Int",string_char_codestays"Nat"— clone-name agreement is the invariant, name precision is a separate decision).