Files
AILang/docs/journals/2026-05-12-iter-ctt.2.md
T

6.7 KiB

iter ctt.2 — Registry.type_def_module re-key to tuple-keyed map

Date: 2026-05-12 Started from: 548ebf8d51 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.jsontype Foo = MkLib + bare instance MyC Foo.
  • examples/ctt2_collision_main.ail.jsontype Foo = MkMain + bare instance MyC Foo, plus imports of both cls and lib.

Stats

bench/orchestrator-stats/2026-05-12-iter-ctt.2.json