plan: 22-tidy.4 primitive-name-set consolidation
This commit is contained in:
@@ -0,0 +1,338 @@
|
||||
# 22-tidy primitive-name-set consolidation — Implementation Plan
|
||||
|
||||
> **Parent spec:** `docs/specs/2026-05-09-22-typeclasses.md` (milestone 22, closed 2026-05-09)
|
||||
>
|
||||
> **Audit anchor:** `docs/JOURNAL.md` 2026-05-09 entry "Iteration 22-tidy: DESIGN.md and spec drift after milestone-22 close" → "Carried debt" → primitive-name-set consolidation.
|
||||
>
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: use `skills/implement`
|
||||
> to run this plan. Steps use `- [ ]` checkboxes for tracking.
|
||||
|
||||
**Goal:** retire the four duplicate `matches!(name.as_str(), "Int" | "Bool" | "Str" | "Unit")` sites by introducing one shared predicate `ailang_core::primitives::is_primitive_name(&str) -> bool`, and wire `mono::primitive_surface_name` through the same predicate so the primitive-name set has a single source of truth.
|
||||
|
||||
**Architecture:** a small module `crates/ailang-core/src/primitives.rs` exporting one predicate function plus one surface-name helper for callers that need the static-lifetime literal. Both consumer crates (`ailang-check`, `ailang-codegen`) already depend on `ailang-core`, so cross-crate consolidation is free. No behaviour change — every call site lands at the same boolean answer it does today.
|
||||
|
||||
**Tech Stack:** `crates/ailang-core` (new module), `crates/ailang-check/src/{lib.rs, linearity.rs, mono.rs}`, `crates/ailang-codegen/src/subst.rs`.
|
||||
|
||||
**Files this plan creates or modifies:**
|
||||
|
||||
- Create: `crates/ailang-core/src/primitives.rs` — single home for the primitive-name set; exports `pub fn is_primitive_name(&str) -> bool` and `pub fn primitive_surface_name(&str) -> Option<&'static str>`.
|
||||
- Modify: `crates/ailang-core/src/lib.rs` — add `pub mod primitives;` declaration.
|
||||
- Modify: `crates/ailang-check/src/linearity.rs:143` — replace inline `matches!` with helper call.
|
||||
- Modify: `crates/ailang-check/src/lib.rs:1279` — replace inline `matches!` with helper call.
|
||||
- Modify: `crates/ailang-check/src/lib.rs:2261` — replace inline `matches!` with helper call.
|
||||
- Modify: `crates/ailang-codegen/src/subst.rs:169` — replace inline `matches!` with helper call.
|
||||
- Modify: `crates/ailang-check/src/mono.rs:316-327` — `primitive_surface_name` delegates to the shared `primitive_surface_name`.
|
||||
- Modify: `docs/JOURNAL.md` — append iteration entry.
|
||||
|
||||
---
|
||||
|
||||
## Task 1: introduce the helper module in `ailang-core`
|
||||
|
||||
Single home for the primitive-name predicate and the surface-name mapping.
|
||||
|
||||
**Files:**
|
||||
- Create: `crates/ailang-core/src/primitives.rs`
|
||||
- Modify: `crates/ailang-core/src/lib.rs` (one-line `pub mod primitives;`)
|
||||
|
||||
- [ ] **Step 1: write `crates/ailang-core/src/primitives.rs`**
|
||||
|
||||
```rust
|
||||
//! The primitive-name set: `Int`, `Bool`, `Str`, `Unit`. Single
|
||||
//! source of truth for "is this name a built-in zero-arity type
|
||||
//! constructor?" Every typecheck and codegen site that gates
|
||||
//! behaviour on the primitive set goes through this module.
|
||||
//!
|
||||
//! Adding a primitive: append to BOTH functions below; the four
|
||||
//! consumers compile-fail-loudly if the predicate is forgotten
|
||||
//! (the failure is a missed branch, not a panic), but
|
||||
//! `primitive_surface_name` will silently return `None` for the
|
||||
//! new name unless its `match` is extended too. Keep the two
|
||||
//! functions in lockstep.
|
||||
|
||||
/// Returns `true` iff `name` is one of the built-in zero-arity
|
||||
/// primitive type constructors. Cross-crate predicate consumed by
|
||||
/// `ailang-check` (`linearity::is_heap_type`, `lib::check_type_well_formed`,
|
||||
/// `lib::qualify_local_types`) and `ailang-codegen`
|
||||
/// (`subst::qualify_local_types_codegen`).
|
||||
pub fn is_primitive_name(name: &str) -> bool {
|
||||
matches!(name, "Int" | "Bool" | "Str" | "Unit")
|
||||
}
|
||||
|
||||
/// Returns the static-lifetime surface name iff `name` is a
|
||||
/// primitive. Used by `mono::primitive_surface_name` to embed the
|
||||
/// human-readable form in monomorphised symbol names; the static
|
||||
/// lifetime is what makes the symbol-builder's `&'static str`
|
||||
/// signature work.
|
||||
pub fn primitive_surface_name(name: &str) -> Option<&'static str> {
|
||||
match name {
|
||||
"Int" => Some("Int"),
|
||||
"Bool" => Some("Bool"),
|
||||
"Str" => Some("Str"),
|
||||
"Unit" => Some("Unit"),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: register the module in `ailang-core::lib.rs`**
|
||||
|
||||
Locate the `pub mod` block in `crates/ailang-core/src/lib.rs` (currently lines 57-62: `ast`, `canonical`, `desugar`, `hash`, `pretty`, `workspace`). Insert `pub mod primitives;` in alphabetical position between `pretty` and `workspace`:
|
||||
|
||||
```rust
|
||||
pub mod ast;
|
||||
pub mod canonical;
|
||||
pub mod desugar;
|
||||
pub mod hash;
|
||||
pub mod pretty;
|
||||
pub mod primitives;
|
||||
pub mod workspace;
|
||||
```
|
||||
|
||||
- [ ] **Step 3: build clean**
|
||||
|
||||
Run: `cargo build --workspace 2>&1 | tail -5`
|
||||
Expected: builds clean. New module compiles; no dead-code warning (next task wires consumers).
|
||||
|
||||
- [ ] **Step 4: commit**
|
||||
|
||||
```bash
|
||||
git add crates/ailang-core/src/primitives.rs crates/ailang-core/src/lib.rs
|
||||
git commit -m "iter 22-tidy.4: ailang-core::primitives — single home for the primitive-name set"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 2: wire the four `matches!` consumer sites through the helper
|
||||
|
||||
Replaces every duplicate `matches!(name.as_str(), "Int" | "Bool" | "Str" | "Unit")` (or order-equivalent) with `ailang_core::primitives::is_primitive_name(name)`. The four sites are independent — bundling them in one task because the change is mechanical and any single-site commit would leave a dead-code warning on `is_primitive_name` until the rest land.
|
||||
|
||||
**Files:**
|
||||
- Modify: `crates/ailang-check/src/linearity.rs:143`
|
||||
- Modify: `crates/ailang-check/src/lib.rs:1279`
|
||||
- Modify: `crates/ailang-check/src/lib.rs:2261`
|
||||
- Modify: `crates/ailang-codegen/src/subst.rs:169`
|
||||
|
||||
- [ ] **Step 1: replace `linearity.rs:143`**
|
||||
|
||||
In `crates/ailang-check/src/linearity.rs`, change:
|
||||
|
||||
```rust
|
||||
fn is_heap_type(t: &Type) -> bool {
|
||||
match t {
|
||||
Type::Con { name, .. } => !matches!(name.as_str(), "Int" | "Bool" | "Str" | "Unit"),
|
||||
```
|
||||
|
||||
to:
|
||||
|
||||
```rust
|
||||
fn is_heap_type(t: &Type) -> bool {
|
||||
match t {
|
||||
Type::Con { name, .. } => !ailang_core::primitives::is_primitive_name(name),
|
||||
```
|
||||
|
||||
- [ ] **Step 2: replace `lib.rs:1279`**
|
||||
|
||||
In `crates/ailang-check/src/lib.rs:1279` (inside `check_type_well_formed`), change:
|
||||
|
||||
```rust
|
||||
let is_primitive = matches!(name.as_str(), "Int" | "Bool" | "Unit" | "Str");
|
||||
```
|
||||
|
||||
to:
|
||||
|
||||
```rust
|
||||
let is_primitive = ailang_core::primitives::is_primitive_name(name);
|
||||
```
|
||||
|
||||
- [ ] **Step 3: replace `lib.rs:2261`**
|
||||
|
||||
In `crates/ailang-check/src/lib.rs:2261` (inside `qualify_local_types`), change:
|
||||
|
||||
```rust
|
||||
} else if matches!(name.as_str(), "Int" | "Bool" | "Unit" | "Str") {
|
||||
```
|
||||
|
||||
to:
|
||||
|
||||
```rust
|
||||
} else if ailang_core::primitives::is_primitive_name(name) {
|
||||
```
|
||||
|
||||
- [ ] **Step 4: replace `subst.rs:169`**
|
||||
|
||||
In `crates/ailang-codegen/src/subst.rs:169` (inside `qualify_local_types_codegen`), change:
|
||||
|
||||
```rust
|
||||
} else if matches!(name.as_str(), "Int" | "Bool" | "Unit" | "Str") {
|
||||
```
|
||||
|
||||
to:
|
||||
|
||||
```rust
|
||||
} else if ailang_core::primitives::is_primitive_name(name) {
|
||||
```
|
||||
|
||||
- [ ] **Step 5: build clean**
|
||||
|
||||
Run: `cargo build --workspace 2>&1 | tail -5`
|
||||
Expected: builds clean. The `is_primitive_name` helper now has four consumers; no dead-code warning.
|
||||
|
||||
- [ ] **Step 6: workspace test sweep**
|
||||
|
||||
Run: `cargo test --workspace 2>&1 | grep -E "^(test result|FAILED)" | tail -25`
|
||||
Expected: every `test result: ok.` line; no `FAILED`. Total test count unchanged at 345.
|
||||
|
||||
- [ ] **Step 7: commit**
|
||||
|
||||
```bash
|
||||
git add crates/ailang-check/src/linearity.rs crates/ailang-check/src/lib.rs crates/ailang-codegen/src/subst.rs
|
||||
git commit -m "iter 22-tidy.4: route 4 matches! sites through is_primitive_name"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 3: route `mono::primitive_surface_name` through the shared helper
|
||||
|
||||
`mono.rs:316-327` is structurally different — it returns `Option<&'static str>` and pattern-matches on a `Type::Con { name, args } if args.is_empty()` to gate on zero-arity. Keep that gating; delegate the inner mapping to `ailang_core::primitives::primitive_surface_name`.
|
||||
|
||||
**Files:**
|
||||
- Modify: `crates/ailang-check/src/mono.rs:316-327`
|
||||
|
||||
- [ ] **Step 1: replace the body**
|
||||
|
||||
In `crates/ailang-check/src/mono.rs`, replace:
|
||||
|
||||
```rust
|
||||
/// Iter 22b.3: returns the surface name iff `ty` is a zero-arity
|
||||
/// primitive `Type::Con`. Used by [`mono_symbol`] to gate the
|
||||
/// human-readable form. The match is intentionally narrow:
|
||||
/// `Int<args>` (which is malformed but parser-accepting) is
|
||||
/// treated as compound, so it falls to the hash form.
|
||||
fn primitive_surface_name(ty: &Type) -> Option<&'static str> {
|
||||
match ty {
|
||||
Type::Con { name, args } if args.is_empty() => match name.as_str() {
|
||||
"Int" => Some("Int"),
|
||||
"Bool" => Some("Bool"),
|
||||
"Str" => Some("Str"),
|
||||
"Unit" => Some("Unit"),
|
||||
_ => None,
|
||||
},
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
with:
|
||||
|
||||
```rust
|
||||
/// Returns the surface name iff `ty` is a zero-arity primitive
|
||||
/// `Type::Con`. Used by [`mono_symbol`] to gate the human-readable
|
||||
/// form. The match is intentionally narrow: `Int<args>` (malformed
|
||||
/// but parser-accepting) is treated as compound, so it falls to
|
||||
/// the hash form. The primitive-set itself lives in
|
||||
/// [`ailang_core::primitives::primitive_surface_name`].
|
||||
fn primitive_surface_name(ty: &Type) -> Option<&'static str> {
|
||||
match ty {
|
||||
Type::Con { name, args } if args.is_empty() => {
|
||||
ailang_core::primitives::primitive_surface_name(name)
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: build clean**
|
||||
|
||||
Run: `cargo build --workspace 2>&1 | tail -5`
|
||||
Expected: builds clean.
|
||||
|
||||
- [ ] **Step 3: workspace test sweep + bench gates 0/0/0 + targeted regressions**
|
||||
|
||||
Run: `cargo test --workspace 2>&1 | grep -E "^(test result|FAILED)" | tail -25`
|
||||
Expected: every `test result: ok.` line; no `FAILED`. 345 tests.
|
||||
|
||||
Run: `cargo test --workspace --test typeclass_22c 2>&1 | tail -5`
|
||||
Expected: `user_class_instance_over_user_adt_builds_and_runs ... ok` (this test runs the mono pass, exercising `primitive_surface_name` through real fixtures).
|
||||
|
||||
- [ ] **Step 4: commit**
|
||||
|
||||
```bash
|
||||
git add crates/ailang-check/src/mono.rs
|
||||
git commit -m "iter 22-tidy.4: mono::primitive_surface_name delegates to ailang-core"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 4: verify + JOURNAL entry
|
||||
|
||||
**Files:**
|
||||
- Modify: `docs/JOURNAL.md`
|
||||
|
||||
- [ ] **Step 1: full workspace test sweep**
|
||||
|
||||
Run: `cargo test --workspace 2>&1 | grep -E "^test result" | wc -l`
|
||||
Expected: ≥ 23 (count of test-binary `test result:` lines; the workspace already had 23 such lines in the env-construction unify close).
|
||||
|
||||
Run: `cargo test --workspace 2>&1 | grep -c "FAILED"`
|
||||
Expected: `0`.
|
||||
|
||||
- [ ] **Step 2: bench gates**
|
||||
|
||||
Run: `python3 bench/check.py 2>&1 | tail -3`
|
||||
Expected: `0 regressed` in the summary line.
|
||||
|
||||
Run: `python3 bench/compile_check.py 2>&1 | tail -3`
|
||||
Expected: `0 regressed`.
|
||||
|
||||
Run: `python3 bench/cross_lang.py 2>&1 | tail -3`
|
||||
Expected: `0 regressed`.
|
||||
|
||||
- [ ] **Step 3: confirm zero remaining duplicates**
|
||||
|
||||
Run: `grep -rn 'matches!(name.as_str(), "Int" | "Bool"\|matches!(name.as_str(), "Bool" | "Int"\|matches!(name, "Int" | "Bool"' crates/ --include="*.rs" 2>&1 | grep -v primitives.rs | head -10`
|
||||
Expected: empty (all four duplicate `matches!` sites have been routed; only `crates/ailang-core/src/primitives.rs` retains the literal set).
|
||||
|
||||
- [ ] **Step 4: append JOURNAL entry**
|
||||
|
||||
Append to `docs/JOURNAL.md`:
|
||||
|
||||
```markdown
|
||||
## 2026-05-10 — Iteration 22-tidy.4: primitive-name-set consolidation
|
||||
|
||||
Closing the milestone-22 carried-debt item flagged in the 22b.3
|
||||
JOURNAL and re-flagged at milestone-22 audit close: the
|
||||
primitive-name set `{Int, Bool, Str, Unit}` was duplicated across
|
||||
five sites (`crates/ailang-codegen/src/subst.rs:169`,
|
||||
`crates/ailang-check/src/linearity.rs:143`,
|
||||
`crates/ailang-check/src/lib.rs:1279` and `:2261`,
|
||||
`crates/ailang-check/src/mono.rs:316-327`).
|
||||
|
||||
This iteration introduces `crates/ailang-core/src/primitives.rs`
|
||||
exporting `is_primitive_name(&str) -> bool` plus
|
||||
`primitive_surface_name(&str) -> Option<&'static str>`. The four
|
||||
`matches!` consumer sites now route through the predicate; the
|
||||
mono-pass surface-name helper retains its outer zero-arity gating
|
||||
and delegates the inner mapping to the same module.
|
||||
|
||||
Tasks (commit subjects):
|
||||
|
||||
- 22-tidy.4: `ailang-core::primitives` — single home for the
|
||||
primitive-name set
|
||||
- 22-tidy.4: route 4 matches! sites through is_primitive_name
|
||||
- 22-tidy.4: mono::primitive_surface_name delegates to ailang-core
|
||||
|
||||
Acceptance: 4 `matches!` sites + 1 mono.rs site routed; full
|
||||
workspace test sweep 345 green; bench gates 0/0/0; grep confirms
|
||||
no remaining duplicate predicate. The audit-flagged milestone-22
|
||||
carried-debt item closes; the remaining carried items
|
||||
(parse-fn/data/const strict duplicate-clause detection, Form-B
|
||||
prose printer arms for ClassDef/InstanceDef, defensive lib.rs
|
||||
gap-related sites) stay queued.
|
||||
```
|
||||
|
||||
- [ ] **Step 5: commit**
|
||||
|
||||
```bash
|
||||
git add docs/JOURNAL.md
|
||||
git commit -m "iter 22-tidy.4: journal entry"
|
||||
```
|
||||
Reference in New Issue
Block a user