832375f2ac
All 176 files in the four accumulating directories now use a zero-padded 4-digit counter prefix that reflects creation order (`NNNN-slug.md`). The counter is assigned per directory in strict git-log creation order; ties broken alphabetically by original name. The old `YYYY-MM-DD-` prefix on docs/specs/ and docs/plans/ files is dropped — the date is recoverable from git log and the counter carries the ordering. A file's counter is stable for the life of the file: never reassigned, never reused, never compacted. Deleted files retire their counter; subsequent files do not fill the gap. This is the property that lets cross-references stay literal — refs use the full filename including the counter (`design/contracts/0007-honesty-rule.md`) so they grep cleanly and resolve directly without a glob step. 313 cross-references updated across .md/.rs/.toml/.c/.json files (test pins, include_str! paths, design-INDEX entries, baseline notes, runtime C comments, inter-contract markdown links incl. bare basename and `../models/foo.md` forms). CLAUDE.md gets a new "File-naming convention" section spelling out the rule and rationale. skills/brainstorm/SKILL.md and skills/planner/SKILL.md updated so new spec/plan creation produces counter-prefixed names from the start. The full test suite (cargo test --workspace) passes.
743 lines
27 KiB
Markdown
743 lines
27 KiB
Markdown
# Iteration 23.1 — Ordering ADT + Prelude Skeleton + Auto-Load — Implementation Plan
|
|
|
|
> **Parent spec:** `docs/specs/2026-05-10-23-eq-ord-prelude.md`
|
|
>
|
|
> **For agentic workers:** REQUIRED SUB-SKILL: use `skills/implement`
|
|
> to run this plan. Steps use `- [ ]` checkboxes for tracking.
|
|
|
|
**Goal:** Ship the `Ordering` ADT (`data Ordering = LT | EQ | GT`)
|
|
inside a new `prelude` module at `examples/prelude.ail.json`,
|
|
auto-loaded into every workspace via `load_workspace`, with the
|
|
prelude implicitly imported into every user module so its
|
|
ctors (`LT`, `EQ`, `GT`) are bare-resolvable. Verify end-to-end via
|
|
a fixture that pattern-matches on a hard-coded `LT` value and
|
|
prints `1`.
|
|
|
|
**Architecture:**
|
|
|
|
1. The prelude module file `examples/prelude.ail.json` is embedded
|
|
into `crates/ailang-core` at compile time via `include_str!`, so
|
|
the loader needs no on-disk resolution; the same file remains
|
|
the LLM-author-readable artefact.
|
|
2. `load_workspace` parses and inserts the embedded prelude into
|
|
`ws.modules` after the DFS over the user's imports finishes
|
|
(and before `validate_classdefs` / `build_registry`), so every
|
|
workspace sees the prelude.
|
|
3. `check_in_workspace` adds the string `"prelude"` to
|
|
`env.imports` for every non-prelude module before body-check
|
|
runs, so the existing bare-ctor fallback (Iter 15a, `lib.rs:2399`)
|
|
resolves `LT` / `EQ` / `GT` through `env.module_types["prelude"]`.
|
|
4. An E2E fixture exercises Ordering through the full pipeline
|
|
(typecheck → codegen → link → run); existing ADT codegen
|
|
handles zero-field ctors with no new arms.
|
|
|
|
**Tech Stack:** Rust (`ailang-core`, `ailang-check`, `ail`), JSON
|
|
fixture authoring, no new C runtime work.
|
|
|
|
**Files this plan creates or modifies:**
|
|
|
|
- Create: `examples/prelude.ail.json` — Form-A JSON, module name
|
|
`prelude`, contains only the `Ordering` type def (three 0-arg
|
|
ctors `LT`, `EQ`, `GT`).
|
|
- Modify: `crates/ailang-core/src/workspace.rs` — `load_workspace`
|
|
embeds and injects the prelude module; new private
|
|
`load_prelude()` helper parses the embedded JSON; new test
|
|
`test_load_workspace_auto_injects_prelude` asserts presence
|
|
after load.
|
|
- Modify: `crates/ailang-check/src/lib.rs` — `check_in_workspace`
|
|
adds `"prelude"` to `env.imports` for every non-prelude module
|
|
during env construction.
|
|
- Create: `examples/ordering_match.ail.json` — fixture: user
|
|
module with no explicit imports, pattern-matches on a
|
|
hard-coded `LT` ctor in `(match LT { LT => 1, EQ => 2, GT => 3 })`,
|
|
prints the result via `io/print_int`.
|
|
- Modify: `crates/ail/tests/e2e.rs` — new test
|
|
`test_23_1_ordering_match_via_prelude` that builds and runs
|
|
the fixture, asserts stdout = `"1\n"`.
|
|
|
|
---
|
|
|
|
## Task 1: Author the prelude module file
|
|
|
|
**Files:**
|
|
- Create: `examples/prelude.ail.json`
|
|
|
|
- [ ] **Step 1: Write the prelude JSON**
|
|
|
|
Create `examples/prelude.ail.json` with the following exact
|
|
contents:
|
|
|
|
```json
|
|
{
|
|
"schema": "ailang/v0",
|
|
"name": "prelude",
|
|
"imports": [],
|
|
"defs": [
|
|
{
|
|
"kind": "type",
|
|
"name": "Ordering",
|
|
"vars": [],
|
|
"doc": "Result of a three-way comparison: LT (less than), EQ (equal), GT (greater than). Ships in milestone 23 as the codomain of Ord.compare.",
|
|
"ctors": [
|
|
{ "name": "LT", "fields": [] },
|
|
{ "name": "EQ", "fields": [] },
|
|
{ "name": "GT", "fields": [] }
|
|
]
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
- [ ] **Step 2: Verify the JSON parses as a Module**
|
|
|
|
Run: `cargo test --workspace -p ailang-core -- workspace::tests::loads_example_workspace_happy_path`
|
|
Expected: PASS (this test loads `examples/ws_main.ail.json`; if it
|
|
breaks, the new prelude file is malformed in a way that affects
|
|
JSON shape detection).
|
|
|
|
Then a manual sanity check that the file is valid JSON:
|
|
|
|
Run: `python3 -c "import json; json.load(open('examples/prelude.ail.json'))"`
|
|
Expected: no output, exit 0.
|
|
|
|
- [ ] **Step 3: Commit**
|
|
|
|
```bash
|
|
git add examples/prelude.ail.json
|
|
git commit -m "iter 23.1.1: examples/prelude.ail.json — Ordering ADT skeleton"
|
|
```
|
|
|
|
---
|
|
|
|
## Task 2: load_workspace auto-injects the prelude module
|
|
|
|
**Files:**
|
|
- Modify: `crates/ailang-core/src/workspace.rs`
|
|
|
|
- [ ] **Step 1: Write the failing test**
|
|
|
|
In the `mod tests` block of `crates/ailang-core/src/workspace.rs`
|
|
(near `loads_example_workspace_happy_path`), add:
|
|
|
|
```rust
|
|
#[test]
|
|
fn loads_workspace_auto_injects_prelude() {
|
|
// Iter 23.1: the prelude module is implicitly part of every
|
|
// workspace, regardless of whether the user's modules import
|
|
// it. Loading any well-formed workspace must result in
|
|
// `ws.modules["prelude"]` being present with the Ordering
|
|
// type def.
|
|
let workspace_root =
|
|
std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../..");
|
|
let entry = workspace_root.join("examples").join("ws_main.ail.json");
|
|
|
|
let ws = load_workspace(&entry).expect("load workspace");
|
|
assert!(
|
|
ws.modules.contains_key("prelude"),
|
|
"prelude module must be auto-injected; modules present: {:?}",
|
|
ws.modules.keys().collect::<Vec<_>>()
|
|
);
|
|
let prelude = &ws.modules["prelude"];
|
|
assert_eq!(prelude.name, "prelude");
|
|
assert!(
|
|
prelude.defs.iter().any(|d| matches!(
|
|
d,
|
|
crate::ast::Def::Type(t) if t.name == "Ordering"
|
|
)),
|
|
"prelude must contain Ordering type def"
|
|
);
|
|
}
|
|
```
|
|
|
|
- [ ] **Step 2: Run test to verify it fails**
|
|
|
|
Run: `cargo test --workspace -p ailang-core -- workspace::tests::loads_workspace_auto_injects_prelude`
|
|
Expected: FAIL with `prelude module must be auto-injected; modules present: ["ws_main", "ws_lib"]` (or similar — `prelude` key missing).
|
|
|
|
- [ ] **Step 3: Add the prelude-loading helper**
|
|
|
|
Inside `crates/ailang-core/src/workspace.rs`, immediately above
|
|
`pub fn load_workspace` (which starts around line 305), add:
|
|
|
|
```rust
|
|
/// Iter 23.1: the prelude module is embedded into the binary at
|
|
/// compile time so the loader needs no on-disk resolution. The
|
|
/// canonical source-of-truth file remains
|
|
/// `examples/prelude.ail.json` (the file the LLM-author edits).
|
|
const PRELUDE_JSON: &str = include_str!(
|
|
"../../../examples/prelude.ail.json"
|
|
);
|
|
|
|
/// Iter 23.1: parse the embedded prelude source into a `Module`.
|
|
/// Panics on parse failure — the prelude is build-time-validated;
|
|
/// a failure here means the embedded file is malformed and is a
|
|
/// build-correctness bug, not a runtime concern.
|
|
fn load_prelude() -> Module {
|
|
serde_json::from_str(PRELUDE_JSON)
|
|
.expect("examples/prelude.ail.json must parse as a Module")
|
|
}
|
|
```
|
|
|
|
- [ ] **Step 4: Inject the prelude in load_workspace**
|
|
|
|
In `pub fn load_workspace` (currently around line 305-353),
|
|
locate the lines AFTER the `visit(entry_module, ...)` call and
|
|
BEFORE `validate_classdefs(&modules)?`. Insert the injection so
|
|
the registry-build pass sees the prelude:
|
|
|
|
```rust
|
|
visit(
|
|
entry_module,
|
|
&root_dir,
|
|
&mut modules,
|
|
&mut visiting,
|
|
&mut visiting_set,
|
|
)?;
|
|
|
|
// Iter 23.1: inject the prelude module. It is implicitly
|
|
// part of every workspace, so the user's import graph
|
|
// does not name it. A user module that happens to be named
|
|
// "prelude" would collide here; reject explicitly so the
|
|
// failure is informative rather than silent.
|
|
if modules.contains_key("prelude") {
|
|
return Err(WorkspaceLoadError::ReservedModuleName {
|
|
name: "prelude".to_string(),
|
|
});
|
|
}
|
|
let prelude = load_prelude();
|
|
modules.insert("prelude".to_string(), prelude);
|
|
|
|
// Iter 22b.2: class-schema validation runs before registry
|
|
...
|
|
```
|
|
|
|
- [ ] **Step 5: Add the new error variant**
|
|
|
|
The `WorkspaceLoadError` enum uses `thiserror::Error` derive
|
|
(see line 104: `#[derive(Debug, thiserror::Error)]`), so the
|
|
Display message is on the variant itself via `#[error(...)]`.
|
|
No separate `impl Display` block exists.
|
|
|
|
In `crates/ailang-core/src/workspace.rs`, inside the
|
|
`pub enum WorkspaceLoadError { ... }` block (currently lines
|
|
105 onwards), add this new variant at the end of the enum
|
|
(after the existing `MethodOnNonClass` or similar terminal
|
|
variant — locate the last `},` before the closing `}` of the
|
|
enum and insert before it):
|
|
|
|
```rust
|
|
/// Iter 23.1: a user module attempted to use the reserved
|
|
/// module name `prelude`. The prelude is auto-injected by
|
|
/// the loader, so a user module of the same name would
|
|
/// collide silently. Reject explicitly.
|
|
#[error("module name {name:?} is reserved (auto-injected by the loader)")]
|
|
ReservedModuleName { name: String },
|
|
```
|
|
|
|
No `impl Display` edit is required — the `#[error]` attribute
|
|
generates it automatically via the `thiserror` derive.
|
|
|
|
- [ ] **Step 6: Run test to verify it passes**
|
|
|
|
Run: `cargo test --workspace -p ailang-core -- workspace::tests::loads_workspace_auto_injects_prelude`
|
|
Expected: PASS.
|
|
|
|
Also run the existing happy-path test to verify nothing regresses:
|
|
|
|
Run: `cargo test --workspace -p ailang-core -- workspace::tests::loads_example_workspace_happy_path`
|
|
Expected: PASS.
|
|
|
|
- [ ] **Step 7: Run the full ailang-core test suite**
|
|
|
|
Run: `cargo test --workspace -p ailang-core`
|
|
Expected: all green. Any regression here is a sign that an
|
|
existing test built a workspace by hand and didn't expect a
|
|
`prelude` entry — fix that test by either updating its
|
|
assertion (if the count of modules is asserted directly) or
|
|
filtering on module names.
|
|
|
|
- [ ] **Step 8: Commit**
|
|
|
|
```bash
|
|
git add crates/ailang-core/src/workspace.rs
|
|
git commit -m "iter 23.1.2: load_workspace auto-injects prelude module"
|
|
```
|
|
|
|
---
|
|
|
|
## Task 3: check_in_workspace implicitly imports the prelude
|
|
|
|
**Files:**
|
|
- Modify: `crates/ailang-check/src/lib.rs`
|
|
|
|
- [ ] **Step 1: Write the failing test**
|
|
|
|
In the `mod tests` block of `crates/ailang-check/src/lib.rs`
|
|
(near the existing `cross_module_*` tests around line 3386-3700),
|
|
add this test. It uses the same struct shapes as the existing
|
|
`cross_module_ws` helper at line 3390 and the `fn_def` helper
|
|
at line 2628.
|
|
|
|
```rust
|
|
/// Iter 23.1: the prelude module is implicitly imported into
|
|
/// every user module. A user module that pattern-matches on
|
|
/// `LT` / `EQ` / `GT` without an explicit
|
|
/// `import { module: "prelude" }` must typecheck cleanly.
|
|
#[test]
|
|
fn user_module_can_pattern_match_on_prelude_ordering_bare() {
|
|
let prelude = Module {
|
|
schema: SCHEMA.into(),
|
|
name: "prelude".into(),
|
|
imports: vec![],
|
|
defs: vec![Def::Type(TypeDef {
|
|
name: "Ordering".into(),
|
|
vars: vec![],
|
|
ctors: vec![
|
|
Ctor { name: "LT".into(), fields: vec![] },
|
|
Ctor { name: "EQ".into(), fields: vec![] },
|
|
Ctor { name: "GT".into(), fields: vec![] },
|
|
],
|
|
doc: None,
|
|
drop_iterative: false,
|
|
})],
|
|
};
|
|
let consumer = Module {
|
|
schema: SCHEMA.into(),
|
|
name: "user".into(),
|
|
imports: vec![],
|
|
defs: vec![fn_def(
|
|
"from_lt",
|
|
Type::Fn {
|
|
params: vec![],
|
|
ret: Box::new(Type::int()),
|
|
effects: vec![],
|
|
param_modes: vec![],
|
|
ret_mode: ParamMode::Implicit,
|
|
},
|
|
vec![],
|
|
Term::Match {
|
|
scrutinee: Box::new(Term::Ctor {
|
|
type_name: "Ordering".into(),
|
|
ctor: "LT".into(),
|
|
args: vec![],
|
|
}),
|
|
arms: vec![
|
|
Arm {
|
|
pat: Pattern::Ctor {
|
|
ctor: "LT".into(),
|
|
fields: vec![],
|
|
},
|
|
body: Term::Lit { lit: Literal::Int { value: 1 } },
|
|
},
|
|
Arm {
|
|
pat: Pattern::Ctor {
|
|
ctor: "EQ".into(),
|
|
fields: vec![],
|
|
},
|
|
body: Term::Lit { lit: Literal::Int { value: 2 } },
|
|
},
|
|
Arm {
|
|
pat: Pattern::Ctor {
|
|
ctor: "GT".into(),
|
|
fields: vec![],
|
|
},
|
|
body: Term::Lit { lit: Literal::Int { value: 3 } },
|
|
},
|
|
],
|
|
},
|
|
)],
|
|
};
|
|
let mut modules = BTreeMap::new();
|
|
modules.insert("prelude".into(), prelude);
|
|
modules.insert("user".into(), consumer);
|
|
let ws = Workspace {
|
|
entry: "user".into(),
|
|
modules,
|
|
root_dir: std::path::PathBuf::from("."),
|
|
registry: ailang_core::workspace::Registry::default(),
|
|
};
|
|
let diags = check_workspace(&ws);
|
|
assert!(
|
|
diags.is_empty(),
|
|
"user module must typecheck bare-LT without explicit prelude import; got: {:?}",
|
|
diags
|
|
);
|
|
}
|
|
```
|
|
|
|
The `use super::*;` and `use ailang_core::SCHEMA;` lines at the
|
|
top of the `mod tests` block (lines 2625-2626) bring `Module`,
|
|
`Def`, `TypeDef`, `Ctor`, `Type`, `ParamMode`, `Term`, `Arm`,
|
|
`Pattern`, `Literal`, `Workspace`, `BTreeMap`, `check_workspace`,
|
|
and `SCHEMA` into scope. No new `use` declarations needed.
|
|
|
|
- [ ] **Step 2: Run test to verify it fails**
|
|
|
|
Run: `cargo test --workspace -p ailang-check -- user_module_can_pattern_match_on_prelude_ordering_bare`
|
|
Expected: FAIL — the test asserts `diags.is_empty()`, but the
|
|
bare `LT` in the user module fires `UnknownCtorInPattern("LT")`
|
|
(per `lib.rs:2419`) because `env.imports` for the `user` module
|
|
is empty and the bare-ctor fallback finds no candidate.
|
|
|
|
- [ ] **Step 3: Inject implicit prelude import in check_in_workspace**
|
|
|
|
Locate `fn check_in_workspace` (around line 1216 in
|
|
`crates/ailang-check/src/lib.rs`). It calls `build_check_env`
|
|
which populates `env.module_imports` from each module's
|
|
declared imports (around lines 1176-1183 inside `build_check_env`).
|
|
|
|
The cleanest place to add the implicit prelude import is inside
|
|
`build_check_env`, in the loop that builds `env.module_imports`,
|
|
right after the per-module import map is constructed and before
|
|
it is inserted. Modify the loop:
|
|
|
|
```rust
|
|
// env.module_imports — per-module alias-or-name -> module-name.
|
|
// Per-module because aliases collide across modules; sibling
|
|
// shape to `module_globals`.
|
|
for m in ws.modules.values() {
|
|
let mut import_map: BTreeMap<String, String> = BTreeMap::new();
|
|
for imp in &m.imports {
|
|
let key = imp.alias.clone().unwrap_or_else(|| imp.module.clone());
|
|
import_map.insert(key, imp.module.clone());
|
|
}
|
|
// Iter 23.1: the prelude module is implicitly imported
|
|
// into every non-prelude module. A user-declared explicit
|
|
// `import { module: "prelude" }` would already place
|
|
// "prelude" in the map above (idempotent insert). The
|
|
// prelude module itself does not import itself.
|
|
if m.name != "prelude" {
|
|
import_map.entry("prelude".to_string())
|
|
.or_insert_with(|| "prelude".to_string());
|
|
}
|
|
env.module_imports.insert(m.name.clone(), import_map);
|
|
}
|
|
```
|
|
|
|
NOTE: this overlay lives in `build_check_env`, which builds the
|
|
workspace-wide env. The per-module overlay in `check_in_workspace`
|
|
(lines 1224+) clears `env.types`/`env.ctor_index` and rebuilds
|
|
them with only the local module's entries, but `env.module_imports`
|
|
(used by the ctor-fallback at line 2405) is set workspace-wide and
|
|
not cleared per module, so the implicit prelude import is in scope
|
|
for every module's body-check.
|
|
|
|
Also need to plumb the local module's import map into `env.imports`
|
|
during body-check. Look for where the per-module body-check
|
|
reads `env.module_imports[<module>]` into `env.imports`. (If the
|
|
existing code already drives `env.imports` from
|
|
`env.module_imports[current_module]`, then no further wiring is
|
|
needed; this step is a code-reading verification, not a code
|
|
change.)
|
|
|
|
Read `crates/ailang-check/src/lib.rs` around line 1260-1320 (the
|
|
end of `check_in_workspace`) and confirm that `env.imports` is
|
|
populated from `env.module_imports[m.name]` before each body-check
|
|
call. If yes, no further changes. If no, plumb it through (extend
|
|
the local overlay).
|
|
|
|
- [ ] **Step 4: Run test to verify it passes**
|
|
|
|
Run: `cargo test --workspace -p ailang-check -- user_module_can_pattern_match_on_prelude_ordering_bare`
|
|
Expected: PASS.
|
|
|
|
- [ ] **Step 5: Run the full ailang-check test suite**
|
|
|
|
Run: `cargo test --workspace -p ailang-check`
|
|
Expected: all green. Watch in particular for regressions in
|
|
the `cross_module_pat_ctor_ambiguous_errors` test
|
|
(line 3536) — if any of those workspaces happened to contain a
|
|
ctor named `LT` / `EQ` / `GT`, the implicit prelude import could
|
|
now create a new ambiguity. Read the test fixtures; if a clash
|
|
exists, rename the clashing ctor in the test fixture (not in
|
|
the prelude).
|
|
|
|
- [ ] **Step 6: Commit**
|
|
|
|
```bash
|
|
git add crates/ailang-check/src/lib.rs
|
|
git commit -m "iter 23.1.3: check_in_workspace implicitly imports prelude into every user module"
|
|
```
|
|
|
|
---
|
|
|
|
## Task 4: E2E fixture + ail-crate test
|
|
|
|
**Files:**
|
|
- Create: `examples/ordering_match.ail.json`
|
|
- Modify: `crates/ail/tests/e2e.rs`
|
|
|
|
- [ ] **Step 1: Author the fixture**
|
|
|
|
Create `examples/ordering_match.ail.json` with the following
|
|
exact contents:
|
|
|
|
```json
|
|
{
|
|
"schema": "ailang/v0",
|
|
"name": "ordering_match",
|
|
"imports": [],
|
|
"defs": [
|
|
{
|
|
"kind": "fn",
|
|
"name": "main",
|
|
"type": {
|
|
"k": "fn",
|
|
"params": [],
|
|
"ret": { "k": "con", "name": "Unit" },
|
|
"effects": ["IO"]
|
|
},
|
|
"params": [],
|
|
"doc": "Iter 23.1 fixture: pattern-match on prelude Ordering ctor without explicit prelude import.",
|
|
"body": {
|
|
"t": "do",
|
|
"op": "io/print_int",
|
|
"args": [
|
|
{
|
|
"t": "match",
|
|
"scrutinee": {
|
|
"t": "ctor",
|
|
"type": "Ordering",
|
|
"ctor": "LT",
|
|
"args": []
|
|
},
|
|
"arms": [
|
|
{
|
|
"pat": { "p": "ctor", "ctor": "LT", "fields": [] },
|
|
"body": { "t": "lit", "lit": { "kind": "int", "value": 1 } }
|
|
},
|
|
{
|
|
"pat": { "p": "ctor", "ctor": "EQ", "fields": [] },
|
|
"body": { "t": "lit", "lit": { "kind": "int", "value": 2 } }
|
|
},
|
|
{
|
|
"pat": { "p": "ctor", "ctor": "GT", "fields": [] },
|
|
"body": { "t": "lit", "lit": { "kind": "int", "value": 3 } }
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
- [ ] **Step 2: Write the failing E2E test**
|
|
|
|
In `crates/ail/tests/e2e.rs`, add a new test function. Locate
|
|
an existing E2E test (e.g. `check_workspace_resolves_import` at
|
|
line 863) to match the helper-usage style, and add this test
|
|
function nearby:
|
|
|
|
```rust
|
|
#[test]
|
|
fn ordering_match_via_prelude_prints_1() {
|
|
// Iter 23.1: examples/ordering_match.ail.json pattern-matches
|
|
// on the prelude Ordering ctor `LT` without an explicit
|
|
// prelude import. End-to-end: typecheck → codegen → link →
|
|
// run → stdout "1\n".
|
|
let stdout = run_example("examples/ordering_match.ail.json")
|
|
.expect("ordering_match should build and run");
|
|
assert_eq!(stdout, "1\n");
|
|
}
|
|
```
|
|
|
|
NOTE: the `run_example` helper name above must match the one
|
|
already in use in `e2e.rs` (read the file's top to confirm —
|
|
some test files name it differently, e.g. `build_and_run`,
|
|
`exec_fixture`, or `run_fixture`). Use whatever name the
|
|
existing E2E tests in this file already use to compile and run
|
|
a `.ail.json` fixture and capture stdout.
|
|
|
|
- [ ] **Step 3: Run test to verify it fails (and isolate why)**
|
|
|
|
Run: `cargo test --workspace -p ail -- ordering_match_via_prelude_prints_1`
|
|
Expected: FAIL. The failure should be either:
|
|
(a) the test infrastructure is fine but the binary outputs
|
|
something wrong (real failure mode worth investigating);
|
|
(b) the test infrastructure compiles successfully and stdout
|
|
is `"1\n"` — meaning Tasks 1-3 already chain through
|
|
correctly and the test is GREEN on first run. That is the
|
|
expected end state.
|
|
|
|
If (b) — test passes on first run — go directly to Step 5.
|
|
|
|
If (a) — investigate. Most likely causes:
|
|
- The fixture failed to typecheck → indicates Task 3 didn't
|
|
fully wire `env.imports` to include "prelude" at body-check
|
|
time. Re-read Task 3 Step 3's NOTE about plumbing
|
|
`env.module_imports[current_module]` into `env.imports`.
|
|
- The fixture typechecks but codegen fails on the cross-module
|
|
ctor reference → indicates codegen needs the cross-module
|
|
resolution too. Symptom: the IR emit step reports
|
|
`UnknownCtor("LT")` or similar.
|
|
|
|
- [ ] **Step 4: Apply minimal fix (only if Step 3 fell into case (a))**
|
|
|
|
If codegen reports `UnknownCtor("LT")`, the codegen lookup path
|
|
for ctors does not honour the implicit prelude import. Locate
|
|
the codegen ctor-resolution (likely in
|
|
`crates/ailang-codegen/src/lib.rs`, grep for `UnknownCtor` or
|
|
`ctor_index` to find the relevant arm). The fix mirrors Task 3:
|
|
add "prelude" to whatever import-set codegen consults for the
|
|
current module's ctor resolution.
|
|
|
|
If the failure is something else, treat as a NEEDS_CONTEXT
|
|
escalation back to the orchestrator.
|
|
|
|
- [ ] **Step 5: Run test to verify it passes**
|
|
|
|
Run: `cargo test --workspace -p ail -- ordering_match_via_prelude_prints_1`
|
|
Expected: PASS, stdout captured as `"1\n"`.
|
|
|
|
- [ ] **Step 6: Run the full ail-crate test suite**
|
|
|
|
Run: `cargo test --workspace -p ail`
|
|
Expected: all green.
|
|
|
|
- [ ] **Step 7: Run the full workspace test suite**
|
|
|
|
Run: `cargo test --workspace`
|
|
Expected: all green.
|
|
|
|
- [ ] **Step 8: Commit**
|
|
|
|
```bash
|
|
git add examples/ordering_match.ail.json crates/ail/tests/e2e.rs
|
|
git commit -m "iter 23.1.4: E2E fixture — bare LT match via implicit prelude import"
|
|
```
|
|
|
|
---
|
|
|
|
## Task 5: JOURNAL entry for iter 23.1
|
|
|
|
**Files:**
|
|
- Modify: `docs/JOURNAL.md`
|
|
|
|
- [ ] **Step 1: Get commit hashes**
|
|
|
|
Run: `git log --oneline -10`
|
|
Expected: top 4 commits are iter 23.1.1, 23.1.2, 23.1.3, 23.1.4
|
|
(in that order, most recent first).
|
|
|
|
- [ ] **Step 2: Append JOURNAL entry**
|
|
|
|
Append the following block at the end of `docs/JOURNAL.md`,
|
|
replacing `<hash N>` with the short SHA from Step 1:
|
|
|
|
```markdown
|
|
## 2026-05-10 — Iteration 23.1: Ordering ADT + prelude skeleton + auto-load
|
|
|
|
First iteration of the milestone-23 Eq/Ord Prelude. Lays the
|
|
groundwork — no classes or instances ship yet — by establishing
|
|
the prelude module as a real loaded module that every workspace
|
|
implicitly contains, with bare-name resolution for its ctors via
|
|
the existing import-fallback machinery (Iter 15a,
|
|
`ailang-check/src/lib.rs:2399`).
|
|
|
|
Three mechanisms compose:
|
|
|
|
1. **Embedded prelude.** `examples/prelude.ail.json` is the
|
|
LLM-author-readable source; the loader embeds it via
|
|
`include_str!` at compile time. No runtime file IO, no CWD
|
|
dependency, no fixture-not-found failure mode.
|
|
2. **Loader injection.** `load_workspace` inserts the prelude
|
|
into `ws.modules` after the user's import DFS finishes and
|
|
before `validate_classdefs` / `build_registry`, so the
|
|
workspace-wide registry passes see it like any other module.
|
|
A user module trying to claim the reserved name `prelude`
|
|
fails fast with `WorkspaceLoadError::ReservedModuleName`.
|
|
3. **Implicit import.** `build_check_env` adds `prelude → prelude`
|
|
to every non-prelude module's `module_imports` map. The
|
|
existing bare-ctor fallback in `Pattern::Ctor` resolution
|
|
(Iter 15a) finds prelude ctors through this path, so the user
|
|
writes `LT` instead of `prelude.LT`.
|
|
|
|
Verified end-to-end through `examples/ordering_match.ail.json`
|
|
(pattern-matches a hard-coded `LT` value, prints `1`). The
|
|
ail-crate E2E test plus the per-crate unit tests catch any
|
|
regression on the three mechanisms independently.
|
|
|
|
Out of scope for this iter (covered by later 23.x):
|
|
- Eq / Ord class definitions (23.2 / 23.3).
|
|
- Free top-level utility functions (23.4).
|
|
- Float-NoInstance diagnostic + DESIGN.md amendment (23.5).
|
|
|
|
Per-task commits:
|
|
|
|
- `<hash 1>` iter 23.1.1: examples/prelude.ail.json — Ordering ADT skeleton
|
|
- `<hash 2>` iter 23.1.2: load_workspace auto-injects prelude module
|
|
- `<hash 3>` iter 23.1.3: check_in_workspace implicitly imports prelude into every user module
|
|
- `<hash 4>` iter 23.1.4: E2E fixture — bare LT match via implicit prelude import
|
|
```
|
|
|
|
- [ ] **Step 3: Commit**
|
|
|
|
```bash
|
|
git add docs/JOURNAL.md
|
|
git commit -m "iter 23.1.5: JOURNAL entry"
|
|
```
|
|
|
|
---
|
|
|
|
## Self-review checklist
|
|
|
|
1. **Spec coverage:** Component row 23.1 in the parent spec names
|
|
four concrete artefacts — Ordering ADT, prelude module skeleton,
|
|
`check_workspace` auto-load wire, codegen+match-lowering
|
|
exercised by unit test. Task 1 ships the ADT and skeleton.
|
|
Task 2 ships the auto-load. Task 3 ships the implicit import
|
|
(the part that makes auto-load *useful*). Task 4 exercises the
|
|
end-to-end pipeline through the unit + E2E fixture. Task 5
|
|
records the JOURNAL entry. ✓
|
|
2. **Placeholder scan:** `<hash 1>` / `<hash 2>` / `<hash 3>` /
|
|
`<hash 4>` are commit-hash placeholders, resolved by the
|
|
implementer at Task 5 Step 1 by reading actual `git log`
|
|
output. No "TBD", no "TODO", no "implement later", no
|
|
"similar to Task N". Task 4 Step 4 says "apply minimal fix
|
|
*only if* Step 3 fell into case (a)" — that is a
|
|
conditional path with explicit triggering criteria, not a
|
|
placeholder. ✓
|
|
3. **Type / path consistency:** `examples/prelude.ail.json`
|
|
(Task 1) is the same path in Task 2 Step 3's `include_str!`
|
|
path component. `ws.modules["prelude"]` is consistent in
|
|
Task 2 Step 4 (insert) and Task 3 Step 1 (assert). Module
|
|
name `"prelude"` appears consistently in Tasks 2, 3, and 4
|
|
(skip-self condition in Task 3 Step 3, fixture import-list
|
|
in Task 4 — fixture has empty `imports` because the implicit
|
|
import is the point). ✓
|
|
4. **Step granularity:** every step is one file edit or one
|
|
command, 2-5 minutes. Task 3 Step 3 is the longest (read +
|
|
modify env-construction); split into NOTE-driven sub-actions
|
|
if the implementer flags it as not bite-sized. ✓
|
|
|
|
---
|
|
|
|
## Notes for the implementer
|
|
|
|
- **The `run_example` helper name in Task 4 Step 2** — the test
|
|
file `crates/ail/tests/e2e.rs` uses a specific helper to
|
|
compile and run a fixture. Read the file's existing tests to
|
|
find the right name (most likely `compile_and_run` or
|
|
`run_example`). Match the existing style; do NOT introduce a
|
|
new helper.
|
|
- **Cross-module ctor resolution at codegen time** (Task 4 Step
|
|
4) — if it surfaces, it is the same "lockstep across files"
|
|
pattern the architect-iron-law extension just guarded against
|
|
(`is_static_callee` ↔ `lower_app`, `pre_desugar_validation`
|
|
↔ desugar). For ctors: the codegen ctor-resolution path likely
|
|
has its own import-list consultation, mirroring
|
|
`check_in_workspace`'s. If the fix in Task 3 fully resolves
|
|
Task 4 without codegen changes (case (b) in Step 3), no
|
|
further lockstep-update is needed — but report this in the
|
|
status payload so the orchestrator can record it.
|
|
- **Existing `loads_example_workspace_happy_path` test** — this
|
|
test currently asserts a count of modules. After Task 2 it
|
|
will see one extra module (the prelude). Either:
|
|
(i) the test does NOT assert a count → unaffected;
|
|
(ii) the test DOES assert a count → it will break, and
|
|
Task 2 Step 7 is the place to fix it (update the
|
|
count or filter the prelude key out).
|
|
Read the test first, before assuming.
|