Adds three end-to-end tests for the 22b.3 mono pass, each with a
matching one-property fixture under examples/. Defends mono-pass
invariants 3, 4, and 6 from docs/specs/2026-05-09-22-typeclasses.md
at the binary level (stdout-asserting, not AST-asserting):
- coherence_two_instances_pick_correct_body_at_runtime — class R
with instances at Int and Bool, both reachable from one main.
Stdout `11\n22` proves both mono fns are synthesised AND each
call site picks the matching body (no collapse).
- class_default_method_runs_and_prints_default_value — empty
instance body + class-level `default` clause must lower, link,
and print the default value (`99`). Promotes the existing
synthesise_mono_fn unit test to a binary-level guarantee.
- cross_module_class_method_runs_and_prints_correct_value —
class+instance in module A, called from module B's main. The
qualified-name rewrite + cross-module synth-def placement was
AST-tested in 22b.3.6; this pins down the emit-and-link path.
All 18 typeclass_22b3 tests pass; full workspace green; 3 bench
gates 0/0/0.
Quality-review follow-up to 89311ee:
- Revert `substitute_rigids` to private. Task 3 does not call it; the
visibility bump was forward-looking for Task 4 (synthesise_mono_fn).
Task 4 will re-bump it in the same diff that uses it.
- Promote `build_module_types` to pub(crate); drop the inline
reimplementation in `mono::build_workspace_env`. Removes drift risk
between the two paths.
- Replace defensive `unwrap_or_default()` on `build_module_globals`
with `expect(...)` naming the pre-condition. Silent default violated
CLAUDE.md "no defensive validation in internal code paths".
- Add a one-line drift-risk reminder at the top of `build_workspace_env`
flagging that it and `check_in_workspace` both populate `Env`.
Build clean; all 5 typeclass_22b3 tests green; no workspace regressions.
Float is referenced nowhere else in the codebase; the plan-text
listed it aspirationally. Restored parity with the four other
primitive enumerations across the crate. When Float lands as a
real type (post-22b.4 Prelude work, if at all), the arm gets
re-added in the same iter that introduces Type::float().
Deferred (not part of this fix):
- Shared-constant extraction across the four primitive enumeration
sites (linearity.rs:143, lib.rs:1232/2214, mono.rs) — workspace-
level cleanup belonging to the milestone-22 audit phase.
- pub mod mono vs sibling pub use lift::lift_letrecs style — plan
prescribed pub mod for 22b.3 because Tasks 3-7 add multiple pub
items reached by integration tests; per-item pub use would
proliferate.
Four new E2E tests in typeclass_22b2.rs covering invariants the
per-task fixtures (all single-module, single-fn) did not exercise:
- cross_module_class_method_resolves_and_fires_no_instance:
module B's call to `show` (declared in module A) reaches the
no-instance arm, proving Env.class_methods is merged workspace-
wide and not restricted to the local module.
- cross_module_polymorphic_fn_without_constraint_fires_missing_constraint:
same merge guarantees the missing-constraint arm sees the residual
produced by a call to A's class method from inside B.
- cross_module_instance_satisfies_concrete_call: the workspace
registry built at load time over all modules must populate keys
visible from B; a concrete call resolves clean cross-module.
- two_fns_each_missing_constraint_produce_two_diagnostics: pins
check_in_workspace's per-def aggregation — two ill-formed fns
produce two diagnostics (one per fn), not one.
Six new fixtures (xmod_* trio + two_fns_*).
Adds the missing positive counterpart to the no-instance test:
`show 42` with `instance Show Int` PRESENT must typecheck green.
This guards against registry-keying or threading bugs that would
otherwise sneak past the negative-only test (an empty registry at
typecheck time, or a key shape mismatch, would silently fire
no-instance even when the instance exists).
Also drops the dead `let _ = ws;` and its stale comment in
`check_in_workspace` — the line above already consumes
`ws.registry`, so the explicit drop is redundant. The remaining
comment is folded into the registry-threading comment so the
"registry is the only thing threaded" rationale is preserved.
Address quality review of a9aa248:
- Replace string-prefix discriminator (`prior.starts_with("class ")`)
with a structural `Origin { Class | Fn }` enum; `kind` is now a
`match` on variants, formatting is reserved for error construction.
- Fix fn-fn collision misreport: in the `Def::Fn` arm, only fire
`MethodNameCollision` when the prior origin is a class. Two
`Def::Fn` with the same name are `CheckError::DuplicateDef`'s job
in `ailang-check`, not this pre-pass.
- Tests use `examples_dir()` like the rest of the module; path
construction is no longer ad-hoc.
- Tests assert `first_origin` / `second_origin` so an origin-format
regression cannot silently break the `kind` discriminator.
- Drop forward-looking 22b.4 prelude reservation note from the
variant doc — keep only what's load-bearing for 22b.2.