From eef5197f753ace0af54d96f29eef2d4752447589 Mon Sep 17 00:00:00 2001 From: Brummel Date: Sat, 9 May 2026 21:37:51 +0200 Subject: [PATCH] =?UTF-8?q?iter=2022b.3:=20JOURNAL=20=E2=80=94=20monomorph?= =?UTF-8?q?isation=20pass=20shipped,=20e2e=20gate=20green?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/JOURNAL.md | 104 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) diff --git a/docs/JOURNAL.md b/docs/JOURNAL.md index a1515d3..ce41ff6 100644 --- a/docs/JOURNAL.md +++ b/docs/JOURNAL.md @@ -11499,3 +11499,107 @@ cost. First end-to-end exercise is a positive signal; the remaining open follow-ups from 2026-05-09 (skill bugs surfaced by real iter use) get queued as targeted SKILL.md tweaks rather than a wholesale revisit. + +## 2026-05-09 — Iteration 22b.3: Monomorphisation pass + +Mono pass slots into `crates/ail/src/main.rs::build_to` between +`lift_letrecs` and `lower_workspace`. The pass is a `Workspace -> +Workspace` transformation living in `crates/ailang-check/src/mono.rs`, +mirroring `lift.rs`'s position and ownership model. For workspaces +with no `Def::Class` / `Def::Instance`, the early-out returns the +input clone byte-identically — pre-22b fixtures stay +hash-stable through the full pipeline. For typeclass-bearing +workspaces, the pass runs three phases: (1) build the full env via +`build_workspace_env` (same shape as `check_in_workspace`), (2) +fixpoint loop — each round walks every fn/const body, re-runs +`synth` to recover residual class constraints, applies `subst`, +filters fully-concrete residuals, dedupes via `(class, method, +type-hash)` key, synthesises one `Def::Fn` per unique target via +`synthesise_mono_fn` (instance body OR class default fallback, +class param substituted to instance type), and appends to the +registry's `defining_module`. Loop terminates when a round adds +no new entries — closes on chained class-method calls (e.g. an +instance body that itself calls a class method, exercised by +`test_22b3_chained_calls.ail.json`). (3) Phase 3 walks every +fn/const body in the post-fixpoint workspace and rewrites every +class-method `Term::Var` to its mono-symbol name, qualifying with +the defining module if cross-module. The walker is shadow-aware: +local bindings (Let / LetRec / Lam / Match-arm) suppress the +rewrite at locally-named Vars, mirroring synth's +locals-take-precedence rule. + +End-to-end gate met: `examples/test_22b3_mono_synthetic.ail.json` +declares `class Foo a where foo : (a) -> Int` + `instance Foo Int +where foo = lam i. i` + `main = do io/print_int (foo 5)`. `ail +run` produces stdout `5\n`. All three bench gates green: +`bench/check.py` 63/63, `bench/compile_check.py` 24/24, +`bench/cross_lang.py` 25/25 — mono pass is genuinely no-op for +class-free fixtures. + +Mono-symbol naming format decision: spec recommended +`#` ("but the implementer chooses"). `#` turns +out to be invalid in LLVM IR global identifiers — clang rejected +`@ail_..._foo#Int_clos`. Switched to `__` +(double underscore separator); same legibility, valid C-ABI. +DESIGN.md / spec amendment in the next sweep. + +Per-task subjects: + +- iter 22b.3.1: mono pass skeleton — identity for class-free workspaces +- iter 22b.3.2: mono_symbol — primitive surface forms + hash for compound +- iter 22b.3.3: collect_mono_targets — synth-replay residual gathering +- iter 22b.3.4: synthesise_mono_fn — instance body + class default fallback +- iter 22b.3.5: workspace fixpoint loop — dedup + synth-append (multi-round chained-call coverage) +- iter 22b.3.6: call-site rewrite — same/cross module qualified names (shadow-aware) +- iter 22b.3.7: synthetic fixture — class+instance compiles, runs, prints 5 +- iter 22b.3.tester: e2e for coherence (two instances), default keyword, cross-module mono + +Each task ran through the two-stage review (spec compliance → +code quality). Quality round caught: shadow-blind walker (Task +6), untested cross-module branch (Task 6), untested zero-arg +method branch (Task 4), untested multi-round chained-call case +(Task 5), defensive `unwrap_or_default` (Task 3), speculative +`Float` arm (Task 2), and module-doc-vs-implementation drift +(Task 1). All caught + fixed before iteration close. + +**Plan-text defects surfaced during execution** (collected here +so a 22-arc audit / next-iter brainstorm can act on them): + +- The plan's RED test for Task 5 used `Term::Seq` with `lhs: + Str`, but Seq requires `lhs: Unit`. Implementer substituted + `Term::Let { name: "_a", ... }`. The spec/plan template needs + a "discard-and-continue" idiom note. +- The plan's fixpoint loop scaffold filtered targets only across + rounds, not within a round. Two same-type call sites in round + 1 would both append; implementer added `seen_this_round`. +- `Float` was speculatively included in `mono_symbol`'s primitive + table — `Float` is not in the language. Removed. +- Pre-existing `examples/test_22b2_*` fixtures had bare-Lit + instance bodies for arrow-typed methods. Worked under 22b.1/2 + because instance-body typecheck is deferred follow-up. + Mono-pass synth requires Lam-shaped bodies; affected fixtures + Lam-wrapped during this iter (`test_22b2_instance_present`, + `test_22b2_xmod_classmod`). Instance-body typecheck remains + deferred — that's the right place to catch this user-side. + +**Known debt / out-of-scope deferrals:** + +- Form-B prose projection for `Def::Class` / `Def::Instance` + remains deferred to 22b.4. `crates/ailang-surface/tests/round_trip.rs` + skip-list excludes `test_22b2_*` and `test_22b3_*`. +- DESIGN.md mono-symbol naming amendment (`#` → `__`) needs a + small edit in 22b.4 alongside the Prelude wiring. +- Primitive-name set is hard-coded in three sites + (`linearity.rs`, `lib.rs` x2, `mono.rs`). Reviewer flagged the + duplication; consolidation deferred to milestone-22 audit. +- `pub mod mono` instead of `mod mono; pub use mono::...;` + pattern was the deliberate plan-level call (multiple pub items + reach by integration tests). Audit may revisit if the + convention drifts further. + +**Next: 22b.4 — Prelude module + Form-B parser/printer arms.** +Prelude declares `Show`, `Eq`, `Ord` over `Int`, `Bool`, `Str` +(default-bearing where Decision 11 mandates), `print` re-routes +through `Show.show`, Form-B class/instance prose lands, round-trip +filter retires. End-to-end gate: any prior `print 42`-style fixture +must keep working through `Show.show#Int`.