diff --git a/bench/orchestrator-stats/2026-05-12-iter-ctt.1.json b/bench/orchestrator-stats/2026-05-12-iter-ctt.1.json new file mode 100644 index 0000000..ba21cdc --- /dev/null +++ b/bench/orchestrator-stats/2026-05-12-iter-ctt.1.json @@ -0,0 +1,16 @@ +{ + "iter_id": "ctt.1", + "date": "2026-05-12", + "mode": "standard", + "outcome": "DONE", + "tasks_total": 3, + "tasks_completed": 3, + "reloops_per_task": { + "1": 0, + "2": 0, + "3": 0 + }, + "review_loops_spec": 0, + "review_loops_quality": 0, + "blocked_reason": null +} diff --git a/crates/ailang-check/tests/duplicate_ctor_pin.rs b/crates/ailang-check/tests/duplicate_ctor_pin.rs new file mode 100644 index 0000000..614cabf --- /dev/null +++ b/crates/ailang-check/tests/duplicate_ctor_pin.rs @@ -0,0 +1,72 @@ +//! Pin for `CheckError::DuplicateCtor` — same-named ctors in two +//! different ADTs within one module. Ratifies the load-bearing +//! consumer of the per-module `env.ctor_index` rebuild in +//! `check_in_workspace` that DESIGN.md §"Env construction" rests +//! on as the asymmetry-with-mono-side argument. If the +//! per-module rebuild is ever removed without an equivalent +//! replacement path, this test goes red. + +use ailang_check::check_workspace; +use ailang_core::ast::{Ctor, Def, Module, TypeDef}; +use ailang_core::workspace::{Registry, Workspace}; +use ailang_core::SCHEMA; +use std::collections::BTreeMap; + +#[test] +fn duplicate_ctor_across_two_adts_in_one_module() { + let m = Module { + schema: SCHEMA.into(), + name: "M".into(), + imports: vec![], + defs: vec![ + Def::Type(TypeDef { + name: "Cat".into(), + vars: vec![], + ctors: vec![Ctor { + name: "Twins".into(), + fields: vec![], + }], + doc: None, + drop_iterative: false, + }), + Def::Type(TypeDef { + name: "Dog".into(), + vars: vec![], + ctors: vec![Ctor { + name: "Twins".into(), + fields: vec![], + }], + doc: None, + drop_iterative: false, + }), + ], + }; + let mut modules = BTreeMap::new(); + modules.insert(m.name.clone(), m.clone()); + let ws = Workspace { + entry: m.name.clone(), + modules, + root_dir: std::path::PathBuf::from("."), + registry: Registry::default(), + }; + + let diags = check_workspace(&ws); + let dup = diags + .iter() + .find(|d| d.code == "duplicate-ctor") + .unwrap_or_else(|| { + panic!( + "expected at least one `duplicate-ctor` diagnostic, got: {diags:?}" + ) + }); + assert!( + dup.message.contains("Twins"), + "expected message to name the duplicate ctor `Twins`, got: {}", + dup.message + ); + assert!( + dup.message.contains("Cat") && dup.message.contains("Dog"), + "expected message to name both ADTs `Cat` and `Dog`, got: {}", + dup.message + ); +} diff --git a/docs/DESIGN.md b/docs/DESIGN.md index 38a15c3..3a44023 100644 --- a/docs/DESIGN.md +++ b/docs/DESIGN.md @@ -1956,6 +1956,35 @@ with exactly one dot in the name is a qualified reference: `.`. Hash stability: no new AST node, no renamed fields — all previous module hashes stay bit-identical. +## Env construction + +The check environment carries two parallel per-module overlays for +ADT definitions: `env.types: IndexMap` is the +**owning index** — it stores the full definition keyed by bare type +name. `env.ctor_index: IndexMap` is the **reverse +index** — it maps each constructor name back to its owning type, so +ctor-by-name lookups run in O(1) rather than searching every type's +ctor list. + +The split is not a refactor wart. The two maps have distinct +semantic roles: an owning data index, and a derived lookup +accelerator over the same data. Collapsing them into one +variant-keyed map (`Map`) would force every +type-iterator and every ctor-lookup site to discriminate the variant +tag, reversing the optimisation the reverse index exists to +provide. + +The check-side per-module rebuild of both overlays at +`crates/ailang-check/src/lib.rs:1342-1384` is intentionally +retained: the in-band `DuplicateCtor` diagnostic at the same site +consumes the rebuilt per-module `env.ctor_index`. The mono-side +overlay was narrowed to types-only at iter ct.3.2 because its +consumer is the runtime ctor lookup, which became type-driven +post-ct.2.2 — a different consumer story, hence a different +overlay shape. The asymmetry between the check side and the mono +side is by design and is pinned by +`crates/ailang-check/tests/duplicate_ctor_pin.rs`. + ## Data model The on-disk JSON-AST is what the toolchain hashes, typechecks, and diff --git a/docs/journals/2026-05-12-iter-ctt.1.md b/docs/journals/2026-05-12-iter-ctt.1.md new file mode 100644 index 0000000..553dacb --- /dev/null +++ b/docs/journals/2026-05-12-iter-ctt.1.md @@ -0,0 +1,47 @@ +# iter ctt.1 — Env-overlay shape ratification + +**Date:** 2026-05-12 +**Started from:** 0a36294e61f08d8de85c084415d694d3736e0c67 +**Status:** DONE +**Tasks completed:** 3 of 3 + +## Summary + +Ratifies the two-overlay shape of the check environment (`env.types` ++ `env.ctor_index`) and pins its load-bearing consumer. The split +decision now has a DESIGN.md anchor (§"Env construction") explaining +the owning-index / reverse-index roles, the rationale for not +collapsing into one variant-keyed map, and the intentional asymmetry +with the mono side (narrowed to types-only at ct.3.2, because its +runtime-ctor-lookup consumer story differs from the check side's +in-band `DuplicateCtor` diagnostic). A new behavioural-pin test +(`crates/ailang-check/tests/duplicate_ctor_pin.rs`) asserts +`code == "duplicate-ctor"` plus message-substring coverage for a +two-ADT-same-ctor fixture, going red if the per-module rebuild at +`crates/ailang-check/src/lib.rs:1342-1384` is ever silently removed. +Two P2 roadmap todos struck `[x]` with one-line forward-references to +this iter. No production-code edits. `cargo test --workspace` green. + +## Per-task notes + +- iter ctt.1.1: New pinning test `crates/ailang-check/tests/duplicate_ctor_pin.rs` — builds a `Workspace` inline with one module declaring `type Cat = Twins` and `type Dog = Twins`, calls `check_workspace`, asserts a `Diagnostic` with `code == "duplicate-ctor"` plus message substring for `Twins` / `Cat` / `Dog`. PASS on first run, as the plan predicted (behaviour pre-exists). +- iter ctt.1.2: Inserted new top-level section `## Env construction` in `docs/DESIGN.md` between `## Convention: qualified cross-module references` (line 1941) and `## Data model` (now line 1988, was 1959 pre-edit). Three paragraphs: split definition, rationale, check-side / mono-side asymmetry. Cross-references the pinning test. +- iter ctt.1.3: Two `[ ]` → `[x]` strikes in `docs/roadmap.md` at lines 79 (overlay shape question) and 183 (per-module overlay narrowing). Each gains a one-line forward-reference suffix on its `context:` line pointing at iter ctt.1 and the closing artefact. + +## Concerns + +(none) + +## Known debt + +(none) + +## Files touched + +- `crates/ailang-check/tests/duplicate_ctor_pin.rs` (new) +- `docs/DESIGN.md` (modified — `## Env construction` section inserted) +- `docs/roadmap.md` (modified — two strikes) + +## Stats + +bench/orchestrator-stats/2026-05-12-iter-ctt.1.json diff --git a/docs/journals/INDEX.md b/docs/journals/INDEX.md index 3d351a6..4b18496 100644 --- a/docs/journals/INDEX.md +++ b/docs/journals/INDEX.md @@ -37,3 +37,4 @@ - 2026-05-12 — iter hs.4: IR + checker + linker wiring — `int_to_str : (Int) -> Str` installed in checker + synth.rs lockstep partner; IR-header preamble unconditionally declares both `@ailang_int_to_str` / `@ailang_float_to_str`; `Emitter::lower_app` gets new `int_to_str` arm and replaces `float_to_str`'s `CodegenError::Internal` with the actual call emission; `is_static_callee` whitelist extends to `int_to_str`; `runtime/rc.c` hoisted out of `AllocStrategy::Rc` arm to the unconditional link path (the `__attribute__((weak))` on str.c's `ailang_rc_alloc` extern becomes the documented no-op); 2 new IR-shape pins + 4 new E2E (2 stdout-smoke + 2 RC-stats) + 4 `.ail.json` fixtures; `drop.rs` Str-arm comment refreshed to dual-realisation framing; 5 IR snapshots regen for the 2 new declare lines. Status: DONE_WITH_CONCERNS — heap-Str RC-discipline incomplete (slabs leak at program end) because plan's literal `ret_mode: Implicit` interacts with uniqueness analyser walking `Term::Do` args as `Position::Consume`. Test asserts weakened from `allocs == frees && live == 0` to `allocs >= 1`. Substantive fix requires spec-level effect-op arg-mode decision (deferred, bounce-back to user) → 2026-05-12-iter-hs.4.md - 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 diff --git a/docs/roadmap.md b/docs/roadmap.md index 6aee916..a769398 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -76,11 +76,11 @@ context. Pick the next milestone from P1.)_ to make sure the indirection doesn't tank latency. - context: JOURNAL 2026-05-09 - depends on: Post-22 Prelude — Eq/Ord (shipped 23.5) -- [ ] **\[todo\]** `types` / `ctor_index` overlay shape question — +- [x] **\[todo\]** `types` / `ctor_index` overlay shape question — decide whether the env's two parallel ctor maps should collapse into one overlay, or stay split. Surfaced during the env-construction unify audit. - - context: JOURNAL 2026-05-10 ("Audit close: env-construction unify") + - context: JOURNAL 2026-05-10 ("Audit close: env-construction unify"); closed by iter ctt.1 — DESIGN.md §"Env construction" anchors the split decision. - [ ] **\[todo\]** CLI human-mode diagnostic surface for `WorkspaceLoadError` — non-JSON `ail check` routes ct.1 errors via `anyhow`/`thiserror` Display (`Error: `) instead of going through @@ -180,7 +180,7 @@ context. Pick the next milestone from P1.)_ - context: fieldtest 2026-05-11 — fieldtest fixtures could not be placed under `examples/fieldtest/` because of this; predates the canonical-type-names milestone but surfaces every time. -- [ ] **\[todo\]** `check_in_workspace` per-module overlay narrowing — +- [x] **\[todo\]** `check_in_workspace` per-module overlay narrowing — `crates/ailang-check/src/lib.rs:1234` still clears+rebuilds `env.ctor_index` per-module; ct.3.2 narrowed the analogous mono overlay to types-only. The typecheck-side overlay's `env.ctor_index` @@ -189,7 +189,7 @@ context. Pick the next milestone from P1.)_ Narrowing is mechanical but needs a careful read to confirm no other consumer survives. - context: JOURNAL 2026-05-11 ("Iteration ct.4") — milestone close - follow-up. + follow-up; closed by iter ctt.1 — recon confirmed the rebuild is the load-bearing consumer of in-band DuplicateCtor (pinned by `crates/ailang-check/tests/duplicate_ctor_pin.rs`); the asymmetry with the mono side is intentional. ## P3 — Ideas