plan: rpe.1 — retire per-type print effect-ops, 10 tasks
Single-iter plan implementing the rpe.1 spec. Ten tasks: RED test, 92 .ail corpus migration, 6 .prose.txt regen, four-site compiler deletion, five test-body migrations, six doc-comment touch-ups, seven DESIGN.md sites, three E2E comment touch-ups, bench ratification (per spec §C4 (a)), and the close-out (roadmap strike, journal, stats). Plan absorbs four spec-vs-code mismatches surfaced by recon (one install-test instead of three, 92/6 fixture split rather than 98, synth.rs:215 parallel registry, incidental test-body + doc-comment sites) as an "Errata vs. spec" block at the top of the plan; the spec itself is unchanged because none of the mismatches affect load-bearing invariants.
This commit is contained in:
@@ -0,0 +1,749 @@
|
||||
# rpe.1 — Retire per-type print effect-ops — Implementation Plan
|
||||
|
||||
> **Parent spec:** `docs/specs/2026-05-14-retire-per-type-print-effects.md`
|
||||
>
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: use `skills/implement`
|
||||
> to run this plan. Steps use `- [ ]` checkboxes for tracking.
|
||||
|
||||
**Goal:** Delete `io/print_int`, `io/print_bool`, `io/print_float`
|
||||
from the compiler and migrate every corpus call site to the
|
||||
polymorphic `print` helper shipped in iter 24.3.
|
||||
|
||||
**Architecture:** Four-site lockstep deletion (check builtins,
|
||||
codegen lower_effect_op, codegen synth.rs effect_op_ret table,
|
||||
plus three doc-comment touch-ups), preceded by exhaustive corpus
|
||||
migration so that the deletion does not strand any live caller.
|
||||
A RED test pins the deletion gate; per-task `cargo test --workspace`
|
||||
gates each step. Bench-latency drift is expected per spec §C4 (a)
|
||||
and is ratified at iter close.
|
||||
|
||||
**Tech Stack:** `ailang-check`, `ailang-codegen`, `ailang-surface`,
|
||||
`ailang-prose`, `ailang-core`, `ail` CLI; the snapshot-regenerated
|
||||
`.prose.txt` artefacts; `bench/run.sh` latency harness baselines.
|
||||
|
||||
---
|
||||
|
||||
## Errata vs. spec (incorporated below; spec is unchanged)
|
||||
|
||||
Recon surfaced four spec-vs-code mismatches; this plan absorbs
|
||||
them without amending the spec:
|
||||
|
||||
1. **Spec §C2 says "three install_io_print_<T>_signature tests
|
||||
(lines ~593–633)".** Only `install_io_print_float_signature`
|
||||
exists (builtins.rs:600). The plan deletes the one that
|
||||
exists, not three.
|
||||
2. **Spec §D1 says "98 examples/*.ail files".** Actual: 92 `.ail`
|
||||
+ 6 `.prose.txt` snapshot siblings = 98. The plan migrates
|
||||
both, but a `.prose.txt` is regenerated, not text-substituted.
|
||||
3. **`crates/ailang-codegen/src/synth.rs:215`** carries a parallel
|
||||
effect-op `ret` table (`builtin_effect_op_ret`) the spec §C2
|
||||
does not name. Lockstep delete with the codegen arms.
|
||||
4. **Five test bodies + four doc-comment sites** carry
|
||||
`io/print_int` as incidental scaffolding (`crates/ailang-check/src/lib.rs`,
|
||||
`crates/ailang-core/tests/spec_drift.rs`, `design_schema_drift.rs`,
|
||||
`crates/ailang-surface/{src/lex.rs, src/parse.rs}`,
|
||||
`crates/ailang-prose/src/lib.rs`, `crates/ail/src/main.rs`,
|
||||
`crates/ailang-core/specs/form_a.md`, `runtime/str.c`
|
||||
comment). Plan covers them in Task 5 and Task 6.
|
||||
|
||||
The spec's §B/§C1/§D1 invariants are unaffected.
|
||||
|
||||
---
|
||||
|
||||
## Files this plan creates or modifies
|
||||
|
||||
**Create:**
|
||||
|
||||
- `crates/ailang-check/tests/no_per_type_print_ops.rs` — RED test
|
||||
(Task 1). Model on `crates/ailang-check/tests/show_dispatch_pin.rs`.
|
||||
|
||||
**Modify — corpus (Task 2 + Task 3):**
|
||||
|
||||
- `examples/<name>.ail` × 92 — bulk substitution. Full list at
|
||||
`/tmp/migration_targets.txt` (built by Task 2 Step 1; see Task 2
|
||||
for the canonical query the LLM re-runs).
|
||||
- `examples/<name>.prose.txt` × 6 — regenerate via `ail prose`.
|
||||
Files: `bench_list_sum.prose.txt`, `ordering_match.prose.txt`,
|
||||
`rc_app_let_partial_drop_leak.prose.txt`,
|
||||
`rc_match_arm_partial_drop_leak.prose.txt`,
|
||||
`rc_own_param_drop.prose.txt`,
|
||||
`test_22b3_default_e2e.prose.txt`.
|
||||
|
||||
**Modify — compiler core (Task 4):**
|
||||
|
||||
- `crates/ailang-check/src/builtins.rs`:
|
||||
- line 5 — module-doc-comment swap `io/print_int` → `io/print_str`.
|
||||
- line 40 — `EffectOpSig.ret` doc swap.
|
||||
- lines 269-276 — delete `env.effect_ops.insert("io/print_int", ...)`.
|
||||
- lines 277-284 — delete `env.effect_ops.insert("io/print_bool", ...)`.
|
||||
- lines 293-302 — delete floats-iter comment + `io/print_float` insert.
|
||||
- line 349 — delete `io/print_int` row in `list()`.
|
||||
- line 350 — delete `io/print_bool` row.
|
||||
- line 352 — delete `io/print_float` row. (Line 351 — `io/print_str` — survives.)
|
||||
- lines 593-635 — delete `install_io_print_float_signature` test fn (incl. its 7-line doc-comment).
|
||||
- `crates/ailang-codegen/src/lib.rs`:
|
||||
- lines 2302-2323 — delete `"io/print_int" => { ... }` arm.
|
||||
- lines 2324-2345 — delete `"io/print_float" => { ... }` arm.
|
||||
- lines 2373-2411 — delete `"io/print_bool" => { ... }` arm.
|
||||
- lines 3580-3617 — delete `lowers_io_print_float` test (incl. doc-comment).
|
||||
- format-string interns (`"fmt_int"`, `"%lld\n"`, `"fmt_float"`, `"%g\n"`, `"fmt_true"`, `"fmt_false"`) orphan and may be deleted in the same edit if they are no longer referenced. Verify by `grep` post-edit.
|
||||
- `crates/ailang-codegen/src/synth.rs:215` — remove `"io/print_int" | "io/print_bool" | "io/print_float"` from the `match name` arm; keep `"io/print_str"`.
|
||||
|
||||
**Modify — incidental-scaffolding test bodies (Task 5):**
|
||||
|
||||
- `crates/ailang-check/src/lib.rs`:
|
||||
- lines 3871-3877 (undeclared-effect-diagnostic test) — swap `op: "io/print_int"` → `op: "io/print_str"`; if the arg type was `Int`, change it to a `Str` literal that types as Str.
|
||||
- lines 5047-5051 (tail-call-in-tail-position test) — same shape.
|
||||
- `crates/ailang-core/tests/spec_drift.rs` lines 64-71 — swap to `io/print_str` with a `Str` arg.
|
||||
- `crates/ailang-core/tests/design_schema_drift.rs` lines 87-94 — same.
|
||||
- `crates/ailang-surface/src/lex.rs`:
|
||||
- lines 18-20 — module-doc-comment swap.
|
||||
- line 320 — `tokenize("+ == <= io/print_int std_list.map")` swap to `io/print_str`.
|
||||
- line 328 — corresponding `assert_eq!` vector swap.
|
||||
- `crates/ailang-prose/src/lib.rs` lines 1585-1593 — round-trip test asserting `render_term(&t) == "do io/print_int(5)"`: swap to `io/print_str` with a Str arg + matching assert.
|
||||
|
||||
**Modify — doc-comment touch-ups (Task 6):**
|
||||
|
||||
- `crates/ailang-surface/src/parse.rs:1377` — diagnostic example swap.
|
||||
- `crates/ail/src/main.rs:30-31, 1419-1420` — CLI doc-comments swap.
|
||||
- `runtime/str.c:117-118` — re-anchor on `%g` directly.
|
||||
- `crates/ailang-core/specs/form_a.md:433-434` — surface-spec example: migrate to either `(do io/print_str s)` with a `Str` literal, or `(app print xs)` — pick the latter to advertise the post-iter idiom.
|
||||
|
||||
**Modify — DESIGN.md (Task 7):**
|
||||
|
||||
- `docs/DESIGN.md`:
|
||||
- line 331 — Decision 11 example swap.
|
||||
- lines 1990-1992 — past-tense rewrite ("retired 2026-05-14 in iter rpe.1").
|
||||
- lines 2024-2025 — Heap-Str primitives output sentence rewrite per spec §C3.
|
||||
- line 2331 — effect-op invocation comment example swap.
|
||||
- lines 2565-2577 — Float NaN-rendering paragraph rewrite (anchor on `float_to_str`).
|
||||
- line 2679 — "What is supported (effect ops)" list, keep only `io/print_str`.
|
||||
- line 2699 — "(IO effect ops)" list, keep only `io/print_str`.
|
||||
|
||||
**Modify — incidental E2E test comments (Task 8):**
|
||||
|
||||
- `crates/ail/tests/eq_ord_e2e.rs:66, 76` — update comments to name `print` semantics.
|
||||
- `crates/ail/tests/e2e.rs:2674-2683` (`int_arg_to_effect_op_does_not_rc_track`) — fixture migrates in Task 2; doc-comment migrates here.
|
||||
- `crates/ail/tests/floats_e2e.rs:7` — module doc-comment swap.
|
||||
|
||||
**Modify — bench baselines (Task 9, conditional):**
|
||||
|
||||
- `bench/baseline.json` "latency" block (lines 70-92) — ratify if drift.
|
||||
- `bench/baseline.json" throughput block (lines 1-69, 93) — ratify if drift.
|
||||
- `bench/baseline_cross_lang.json` — ratify if drift.
|
||||
- `bench/baseline_compile.json` — expected exit-0; ratify only if unexpected drift.
|
||||
|
||||
**Modify — roadmap (Task 10):**
|
||||
|
||||
- `docs/roadmap.md` P2 section lines 80-90 — strike entry with `- [x]` + date.
|
||||
|
||||
---
|
||||
|
||||
## Task 1: RED test — `no_per_type_print_ops.rs`
|
||||
|
||||
**Files:**
|
||||
- Create: `crates/ailang-check/tests/no_per_type_print_ops.rs`
|
||||
|
||||
- [ ] **Step 1: Write the failing test**
|
||||
|
||||
```rust
|
||||
//! Hard gate for iter rpe.1: the per-type print effect-ops
|
||||
//! `io/print_int`, `io/print_bool`, `io/print_float` are retired
|
||||
//! and must NOT appear in the builtin registry. The polymorphic
|
||||
//! `print` helper (prelude, iter 24.3) is the canonical replacement;
|
||||
//! `io/print_str` survives as the byte-channel primitive.
|
||||
|
||||
use ailang_check::build_check_env;
|
||||
use ailang_surface::load_workspace;
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn fixture_dir() -> PathBuf {
|
||||
let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
||||
d.pop();
|
||||
d.pop();
|
||||
d.join("examples")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn per_type_print_effect_ops_are_retired() {
|
||||
// Any workspace will do; we only inspect the post-install
|
||||
// builtin registry. `hello.ail` is the smallest surviving
|
||||
// fixture (uses `io/print_str` only).
|
||||
let ws_path = fixture_dir().join("hello.ail");
|
||||
let ws = load_workspace(&ws_path).expect("workspace loads");
|
||||
let env = build_check_env(&ws);
|
||||
|
||||
for retired in ["io/print_int", "io/print_bool", "io/print_float"] {
|
||||
assert!(
|
||||
!env.effect_ops.contains_key(retired),
|
||||
"{retired} must not appear in builtin registry post-rpe.1; \
|
||||
use polymorphic `print` (Show-based) instead"
|
||||
);
|
||||
}
|
||||
|
||||
// Survivor pin: io/print_str stays.
|
||||
assert!(
|
||||
env.effect_ops.contains_key("io/print_str"),
|
||||
"io/print_str must survive — it is the canonical byte-channel \
|
||||
primitive and used internally by `print`"
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Verify the test fails RED**
|
||||
|
||||
Run: `cargo test --workspace -p ailang-check --test no_per_type_print_ops`
|
||||
Expected: FAIL on the first `assert!` because `io/print_int`
|
||||
still appears in `env.effect_ops` post-Task-2.
|
||||
|
||||
This gate flips to GREEN at Task 4 close.
|
||||
|
||||
---
|
||||
|
||||
## Task 2: Corpus `.ail` migration — 92 fixtures
|
||||
|
||||
**Files:**
|
||||
- Modify: 92 `examples/<name>.ail` files (full list deterministic
|
||||
from `grep -rl "io/print_int\|io/print_bool\|io/print_float"
|
||||
examples/ | grep "\.ail$"`).
|
||||
|
||||
- [ ] **Step 1: Build the canonical target list**
|
||||
|
||||
Run:
|
||||
```bash
|
||||
grep -rl "io/print_int\|io/print_bool\|io/print_float" examples/ \
|
||||
| grep "\.ail$" > /tmp/rpe1_targets.txt && \
|
||||
wc -l /tmp/rpe1_targets.txt
|
||||
```
|
||||
Expected: `92 /tmp/rpe1_targets.txt`.
|
||||
|
||||
- [ ] **Step 2: Bulk-substitute each fixture**
|
||||
|
||||
For each path in `/tmp/rpe1_targets.txt`, transform every
|
||||
occurrence of:
|
||||
```
|
||||
(do io/print_int <ARG>)
|
||||
```
|
||||
to:
|
||||
```
|
||||
(app print <ARG>)
|
||||
```
|
||||
And the same for `io/print_bool`, `io/print_float`.
|
||||
|
||||
The transformation is regular: parenthesis-balance is preserved
|
||||
(both `do` and `app` consume their parentheses identically),
|
||||
the head is replaced (`io/print_<T>` → `print`), and the leading
|
||||
keyword is replaced (`do` → `app`). The argument expression is
|
||||
copied verbatim.
|
||||
|
||||
Implementation: read each file, apply the regex
|
||||
`\(do io/print_(int|bool|float) ` → `(app print `, write back.
|
||||
Or process file-by-file with Edit-tool calls; either is fine.
|
||||
|
||||
- [ ] **Step 3: Verify each migrated fixture parses + round-trips**
|
||||
|
||||
Run:
|
||||
```bash
|
||||
cargo run -p ail -- check examples/<each-fixture> 2>&1
|
||||
```
|
||||
Expected: exit 0. The pre-iter compiler still knows
|
||||
`io/print_int|bool|float`, so existing fixtures stay green; the
|
||||
migrated form (using polymorphic `print`) ALSO type-checks because
|
||||
`print` is in the prelude (iter 24.3). Both paths are green
|
||||
simultaneously in this iter window.
|
||||
|
||||
Pragmatic shortcut: run `cargo test --workspace` at Step 4 instead
|
||||
of per-fixture `ail check`.
|
||||
|
||||
- [ ] **Step 4: `cargo test --workspace` green**
|
||||
|
||||
Run: `cargo test --workspace`
|
||||
Expected: 0 failures. The migrated fixtures' E2E test wrappers
|
||||
still produce the same stdout (output bytes identical between
|
||||
`(do io/print_<T> x)` and `(app print x)` per spec §"Data flow").
|
||||
|
||||
If any fixture has a stdout-comparison test that now produces
|
||||
different bytes, that is a real divergence and the spec's
|
||||
"output bytes identical" claim has a counterexample — STOP
|
||||
and investigate before continuing.
|
||||
|
||||
---
|
||||
|
||||
## Task 3: Prose snapshot regeneration — 6 `.prose.txt` files
|
||||
|
||||
**Files:**
|
||||
- Modify: `examples/bench_list_sum.prose.txt`,
|
||||
`examples/ordering_match.prose.txt`,
|
||||
`examples/rc_app_let_partial_drop_leak.prose.txt`,
|
||||
`examples/rc_match_arm_partial_drop_leak.prose.txt`,
|
||||
`examples/rc_own_param_drop.prose.txt`,
|
||||
`examples/test_22b3_default_e2e.prose.txt`.
|
||||
|
||||
- [ ] **Step 1: Regenerate each prose snapshot**
|
||||
|
||||
For each fixture above, run:
|
||||
```bash
|
||||
cargo run -p ail -- prose examples/<stem>.ail \
|
||||
> examples/<stem>.prose.txt
|
||||
```
|
||||
|
||||
The `ail prose` subcommand emits Form-B; this file becomes the
|
||||
committed snapshot used by `crates/ailang-prose/tests/snapshot.rs`.
|
||||
|
||||
- [ ] **Step 2: `cargo test --workspace -p ailang-prose` green**
|
||||
|
||||
Run: `cargo test --workspace -p ailang-prose`
|
||||
Expected: 0 failures. The snapshot test will compare the
|
||||
regenerated `.prose.txt` to the prose-renderer's current output
|
||||
of the migrated fixture; they must match because we generated
|
||||
them with the very command the test uses.
|
||||
|
||||
- [ ] **Step 3: `cargo test --workspace` green**
|
||||
|
||||
Run: `cargo test --workspace`
|
||||
Expected: 0 failures (no regression in any other crate).
|
||||
|
||||
---
|
||||
|
||||
## Task 4: Compiler-side deletion (lockstep)
|
||||
|
||||
**Files:**
|
||||
- Modify: `crates/ailang-check/src/builtins.rs`
|
||||
(lines 5, 40, 269-302, 349, 350, 352, 593-635)
|
||||
- Modify: `crates/ailang-codegen/src/lib.rs`
|
||||
(lines 2302-2323, 2324-2345, 2373-2411, 3580-3617)
|
||||
- Modify: `crates/ailang-codegen/src/synth.rs:215`
|
||||
|
||||
- [ ] **Step 1: Delete the three `env.effect_ops.insert` blocks**
|
||||
|
||||
In `crates/ailang-check/src/builtins.rs`, delete:
|
||||
- lines 269-276 (`io/print_int` block)
|
||||
- lines 277-284 (`io/print_bool` block)
|
||||
- lines 293-302 (floats-iter comment + `io/print_float` block)
|
||||
|
||||
Keep the surviving `io/print_str` block (between or after; verify
|
||||
by reading the file post-edit).
|
||||
|
||||
- [ ] **Step 2: Delete the three `list()` rows**
|
||||
|
||||
In `crates/ailang-check/src/builtins.rs`, delete:
|
||||
- line 349 (`io/print_int` row)
|
||||
- line 350 (`io/print_bool` row)
|
||||
- line 352 (`io/print_float` row)
|
||||
|
||||
Keep line 351 (`io/print_str` row). Verify by re-reading.
|
||||
|
||||
- [ ] **Step 3: Delete the `install_io_print_float_signature` test**
|
||||
|
||||
In `crates/ailang-check/src/builtins.rs`, delete lines 593-635
|
||||
(test fn + its 7-line doc-comment). This is the ONE test that
|
||||
exists matching the spec's "three install_io_print_<T>_signature"
|
||||
description — see Errata #1 above.
|
||||
|
||||
- [ ] **Step 4: Swap the module-doc-comments**
|
||||
|
||||
In `crates/ailang-check/src/builtins.rs`:
|
||||
- line 5 — swap `io/print_int` reference to `io/print_str`.
|
||||
- line 40 — same swap in `EffectOpSig.ret` doc.
|
||||
|
||||
- [ ] **Step 5: Delete the three codegen `lower_effect_op` arms**
|
||||
|
||||
In `crates/ailang-codegen/src/lib.rs`:
|
||||
- lines 2302-2323 — delete `"io/print_int" => { ... }` arm.
|
||||
- lines 2324-2345 — delete `"io/print_float" => { ... }` arm.
|
||||
- lines 2373-2411 — delete `"io/print_bool" => { ... }` arm.
|
||||
|
||||
Keep the `"io/print_str"` arm (lines 2346-2372). Verify by
|
||||
re-reading.
|
||||
|
||||
After deletion, search the file for orphaned format-string
|
||||
interns (`"fmt_int"`, `"%lld\n"`, `"fmt_float"`, `"%g\n"`,
|
||||
`"fmt_true"`, `"fmt_false"`). If they are no longer referenced
|
||||
anywhere in the crate, delete the corresponding `out.push_str`
|
||||
or `intern` calls too. If they ARE still referenced (e.g.
|
||||
`"%g\n"` shared with another path), leave them.
|
||||
|
||||
- [ ] **Step 6: Delete the `lowers_io_print_float` test**
|
||||
|
||||
In `crates/ailang-codegen/src/lib.rs`, delete lines 3580-3617
|
||||
(test + doc-comment).
|
||||
|
||||
- [ ] **Step 7: Update `synth.rs` builtin_effect_op_ret**
|
||||
|
||||
In `crates/ailang-codegen/src/synth.rs:215`, remove
|
||||
`"io/print_int" | "io/print_bool" | "io/print_float"` from the
|
||||
match arm pattern. Keep `"io/print_str"`. The match arm body
|
||||
(returning `Type::unit()`) stays unchanged for the surviving
|
||||
op.
|
||||
|
||||
- [ ] **Step 8: RED test flips GREEN**
|
||||
|
||||
Run: `cargo test --workspace -p ailang-check --test no_per_type_print_ops`
|
||||
Expected: PASS. The Task-1 RED test is now satisfied because
|
||||
the three retired ops no longer appear in `env.effect_ops`.
|
||||
|
||||
- [ ] **Step 9: `cargo test --workspace` green**
|
||||
|
||||
Run: `cargo test --workspace`
|
||||
Expected: 0 failures. Any failure here means a stale `io/print_<T>`
|
||||
reference survived in a fixture or test body — those are addressed
|
||||
in Task 5; expect Task-5 sites to fail and proceed to Task 5.
|
||||
|
||||
---
|
||||
|
||||
## Task 5: In-tree test-body migration
|
||||
|
||||
**Files:**
|
||||
- Modify: `crates/ailang-check/src/lib.rs:3871-3877, 5047-5051`
|
||||
- Modify: `crates/ailang-core/tests/spec_drift.rs:64-71`
|
||||
- Modify: `crates/ailang-core/tests/design_schema_drift.rs:87-94`
|
||||
- Modify: `crates/ailang-surface/src/lex.rs:320, 328`
|
||||
- Modify: `crates/ailang-prose/src/lib.rs:1585-1593`
|
||||
|
||||
- [ ] **Step 1: Migrate `ailang-check/src/lib.rs:3871-3877`**
|
||||
|
||||
The site is an undeclared-effect-diagnostic test. The op name
|
||||
is incidental — the test asserts "calling an unknown effect-op
|
||||
produces diagnostic X". Swap `op: "io/print_int"` → `op:
|
||||
"io/print_str"` AND change the arg from an `Int` literal to a
|
||||
`Str` literal so the swapped op type-checks. Then change the
|
||||
expected diagnostic's `effect_op` field if it's pinned by name.
|
||||
|
||||
If the test is asserting "unknown effect-op", the spirit is
|
||||
preserved by inventing a never-registered name like
|
||||
`"io/print_unknown"` and the test continues to assert "diagnostic
|
||||
produced". Use whichever shape is closest to the original intent
|
||||
— inspect the test body to decide.
|
||||
|
||||
- [ ] **Step 2: Migrate `ailang-check/src/lib.rs:5047-5051`**
|
||||
|
||||
Tail-call-in-tail-position test. Same shape as Step 1; the op
|
||||
is incidental. Swap to `io/print_str` + `Str` arg.
|
||||
|
||||
- [ ] **Step 3: Migrate `spec_drift.rs:64-71`**
|
||||
|
||||
JSON-shape drift test on `Term::Do`. The op identity is the
|
||||
serialised payload's key, not the load-bearing assertion.
|
||||
Swap to `io/print_str` + `Str` arg.
|
||||
|
||||
- [ ] **Step 4: Migrate `design_schema_drift.rs:87-94`**
|
||||
|
||||
Same shape as Step 3.
|
||||
|
||||
- [ ] **Step 5: Migrate `lex.rs:320, 328`**
|
||||
|
||||
`tokenize("+ == <= io/print_int std_list.map")` exercises that
|
||||
the lexer treats `io/print_int` as a single identifier-with-slash
|
||||
token. The op name is incidental; the assertion is on the token
|
||||
shape. Swap to `io/print_str`. Update the `assert_eq!` vector
|
||||
at line 328 to match.
|
||||
|
||||
- [ ] **Step 6: Migrate `prose/src/lib.rs:1585-1593`**
|
||||
|
||||
`do_renders_with_keyword_and_op` round-trip test. Asserts
|
||||
`render_term(&t) == "do io/print_int(5)"`. Swap to
|
||||
`render_term(&t) == "do io/print_str(\"5\")"` (with `Str` literal
|
||||
arg) OR pick a different surviving op if `io/print_str` makes
|
||||
the test awkward.
|
||||
|
||||
- [ ] **Step 7: `cargo test --workspace` green**
|
||||
|
||||
Run: `cargo test --workspace`
|
||||
Expected: 0 failures. Task 5 closes the test-body migration set.
|
||||
|
||||
---
|
||||
|
||||
## Task 6: Documentation-comment touch-ups
|
||||
|
||||
**Files:**
|
||||
- Modify: `crates/ailang-surface/src/lex.rs:18-20`
|
||||
- Modify: `crates/ailang-surface/src/parse.rs:1377`
|
||||
- Modify: `crates/ail/src/main.rs:30-31, 1419-1420`
|
||||
- Modify: `runtime/str.c:117-118`
|
||||
- Modify: `crates/ailang-core/specs/form_a.md:433-434`
|
||||
|
||||
- [ ] **Step 1: Swap `lex.rs:18-20` module-doc-comment**
|
||||
|
||||
Replace the `io/print_int` example with `io/print_str` in the
|
||||
qualified-name-tokenisation explanation.
|
||||
|
||||
- [ ] **Step 2: Swap `parse.rs:1377` diagnostic example**
|
||||
|
||||
`expect_ident("effect op (e.g. `io/print_int`)")` → swap to
|
||||
`io/print_str`.
|
||||
|
||||
- [ ] **Step 3: Swap `main.rs:30-31` and `:1419-1420`**
|
||||
|
||||
`main.rs:30-31`: `builtins` subcommand doc — list a single
|
||||
`io/print_str` (drop the per-type ops).
|
||||
`main.rs:1419-1420`: `walk_term` Do-arm format-string comment
|
||||
— swap to `io/print_str`.
|
||||
|
||||
- [ ] **Step 4: Re-anchor `runtime/str.c:117-118`**
|
||||
|
||||
Original: `matches io/print_float's libc rendering for IEEE
|
||||
consistency`. Rewrite to: `matches the same %g format used
|
||||
throughout the runtime for Float text rendering` (or similar —
|
||||
the anchor is the format, not the now-retired effect-op).
|
||||
|
||||
- [ ] **Step 5: Migrate `form_a.md:433-434`**
|
||||
|
||||
The surface-spec example shows `(do io/print_int n)`. Migrate
|
||||
to `(app print n)` to advertise the post-iter canonical idiom.
|
||||
|
||||
- [ ] **Step 6: `cargo test --workspace` green**
|
||||
|
||||
Run: `cargo test --workspace`
|
||||
Expected: 0 failures. (Doc-comment changes can break tests if
|
||||
they're embedded in doc-tests; verify.)
|
||||
|
||||
---
|
||||
|
||||
## Task 7: DESIGN.md sweep
|
||||
|
||||
**Files:**
|
||||
- Modify: `docs/DESIGN.md` lines 331, 1990-1992, 2024-2025, 2331,
|
||||
2565-2577, 2679, 2699.
|
||||
|
||||
- [ ] **Step 1: Line 331 — Decision 11 example**
|
||||
|
||||
Original (excerpt): "names like `io/print_int`". Swap to
|
||||
`io/print_str`.
|
||||
|
||||
- [ ] **Step 2: Lines 1990-1992 — Polymorphic print past-tense rewrite**
|
||||
|
||||
Original:
|
||||
> Routing through `print` replaces the ad-hoc `io/print_int|bool|float`
|
||||
> idiom for new code; retiring the per-type effect-ops is queued
|
||||
> as a P2 follow-up.
|
||||
|
||||
Rewrite:
|
||||
> Routing through `print` replaces the ad-hoc per-type print
|
||||
> effect-ops; the `io/print_int`, `io/print_bool`, `io/print_float`
|
||||
> primitives were retired 2026-05-14 in iter rpe.1, leaving
|
||||
> `io/print_str` as the only surviving direct-output effect-op.
|
||||
|
||||
- [ ] **Step 3: Lines 2024-2025 — Heap-Str primitives output sentence**
|
||||
|
||||
Original:
|
||||
> Primitive output goes through `io/print_int` / `io/print_bool` /
|
||||
> `io/print_str` directly.
|
||||
|
||||
Rewrite:
|
||||
> Primitive output for `Str` values goes through `io/print_str`
|
||||
> directly; values of other primitive types route through the
|
||||
> polymorphic `print` helper (§"Polymorphic print"), which feeds
|
||||
> the heap-Str result of `show x` into `io/print_str`. The
|
||||
> per-type effect-ops `io/print_int`, `io/print_bool`,
|
||||
> `io/print_float` were retired in iter rpe.1 (2026-05-14).
|
||||
|
||||
- [ ] **Step 4: Line 2331 — Effect-op invocation comment example**
|
||||
|
||||
Original: `// Effect-op invocation. \`op\` is "<eff>/<op>"
|
||||
(e.g. "io/print_int").`
|
||||
Rewrite: `// Effect-op invocation. \`op\` is "<eff>/<op>"
|
||||
(e.g. "io/print_str").`
|
||||
|
||||
- [ ] **Step 5: Lines 2565-2577 — Float NaN-rendering paragraph**
|
||||
|
||||
The paragraph anchors NaN textual semantics on `io/print_float`
|
||||
and contrasts with `do print x`. After iter rpe.1, both ends of
|
||||
that contrast reach `%g` via `float_to_str` + `io/print_str`.
|
||||
|
||||
Rewrite the paragraph to anchor on `float_to_str` (the canonical
|
||||
heap-Str producer for Float) rather than the now-retired
|
||||
`io/print_float`. The `%g` semantics are unchanged; this is a
|
||||
re-anchoring, not a semantic change.
|
||||
|
||||
- [ ] **Step 6: Line 2679 — "What is supported (effect ops)"**
|
||||
|
||||
Original: `(\`io/print_int\`, \`io/print_bool\`, \`io/print_str\`)`
|
||||
Rewrite: `(\`io/print_str\`)`. Add a sentence: `Per-type print
|
||||
ops (\`io/print_int\`, \`io/print_bool\`, \`io/print_float\`)
|
||||
were retired in iter rpe.1; polymorphic \`print\` is the canonical
|
||||
output path.`
|
||||
|
||||
- [ ] **Step 7: Line 2699 — "(IO effect ops)" list**
|
||||
|
||||
Original: `the IO effect ops (\`io/print_int|bool|str|float\`)`
|
||||
Rewrite: `the IO effect op \`io/print_str\``.
|
||||
|
||||
- [ ] **Step 8: `cargo test --workspace` green**
|
||||
|
||||
Run: `cargo test --workspace`
|
||||
Expected: 0 failures. The `design_schema_drift` test (mentioned
|
||||
in Task 5) reads DESIGN.md; verify it still passes after these
|
||||
swaps.
|
||||
|
||||
---
|
||||
|
||||
## Task 8: Incidental E2E test-comment touch-ups
|
||||
|
||||
**Files:**
|
||||
- Modify: `crates/ail/tests/eq_ord_e2e.rs:66, 76`
|
||||
- Modify: `crates/ail/tests/e2e.rs:2674-2683`
|
||||
- Modify: `crates/ail/tests/floats_e2e.rs:7`
|
||||
|
||||
- [ ] **Step 1: Update `eq_ord_e2e.rs` comments**
|
||||
|
||||
Lines 66 and 76 reference `io/print_int` emitting `<int>\n`.
|
||||
The asserted stdout is unchanged (post-migration `(app print x)`
|
||||
at `x : Int` also emits `<int>\n`). Update the comments to
|
||||
reference `print` semantics. The test body itself does not
|
||||
change (it was migrated in Task 2 via the fixture migration).
|
||||
|
||||
- [ ] **Step 2: Update `e2e.rs:2674-2683` (int_arg_to_effect_op test)**
|
||||
|
||||
The test pins the RC-tracking property "Int args passed to an
|
||||
effect-op are not consumed". The fixture `int_to_print_int_borrow.ail`
|
||||
migrates in Task 2. The test name and doc-comment lose
|
||||
grounding once `io/print_int` is gone.
|
||||
|
||||
Two options:
|
||||
- Update the doc-comment to explain that the test now pins
|
||||
"Int args passed to `print` (the polymorphic helper) are not
|
||||
consumed" — the property is the same.
|
||||
- Rename the test if Boss prefers (e.g.
|
||||
`int_arg_to_print_does_not_rc_track`). Renaming changes a
|
||||
test identity; lean toward keeping the name and updating the
|
||||
doc.
|
||||
|
||||
Pick keep-the-name-and-update-the-doc. The test pins the
|
||||
property, not the identifier.
|
||||
|
||||
- [ ] **Step 3: Update `floats_e2e.rs:7` module doc-comment**
|
||||
|
||||
Lists `io/print_float` as a covered op. Swap to `print` / Show
|
||||
Float.
|
||||
|
||||
- [ ] **Step 4: `cargo test --workspace` green**
|
||||
|
||||
Run: `cargo test --workspace`
|
||||
Expected: 0 failures.
|
||||
|
||||
---
|
||||
|
||||
## Task 9: Bench run + conditional ratification
|
||||
|
||||
**Files:**
|
||||
- Conditional Modify: `bench/baseline.json`, `bench/baseline_cross_lang.json`,
|
||||
`bench/baseline_compile.json`.
|
||||
|
||||
- [ ] **Step 1: Run `bench/check.py`**
|
||||
|
||||
Run: `python3 bench/check.py`
|
||||
Expected exit code: `0` (compile-time-symmetric). If `1`
|
||||
(drift), investigate before ratifying — the spec did not
|
||||
anticipate a check-perf change.
|
||||
|
||||
- [ ] **Step 2: Run `bench/compile_check.py`**
|
||||
|
||||
Run: `python3 bench/compile_check.py`
|
||||
Expected exit code: `0`. Same rationale.
|
||||
|
||||
- [ ] **Step 3: Run `bench/cross_lang.py`**
|
||||
|
||||
Run: `python3 bench/cross_lang.py`
|
||||
Expected exit code: `0` OR `1` (legitimate drift if any bench
|
||||
fixture's hot loop now allocates a heap-Str per print).
|
||||
|
||||
- [ ] **Step 4: Conditional ratification — if any script returned `1`**
|
||||
|
||||
For each script that returned `1`:
|
||||
1. Inspect the per-fixture report to identify which fixtures
|
||||
moved.
|
||||
2. Run `python3 <script> --update-baseline` to write the new
|
||||
baseline.
|
||||
3. Note in the per-iter journal: which baseline was updated,
|
||||
the magnitude of the drift, and the language reason
|
||||
(post-iter `print` heap-alloc-per-iteration is the canonical
|
||||
output path; old baseline reflected the retired direct-printf
|
||||
path).
|
||||
|
||||
- [ ] **Step 5: All scripts green at final state**
|
||||
|
||||
After any ratification, re-run all three scripts; all must
|
||||
return `0`.
|
||||
|
||||
---
|
||||
|
||||
## Task 10: Roadmap strike + per-iter journal + stats
|
||||
|
||||
**Files:**
|
||||
- Modify: `docs/roadmap.md` P2 lines 80-90 (entry strike).
|
||||
- Create: `docs/journals/2026-05-14-iter-rpe.1.md` (per-iter
|
||||
journal).
|
||||
- Create: `bench/orchestrator-stats/2026-05-14-iter-rpe.1.json`
|
||||
(stats file per `implement/SKILL.md`).
|
||||
|
||||
- [ ] **Step 1: Strike the roadmap entry**
|
||||
|
||||
In `docs/roadmap.md` lines 80-90 (the
|
||||
`Retire io/print_int / io/print_bool / io/print_float` P2
|
||||
entry), change `- [ ]` → `- [x]` and append a one-line tail
|
||||
naming this iter:
|
||||
|
||||
```markdown
|
||||
- [x] **\[milestone\]** Retire `io/print_int` / `io/print_bool` /
|
||||
`io/print_float` effect-ops + migrate example corpus to `print`.
|
||||
Shipped 2026-05-14 as iter rpe.1.
|
||||
- context: per-iter journal `docs/journals/2026-05-14-iter-rpe.1.md`.
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Write the per-iter journal**
|
||||
|
||||
Standard journal shape: `# iter rpe.1 — Retire per-type print
|
||||
effect-ops`, sections: Summary / Decisions / Files touched /
|
||||
Verification / Concerns / Known debt.
|
||||
|
||||
The journal records the §C4 (a) decision (migrate all,
|
||||
including bench-latency fixtures) and any bench-baseline
|
||||
ratifications from Task 9.
|
||||
|
||||
- [ ] **Step 3: Write the stats file**
|
||||
|
||||
JSON file at `bench/orchestrator-stats/2026-05-14-iter-rpe.1.json`
|
||||
recording: tasks completed, retries per task, blocked count,
|
||||
duration, `cargo test --workspace` final count.
|
||||
|
||||
- [ ] **Step 4: Final `cargo test --workspace` + clippy + doc**
|
||||
|
||||
Run, in parallel:
|
||||
- `cargo test --workspace`
|
||||
- `cargo clippy --workspace --all-targets -- -D warnings`
|
||||
- `cargo doc --workspace --no-deps 2>&1 | grep -c '^warning:'`
|
||||
|
||||
Expected: tests green; clippy zero (the iter-clippy-sweep
|
||||
baseline holds); doc zero (the rustdoc-sweep baseline holds).
|
||||
|
||||
Any clippy / rustdoc regression introduced by this iter is
|
||||
itself a Task-N debt; fix in-task before declaring DONE.
|
||||
|
||||
---
|
||||
|
||||
## Self-review
|
||||
|
||||
1. **Spec coverage:** every §C/§D/§G/§I-named element has a
|
||||
task — RED test (Task 1, spec §G2), corpus migration (Task 2,
|
||||
spec §C1 + §D1), prose snapshot regen (Task 3, §D1 extension
|
||||
via Errata #2), four-site deletion (Task 4, spec §C2 + Errata
|
||||
#3), test-body migration (Task 5, Errata #4), doc-comment
|
||||
touch-ups (Task 6, Errata #4), DESIGN.md sweep (Task 7, spec
|
||||
§C3), E2E-comment touch-ups (Task 8, partial Errata #4), bench
|
||||
ratification (Task 9, spec §C4 (a) + §G3), roadmap strike +
|
||||
journal + stats (Task 10, spec §I task 7).
|
||||
|
||||
2. **Placeholder scan:** zero "TBD" / "TODO" / "similar to Task".
|
||||
|
||||
3. **Type consistency:** `env.effect_ops` (the BTreeMap on `Env`),
|
||||
`lower_effect_op` (the codegen dispatcher), `build_check_env`
|
||||
(entry point used by the RED test) all match real symbol names
|
||||
in the workspace.
|
||||
|
||||
4. **Step granularity:** every numbered step is 2–5 minutes.
|
||||
Task 2 Step 2 is "92 file edits" but they are mechanical and
|
||||
the LLM can stream them with one Edit-tool call per file.
|
||||
|
||||
5. **No commit steps:** zero `git commit` calls in any task
|
||||
template. The orchestrator-agent (or implement-skill) leaves
|
||||
the working tree dirty; Boss commits.
|
||||
Reference in New Issue
Block a user