iter ctt.2: Registry.type_def_module re-key to (owning_module, bare_name)

This commit is contained in:
2026-05-12 22:23:46 +02:00
parent 548ebf8d51
commit 9d01d0884c
10 changed files with 385 additions and 40 deletions
+142
View File
@@ -0,0 +1,142 @@
# iter ctt.2 — `Registry.type_def_module` re-key to tuple-keyed map
**Date:** 2026-05-12
**Started from:** 548ebf8d51758ab9f156b266e0baa59c78b14034
**Status:** DONE
**Tasks completed:** 3 of 3
## Summary
`Registry.type_def_module` re-keyed from `BTreeMap<String, String>` to
`BTreeMap<(String, String), String>` keyed by `(owning_module,
bare_name)`. A new `caller_module: &str` parameter threads through
`normalize_type_for_registry` and `Registry::normalize_type_for_lookup`,
and all four `ailang-check` consumer sites pass the correct
module-context per the plan's Design Notes block. A regression test
plus a three-module fixture exercise the bare-name collision the
re-key fixes — two modules each declaring `type Foo` now register
under distinct canonical keys instead of tripping the silent-overwrite
defect. `cargo test --workspace` green (16 binary-test suites,
~600 tests total, zero failures).
## Per-task notes
- iter ctt.2.1: RED-first — three fixture JSON files
(`ctt2_collision_{cls,lib,main}.ail.json`) plus
`crates/ailang-core/tests/ctt2_registry_rekey.rs`. Test goes
RED today. Failure shape was `OrphanInstance` rather than the
plan's predicted `DuplicateInstance` (a) or missing-hash (b) —
see Concerns.
- iter ctt.2.2: GREEN — atomic re-key edit pass. Touched the field
type + doc-comment, `Registry::normalize_type_for_lookup`
signature + body, pass-1 declaration + insert,
pass-2 coherence-check lookup + canonical-form hashing call,
`normalize_type_for_registry` signature + doc-comment + body
(recursive calls thread caller_module everywhere), the inline
`ct1_5a_normalize_recurses_into_forall_constraints` test
at workspace.rs:2543, and all four `ailang-check` consumer sites
at the module-context dictated by the plan's Design Notes
(lib.rs:1640 → `env.current_module.as_str()`, mono.rs:121 →
`defining_module.as_str()`, mono.rs:624 and :1175 → `module_name`).
Clean `cargo build --workspace` at the end of the pass.
- iter ctt.2.3: workspace-wide test gate. New test goes GREEN.
Full `cargo test --workspace` green, no test in any crate
regressed. The plan's Step 2 hypothesis ("a fixture under
`examples/` might legitimately exercise a bare cross-module
type reference") did not materialise — the existing corpus
uses canonical-form (qualified) cross-module type refs
exclusively, so the tightening had no surface.
## Concerns
- Task 1 RED-failure shape diverged from the plan's predicted
shapes (a) `DuplicateInstance` and (b) missing-`contains_key`.
The plan's verbatim two-module fixture pair (lib imports main +
main imports lib + lib-side instance with qualified class
`ctt2_collision_main.MyC`) had two independent structural
defects against the loaded codebase invariants:
1. The workspace loader rejects import cycles
(`WorkspaceLoadError::Cycle`) at module-name granularity —
the plan's claim "Two-way import is supported; workspace
loader DFS handles cycles at module-name granularity" is
empirically false. Confirmed by inspection at
`workspace.rs:1410-1413` and `:1451-1454`.
2. `validate_canonical_type_names` rejects qualified
`InstanceDef.class` with `QualifiedClassName` (see test
`ct1_validator_rejects_qualified_instancedef_class` at
workspace.rs:2321) — the plan's lib-side instance with
`class: "ctt2_collision_main.MyC"` would have been rejected
even without the cycle.
Implementer-phase repair: introduced a third module
`ctt2_collision_cls.ail.json` carrying `class MyC`, broke the
cycle (both lib and main import cls only; main also imports
lib so the loader DFS reaches it), and dropped the qualifier
from both instance heads (both declare bare `class: "MyC"`
satisfying coherence via the type's module). The plan's
*core* RED-condition is preserved: two modules each declaring
`type Foo` with `instance MyC Foo` collide on the pre-ctt.2
bare-name `type_def_module` key.
The actually-observed RED shape was `OrphanInstance` rather
than `DuplicateInstance` because the pass-2 coherence check
(`workspace.rs:656-659`) uses the bare-name lookup *before*
the duplicate-uniqueness check fires — lib's `Foo` and main's
`Foo` both resolve to whichever module won the alphabetical
race (`ctt2_collision_main` beats `ctt2_collision_lib`
lexicographically), so lib's instance is no longer coherent
("type lives in main, class lives in cls, instance lives in
lib — neither matches"). The re-key fixes this consumer site
simultaneously with the duplicate-check site because both go
through the same `type_def_module` lookup, now tuple-keyed.
The deviation from the plan's verbatim fixtures is an
implementer-phase NEEDS_CONTEXT repair that the
user-no-stop-instruction permitted in-line; the plan's *intent*
(RED-first against the bare-name collision) is achieved and
the GREEN-state matches the plan's acceptance criteria
unchanged. Boss may want to back-edit the plan or amend the
spec to note the multi-module fixture shape if this kind of
collision regression returns later.
## Known debt
- The `OrphanInstance` consumer site at `workspace.rs:656-659`
is fixed by ctt.2 but the diagnostic-shape change (orphan
instead of duplicate-on-loser) is not separately tested. The
new test asserts the GREEN state directly (both entries land
under distinct keys) which transitively exercises both
consumer sites, but neither the orphan nor the duplicate
diagnostic shapes are pinned by a dedicated RED test. Adding
one is plausibly out-of-scope for ctt.2.
## Files touched
Modified (3):
- `crates/ailang-check/src/lib.rs` — single call-site update at
the NoInstance check inside `check_fn`.
- `crates/ailang-check/src/mono.rs` — three call-site updates
(monomorphise_workspace at :121, collect_mono_targets at :624,
collect_residuals_ordered at :1175).
- `crates/ailang-core/src/workspace.rs` — field re-key,
`Registry::normalize_type_for_lookup` signature,
`normalize_type_for_registry` signature + body,
pass-1 declaration + insert,
pass-2 coherence-check lookup + canonical-form hashing call,
inline test `ct1_5a_normalize_recurses_into_forall_constraints`
at :2543; doc-comments at field-decl and at both
`normalize_…` fns.
Added (4):
- `crates/ailang-core/tests/ctt2_registry_rekey.rs` — regression test.
- `examples/ctt2_collision_cls.ail.json` — class-bearing module
(not in plan; required to break the cycle in the plan's
verbatim fixture design).
- `examples/ctt2_collision_lib.ail.json``type Foo = MkLib` +
bare `instance MyC Foo`.
- `examples/ctt2_collision_main.ail.json``type Foo = MkMain` +
bare `instance MyC Foo`, plus imports of both `cls` and `lib`.
## Stats
bench/orchestrator-stats/2026-05-12-iter-ctt.2.json
+1
View File
@@ -38,3 +38,4 @@
- 2026-05-12 — iter eob.1: Effect-op args walked as Borrow (uniqueness.rs + linearity.rs + linearity.rs doc-comment); heap-Str RC discipline closes (int_to_str / float_to_str `ret_mode: Implicit``Own` at 4 lockstep sites across builtins.rs + synth.rs; `drop_symbol_for_binder` App-arm gets `Str` carve-out symmetric to `field_drop_call`'s existing one); 2 pre-existing RED tests at e2e.rs (commit 592d87b) flipped to GREEN unchanged with predicted concrete numbers (`allocs==1,frees==1,live==0` and `allocs==2,frees==2,live==0`); 2 new positive tests + fixtures (primitive-Int through `io/print_int`: zero RC traffic; heap-Str repeated borrow through 2× `io/print_str`: `allocs==1,frees==1,live==0`); DESIGN.md §Decision 10 anchors both arg-position rules together (Ctor=Consume, Do=Borrow, plus a paragraph linking Do=Borrow to ret_mode==Own letbinder-trackability); WhatsNew entry shipped, roadmap P1 `[milestone] Heap-Str ABI` checked off, Post-22-Prelude `depends on:` line removed; 7/7 tasks first-shot, zero review re-loops; lint side-effect surface (over-strict-mode on (own T) params used solely via effect-ops) was empty for the current corpus → 2026-05-12-iter-eob.1.md
- 2026-05-12 — audit-eob: milestone close (heap-str-abi) — architect drift fixed inline as `eob.tidy` (3 DESIGN.md edits: float_to_str / int_to_str caveats dropped, "Str ABI" anchor added documenting both heap-Str and static-Str realisations with their shared consumer ABI and codegen-level non-RC invariant for static-Str); bench mixed (latency.explicit_at_rc improvement cluster reappears for the 3rd consecutive audit + new marginal bump_s regressions on list_sum/hof_pipeline 12% over 10% tol), baseline pristine for 3rd consecutive audit (conservative call: latency cluster has no identified cause across 3 audits — ratify-without-attribution would obscure future signal; bump_s cluster is first-sighting, observe next audit) → 2026-05-12-audit-eob.md
- 2026-05-12 — iter ctt.1: env-overlay shape ratification — new DESIGN.md top-level section `## Env construction` anchors the `env.types` (owning) / `env.ctor_index` (reverse-index) split with the semantic rationale, plus the intentional check-side / mono-side asymmetry (mono-side narrowed at ct.3.2, check-side retains both halves because in-band `DuplicateCtor` consumes the per-module `env.ctor_index` rebuild); new behavioural-pin test `crates/ailang-check/tests/duplicate_ctor_pin.rs` ratifies the consumer; two P2 roadmap todos struck `[x]` (overlay shape question, per-module overlay narrowing); 3/3 tasks first-shot, zero review re-loops, no production-code edits → 2026-05-12-iter-ctt.1.md
- 2026-05-12 — iter ctt.2: `Registry.type_def_module` re-key from `BTreeMap<String, String>` to `BTreeMap<(String, String), String>` keyed by `(owning_module, bare_name)`; new `caller_module: &str` parameter threads through `normalize_type_for_registry` + `Registry::normalize_type_for_lookup`; four `ailang-check` consumer sites (lib.rs:1640, mono.rs:121/624/1175) pass the correct module-context (env.current_module / defining_module / module_name) per the plan's Design Notes; new regression test plus three-module fixture (`ctt2_collision_{cls,lib,main}.ail.json`) exercises the cross-module bare-name collision shape that pre-ctt.2 silently overwrote; RED-failure observed as `OrphanInstance` rather than the plan's predicted `DuplicateInstance` because the pass-2 coherence check (workspace.rs:656-659) is also a bare-name consumer and fires earlier; both consumers fixed by the same re-key; plan's verbatim two-module fixture had two structural defects (two-way imports → Cycle, qualified `InstanceDef.class` → QualifiedClassName) that the implementer-phase repair handled by introducing the third cls-module; spec-intent (RED-first against bare-name collision) preserved; `cargo test --workspace` green (16 binary-test suites, ~600 tests, zero failures) → 2026-05-12-iter-ctt.2.md