iter ctt.1: env-overlay shape ratification + DuplicateCtor pin

This commit is contained in:
2026-05-12 22:07:39 +02:00
parent 0a36294e61
commit 805bba3fda
6 changed files with 169 additions and 4 deletions
@@ -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
}
@@ -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
);
}
+29
View File
@@ -1956,6 +1956,35 @@ with exactly one dot in the name is a qualified reference: `<prefix>.<def>`.
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<String, TypeDef>` is the
**owning index** — it stores the full definition keyed by bare type
name. `env.ctor_index: IndexMap<String, CtorRef>` 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<Name, EntryKind>`) 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
+47
View File
@@ -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
+1
View File
@@ -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
+4 -4
View File
@@ -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: <message>`) 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