iter 23.2.5: journal entry — Eq class + primitive instances

This commit is contained in:
2026-05-10 22:51:29 +02:00
parent a11cb2f144
commit 24f900194f
+123
View File
@@ -13944,3 +13944,126 @@ tests + the new ail-crate E2E + the new workspace-loader tests,
all green. Cross-language and the three regression scripts not
re-run for this iter (no codegen-shape change to the existing
fixtures); they'll run at milestone-23 close.
## 2026-05-10 — Iteration 23.2: Eq class + three primitive instances
Second iteration of milestone 23. Ships `class Eq a where eq : (a
borrow, a borrow) -> Bool` in the auto-loaded prelude plus three
primitive instances (Eq Int, Eq Bool, Eq Str). A user program that
calls `eq x y` on any of those three primitives now monomorphises
to `eq__Int` / `eq__Bool` / `eq__Str` synthesised in the prelude
module and codegen lowers them as:
- `eq__Int``icmp eq i64` via existing `lower_eq` Int arm.
- `eq__Bool``icmp eq i1` via existing `lower_eq` Bool arm.
- `eq__Str``call zeroext i1 @ail_str_eq(...)` via a new codegen
intercept (`try_emit_primitive_instance_body` in
`crates/ailang-codegen/src/lib.rs`).
The Str path is the only one needing new mechanism: a hand-rolled
body in `emit_fn` that bypasses normal lambda lowering and calls
the new C-runtime primitive `ail_str_eq` from `runtime/str.c`.
`runtime/str.c` is linked unconditionally for every alloc strategy
(Gc, Bump, Rc) — the `@ail_str_eq` IR declaration is unconditional
so the symbol must always resolve at link time; clang -O2 dead-
strips it when no caller exists. `==` on Str stays on `@strcmp +
icmp eq i32 0` this iter; only the mono-synthesised `eq__Str`
goes through `@ail_str_eq`. Routing primitive `==` through the
class methods is the declared P2 follow-up.
**Two latent bugs surfaced.** Adding `class Eq` to the prelude
flipped the `workspace_has_typeclasses` gate from false to true
on every test workspace, exposing two long-dormant gaps:
1. **`mono::build_workspace_env` ctor_index was workspace-flat.**
`check_in_workspace` clears the flat ctor_index after
`build_check_env` and rebuilds it per-module (lib.rs:1257-1287)
so `Pattern::Ctor` resolution at lib.rs:2486-2526 produces a
qualified `resolved_type_name` via the imports-fallback that
matches the scrutinee's qualified `Type::Con`. The mono entry
points (`collect_targets_workspace_wide`, the phase-3 rewrite
loop) consumed the flat-index env directly, so cross-module
`Cons` patterns resolved against bare `"List"` and mismatched
the qualified `"std_list.List"`. Fix in `84dcc46`: per-module
ctor_index overlay helper in mono.rs, applied at both sites.
Empirically `env.types` also had to be overlayed (the carrier's
"asymmetry" note was wrong — kept flat, the Term::Ctor and
Pattern::Ctor synth paths disagree on bare-vs-qualified
names). RED test: `crates/ail/tests/mono_xmod_ctor_pattern.rs`
(`e580f75`). Same family as commits 13b36cc / 5c5180f.
2. **Codegen `lower_workspace_inner` import_map missed implicit
prelude.** Symmetric to the typechecker's implicit-prelude
injection at `build_check_env` / `check_in_workspace` (Iter
23.1.3). When Iter 22b.3 monomorphisation rewrites a user
call `eq x y` to the cross-module symbol `prelude.eq__Int`,
`lower_call`'s prefix resolution looked up `"prelude"` in the
codegen import_map and found it missing — error
`cross-module call 'prelude.eq__Int': prefix 'prelude' not
in import map`. Fix in `be882c4`: mirror the typechecker's
guard (`m.name != "prelude"`) and inject the implicit entry.
**Four-site lockstep now confirmed.** Iter 23.1 flagged a
three-site lockstep for implicit-prelude visibility (Pattern::Ctor
15a / Term::Ctor typecheck / `lookup_ctor_by_type` codegen).
Iter 23.2 just added the fourth site:
`lower_workspace_inner` import_map. The lockstep table is now
ripe for the architect-iron-law extension that already covers
`lower_app ↔ is_static_callee` and `Pattern::Lit::Float reject ↔
pre_desugar_validation`. Adding it next time the architect role
takes an iter.
**Fixture rename ripple.** Six existing test fixtures used a
local `class Eq` (some with `class Ord extends Eq`) for typeclass-
machinery tests. With the prelude's `class Eq.eq` injected, the
fixture-side methods collide on the global method-name uniqueness
check (`MethodNameCollision`). Five out of six were currently
visible failures (two surfaced first, three more after the
mono ctor_index bug was fixed). Renamed the colliding classes
and methods to `TEq` / `teq` / `TOrd` / `tlt` across the 6
JSON fixtures (the prose snapshot for one of them too) and
updated the corresponding test assertions in workspace.rs. The
`iter22b1_workspace_with_no_classes_has_empty_registry` and
`iter22b1_instance_in_class_module_loads_clean` tests were
rewritten to filter `defining_module == "prelude"` rather than
counting raw registry entries — they now assert "no NON-prelude
entries" / "exactly one non-prelude entry", which is the
property they always meant.
Coverage at iter close:
- Codegen unit + integration tests: `eq__Str`-intercept-shape
test, `eq__Str` closure-adapter test, three IR-shape tests
on the smoke fixture (`@ail_prelude_eq__Int` + `icmp eq i64`,
Bool / i1, Str / `call zeroext i1 @ail_str_eq`).
- E2E: `examples/eq_primitives_smoke.ail.json`
`eq_primitives_smoke_compiles_and_runs` (full pipeline:
AST → typecheck → mono → codegen → clang → binary → stdout
`"1\n0\n1\n0\n1\n0\n"`).
- Mono RED-test: `mono_xmod_ctor_pattern.rs` (guards the
per-module ctor_index overlay against regression).
- Prelude-load assertion extended (`loads_workspace_auto_injects_prelude`)
to also check `class Eq` + Eq Int/Bool/Str instances.
Per-task commits:
- `cc2d694` iter 23.2.1: runtime/str.c with ail_str_eq + unconditional link
- `736064a` iter 23.2.2: codegen — declare @ail_str_eq + eq__Str body intercept
- `c6168ad` iter 23.2.2-fixup: emit closure adapter for primitive-instance-bodied fns
- `1618182` iter 23.2.2-fixup-doc: tighten try_emit_primitive_instance_body contract doc
- `e580f75` RED test (debug): mono_xmod_ctor_pattern.rs pins flat-ctor_index bug
- `84dcc46` fix: mono.rs per-module env.ctor_index overlay (same family as 13b36cc / 5c5180f)
- `7289bbc` iter 23.2.3-prep: reconcile workspace tests with auto-loaded prelude class Eq
- `65ab6c6` iter 23.2.3-prep2: rename remaining two 22b2 fixtures + their prose snapshot (orchestrator-inline)
- `7172e7e` iter 23.2.3: prelude — class Eq a + Eq Int/Bool/Str instances
- `be882c4` fix: codegen lower_workspace_inner implicit prelude import (4th lockstep site)
- `559e591` iter 23.2.4: e2e smoke fixture + ir-shape integration tests for eq__T mono symbols
- `a11cb2f` iter 23.2.4-fixup: symmetrize check_workspace error assertion across three IR-shape tests
Out of scope for this iter (covered by later 23.x):
- Ord class + three Ord instances + `ail_str_compare` (23.3).
- Free top-level utility functions `ne` / `lt` / `le` / `gt` / `ge` (23.4).
- Float-NoInstance diagnostic + DESIGN.md amendment (23.5).
Workspace at iter-23.2 close: full `cargo test --workspace` green
(416 tests, 0 failed). Cross-language and the three regression
scripts not re-run for this iter; they run at milestone-23 close.