plan: mir.3b fill MArg.mode, switch the anon-temp gate onto it, delete module_def_ail_types
Executable plan for the second of two iterations delivering spec 0060's mir.3 row. A focused recon (the anon-temp gate + the per-arg mode sources) established two refinements to the spec sketch, both locked in the plan: 1. module_def_ail_types is deleted in mir.3b, not mir.5. A grep proved self.module_def_ail_types has exactly ONE reader — the emit_call anon-temp borrow-slot drop gate. The own-param drop reads param_modes from the def's own f.ty, not this table. So switching that gate onto MArg.mode leaves the table dead; deleting it now is the clean move. mir.5's "last re-derivation residue" shrinks to element-type/Term::New. 2. MArg.mode is filled for App args only; MTerm::Let.mode is not filled. Only App args have a real per-arg mode source (the callee Type::Fn.param_modes). Do/Ctor/Recur args have no per-arg mode (EffectOpSig/Ctor.fields/loop-binders carry none) -> stay Owned default. Let.mode has no source (ast::Term::Let has no mode field) and no consumer (the let-drop gate reads consume, not Let.mode) -> stays Owned. Safety property (recon-confirmed on the #43 anon-temp witness): MArg.mode is filled from the same callee fn-type the gate read from the table, so the drop fires identically. For (app RawBuf.get (app RawBuf.set …) 0), RawBuf.get's param 0 is borrow -> args[0].mode = Borrow, exactly the value module_def_ail_types yielded. Four tasks: (1) producer fills App-arg MArg.mode via a param_mode_at helper; (2) codegen gate reads arg.mode + delete module_def_ail_types (field + construction + threading, compiler-completeness-checked); (3) producer pin (App arg mode == Borrow) + the live=0 anon-temp acceptance (raw_buf_owned_drop_balances_rc_stats); (4) record the refinements in spec 0060. No new .ail fixture (reuses raw_buf_drop_min.ail).
This commit is contained in:
@@ -0,0 +1,428 @@
|
|||||||
|
# mir.3b — fill MArg.mode, switch the anon-temp drop gate onto it, delete module_def_ail_types — Implementation Plan
|
||||||
|
|
||||||
|
> **Parent spec:** `docs/specs/0060-typed-mir.md` (Iteration decomposition table, mir.3 row — second of two iterations; see mir.3a plan `docs/plans/0117-*`)
|
||||||
|
>
|
||||||
|
> **For agentic workers:** REQUIRED SUB-SKILL: use the `implement`
|
||||||
|
> skill to run this plan. Steps use `- [ ]` checkboxes for tracking.
|
||||||
|
|
||||||
|
**Goal:** Fill `MArg.mode` for App args from the callee's param modes,
|
||||||
|
switch codegen's `emit_call` anon-temp borrow-slot drop gate to read
|
||||||
|
`MArg.mode` instead of re-looking-up the callee's `param_modes` from
|
||||||
|
the codegen-side `module_def_ail_types` table, and — since that gate is
|
||||||
|
the table's only reader — delete `module_def_ail_types` entirely.
|
||||||
|
|
||||||
|
**Architecture:** `lower_to_mir`'s `Term::App` arm already holds the
|
||||||
|
resolved callee fn-type (`sig = synth_pure(callee)`, a `Type::Fn` with
|
||||||
|
`param_modes`). mir.3b reads `sig.param_modes[i]` into each App arg's
|
||||||
|
`MArg.mode`. Codegen's anon-temp gate then tests `arg.mode == Borrow`
|
||||||
|
in place of the `module_def_ail_types[target][def].param_modes[i]`
|
||||||
|
lookup. With that read gone, `module_def_ail_types` has zero readers
|
||||||
|
and is removed (field + construction + threading). This is a faithful
|
||||||
|
swap: `MArg.mode` is filled from the same callee fn-type the gate read
|
||||||
|
from the table, so the gate's behaviour is unchanged.
|
||||||
|
|
||||||
|
**Tech Stack:** `ailang-check::lower_to_mir` (producer: App-arg mode
|
||||||
|
fill), `ailang-codegen` (consumer: the gate + the deleted table).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Design decisions locked before this plan (orchestrator)
|
||||||
|
|
||||||
|
Two refinements to the spec's mir.3b sketch, settled in planning from a
|
||||||
|
focused recon:
|
||||||
|
|
||||||
|
1. **`module_def_ail_types` is deleted in mir.3b, not mir.5.** The spec
|
||||||
|
parked it as mir.5's "last re-derivation residue", assuming multiple
|
||||||
|
readers. A grep proved `self.module_def_ail_types` is read at exactly
|
||||||
|
**one** site — the `emit_call` anon-temp gate (`lib.rs:2701`). The
|
||||||
|
own-param drop reads `param_modes` from the def's own `f.ty`
|
||||||
|
(`lib.rs:1356,1507`), not from this table. So once mir.3b switches
|
||||||
|
the gate onto `MArg.mode`, the table is dead; leaving it behind would
|
||||||
|
be dead code with a `#[allow]`. Deleting it now is the clean,
|
||||||
|
telos-aligned move (codegen reads the param mode off MIR, not a
|
||||||
|
parallel sig table). mir.5's residue shrinks to element-type /
|
||||||
|
`Term::New` (`New.elem`).
|
||||||
|
|
||||||
|
2. **`MArg.mode` is filled for App args only; `MTerm::Let.mode` is NOT
|
||||||
|
filled.** Of the four `arg()` call sites, only App args have a real
|
||||||
|
per-arg mode source (the callee `Type::Fn.param_modes`). Do args
|
||||||
|
(`EffectOpSig` has no `param_modes`), Ctor args (`ast::Ctor.fields`
|
||||||
|
is `Vec<Type>`, no per-field mode), and Recur args (loop binders
|
||||||
|
carry no mode) have no mode to fill → they stay `Mode::Owned`
|
||||||
|
(documented default; these positions consume their arg). And
|
||||||
|
`MTerm::Let.mode` has **no source** (`ast::Term::Let` carries no mode
|
||||||
|
field) **and no consumer** (the let-drop gate reads `self.consume`,
|
||||||
|
not `Let.mode`) — filling it would be inventing a value nobody reads.
|
||||||
|
So mir.3b leaves `Let.mode` at its `Owned` default.
|
||||||
|
|
||||||
|
The safety property (recon-confirmed on the #43 anon-temp witness): for
|
||||||
|
`(app RawBuf.get (app RawBuf.set buf 0 10) 0)`, `sig = synth_pure(RawBuf.get)`
|
||||||
|
carries `param_modes[0] = Borrow` (kernel sig
|
||||||
|
`crates/ailang-kernel/src/raw_buf/source.ail:14`), so `args[0].mode`
|
||||||
|
becomes `Borrow` — exactly the value the gate currently reads from
|
||||||
|
`module_def_ail_types`. Same source (the callee fn-type), so the drop
|
||||||
|
fires identically.
|
||||||
|
|
||||||
|
**Files this plan creates or modifies:**
|
||||||
|
|
||||||
|
- Modify: `crates/ailang-check/src/lower_to_mir.rs` — App-arm `MArg.mode` fill + a `param_mode_at` helper.
|
||||||
|
- Modify: `crates/ailang-codegen/src/lib.rs` — the anon-temp gate (`:2699-2723`); delete `module_def_ail_types` (`:328,332,401,439,782,985,1045`).
|
||||||
|
- Test: `crates/ailang-check/tests/lower_to_mir_ty.rs` — producer pin (App arg mode == Borrow).
|
||||||
|
- Modify (docs): `docs/specs/0060-typed-mir.md` — record the mir.3b refinements.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 1: Producer — fill `MArg.mode` for App args
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `crates/ailang-check/src/lower_to_mir.rs` (the `Term::App` arm + a new helper)
|
||||||
|
|
||||||
|
- [ ] **Step 1: Add the `param_mode_at` helper**
|
||||||
|
|
||||||
|
Insert above `fn lower_term` (near the `arg()` helper at
|
||||||
|
`crates/ailang-check/src/lower_to_mir.rs:103-107`):
|
||||||
|
|
||||||
|
```rust
|
||||||
|
/// The MIR `Mode` of a callee's parameter at position `i`, read off
|
||||||
|
/// the callee's `Type::Fn.param_modes`. `Borrow` maps to
|
||||||
|
/// `Mode::Borrow`; `Own` and `Implicit` (the `Implicit ≡ Own`
|
||||||
|
/// contract, `ast.rs` ParamMode) both map to `Mode::Owned`. Out of
|
||||||
|
/// range / non-fn → `Owned` (the consume default). Used by the App arm
|
||||||
|
/// to fill `MArg.mode` so codegen reads the slot mode off MIR.
|
||||||
|
fn param_mode_at(sig: &Type, i: usize) -> Mode {
|
||||||
|
match sig {
|
||||||
|
Type::Fn { param_modes, .. } => {
|
||||||
|
match param_modes.get(i) {
|
||||||
|
Some(ailang_core::ast::ParamMode::Borrow) => Mode::Borrow,
|
||||||
|
_ => Mode::Owned,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => Mode::Owned,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
> Implementer note: `Mode` is already imported in `lower_to_mir.rs`
|
||||||
|
> (the `arg()` helper builds `Mode::Owned`). `Type` is imported (the
|
||||||
|
> file's `use` line). `ailang_core::ast::ParamMode` is referenced
|
||||||
|
> fully-qualified above; if the file already imports `ParamMode`, use
|
||||||
|
> the short name. `Type::Fn`'s field is `param_modes: Vec<ParamMode>`
|
||||||
|
> (`crates/ailang-core/src/ast.rs:781`).
|
||||||
|
|
||||||
|
- [ ] **Step 2: Fill the App args with their slot modes**
|
||||||
|
|
||||||
|
In the `Term::App` arm of `lower_term` (the arm landed by mir.2 — it
|
||||||
|
binds `let sig = ctx.synth_pure(callee)?;` and builds `m_args` via the
|
||||||
|
`arg()` helper over `args.iter()`), replace the `m_args` construction:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
let m_args = args
|
||||||
|
.iter()
|
||||||
|
.map(|a| Ok(arg(lower_term(ctx, a)?)))
|
||||||
|
.collect::<Result<Vec<_>>>()?;
|
||||||
|
```
|
||||||
|
|
||||||
|
with a per-arg mode read off the callee `sig` already in scope:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// mir.3b: each App arg carries the callee's slot mode, read
|
||||||
|
// off the resolved callee fn-type. codegen's anon-temp drop
|
||||||
|
// gate reads this instead of re-looking-up the callee's
|
||||||
|
// param_modes from a sig table.
|
||||||
|
let m_args = args
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(i, a)| {
|
||||||
|
Ok(MArg {
|
||||||
|
term: lower_term(ctx, a)?,
|
||||||
|
mode: param_mode_at(&sig, i),
|
||||||
|
consume_count: 1,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect::<Result<Vec<_>>>()?;
|
||||||
|
```
|
||||||
|
|
||||||
|
> Implementer note: this is the ONLY `arg()` call site changed. The Do,
|
||||||
|
> Ctor, and Recur arms keep `arg(...)` (their args have no per-arg mode
|
||||||
|
> source — `Mode::Owned` default). `MArg` is already imported. `sig` is
|
||||||
|
> the `let sig = ctx.synth_pure(callee)?;` binding the mir.2 arm
|
||||||
|
> introduced — confirm it is in scope at the `m_args` construction (it
|
||||||
|
> is used just above to build `m_callee`).
|
||||||
|
|
||||||
|
- [ ] **Step 3: Build the producer crate**
|
||||||
|
|
||||||
|
Run: `cargo build -p ailang-check`
|
||||||
|
Expected: PASS. (codegen still reads the gate from `module_def_ail_types`
|
||||||
|
— unchanged until Task 2 — so the workspace also still builds; this is
|
||||||
|
a clean partial gate.)
|
||||||
|
|
||||||
|
- [ ] **Step 4: Build the workspace (still green — gate not yet switched)**
|
||||||
|
|
||||||
|
Run: `cargo build --workspace`
|
||||||
|
Expected: PASS. `MArg.mode` is now filled but the codegen gate still
|
||||||
|
reads the table; behaviour is unchanged.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 2: Consumer — gate reads `MArg.mode`; delete `module_def_ail_types`
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `crates/ailang-codegen/src/lib.rs:2699-2723` (the gate)
|
||||||
|
- Modify: `crates/ailang-codegen/src/lib.rs:328-330,332,401,439,782,985,1045` (delete the table)
|
||||||
|
|
||||||
|
- [ ] **Step 1: Switch the anon-temp gate to read `MArg.mode`**
|
||||||
|
|
||||||
|
At `crates/ailang-codegen/src/lib.rs:2699-2723`, replace:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
if !tail {
|
||||||
|
if let Some(Type::Fn { param_modes, .. }) = self
|
||||||
|
.module_def_ail_types
|
||||||
|
.get(target_module)
|
||||||
|
.and_then(|m| m.get(target_def))
|
||||||
|
.cloned()
|
||||||
|
{
|
||||||
|
for (i, (arg, (arg_ssa, arg_ty))) in
|
||||||
|
args.iter().zip(compiled_args.iter()).enumerate()
|
||||||
|
{
|
||||||
|
let is_borrow_slot =
|
||||||
|
matches!(param_modes.get(i), Some(ParamMode::Borrow));
|
||||||
|
if is_borrow_slot
|
||||||
|
&& arg_ty == "ptr"
|
||||||
|
&& arg_ssa != &dst
|
||||||
|
&& self.is_rc_heap_allocated(&arg.term)
|
||||||
|
{
|
||||||
|
let drop_sym = self.drop_symbol_for_binder(&arg.term, arg_ssa);
|
||||||
|
self.body.push_str(&format!(
|
||||||
|
" call void @{drop_sym}(ptr {arg_ssa})\n"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
with:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
if !tail {
|
||||||
|
// mir.3b: the per-arg slot mode rides on the MIR arg
|
||||||
|
// (`MArg.mode`, filled by lower_to_mir from the callee's
|
||||||
|
// `param_modes`), so the borrow-slot test reads it directly
|
||||||
|
// — no re-lookup of the callee's signature from a codegen
|
||||||
|
// sig table. `Borrow` slots borrow the arg, so an Own-ret
|
||||||
|
// heap temp landing in one is dropped here; `Own`/`Implicit`
|
||||||
|
// slots consume the arg (the callee dec's it).
|
||||||
|
for (arg, (arg_ssa, arg_ty)) in args.iter().zip(compiled_args.iter()) {
|
||||||
|
let is_borrow_slot = matches!(arg.mode, Mode::Borrow);
|
||||||
|
if is_borrow_slot
|
||||||
|
&& arg_ty == "ptr"
|
||||||
|
&& arg_ssa != &dst
|
||||||
|
&& self.is_rc_heap_allocated(&arg.term)
|
||||||
|
{
|
||||||
|
let drop_sym = self.drop_symbol_for_binder(&arg.term, arg_ssa);
|
||||||
|
self.body.push_str(&format!(
|
||||||
|
" call void @{drop_sym}(ptr {arg_ssa})\n"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
> Implementer note: `Mode` must be in scope — add it to the
|
||||||
|
> `use ailang_mir::{...}` line at `lib.rs:40` (currently
|
||||||
|
> `Callee, MArg, MTerm, MirDef, MirWorkspace` → add `Mode`). `arg` is
|
||||||
|
> already `&MArg` in the loop, so `arg.mode` is direct. `ParamMode` and
|
||||||
|
> `Type` remain imported (still used by the own-param drop at
|
||||||
|
> `:1356,:1507` and elsewhere) — do not remove their imports.
|
||||||
|
|
||||||
|
- [ ] **Step 2: Delete the `module_def_ail_types` field and all its plumbing**
|
||||||
|
|
||||||
|
`self.module_def_ail_types` now has zero readers (Step 1 removed the
|
||||||
|
only one). Remove the field and every construction/threading site.
|
||||||
|
Read each region and delete cleanly:
|
||||||
|
|
||||||
|
1. `lib.rs:782` — the `Emitter` field declaration `module_def_ail_types: &'a BTreeMap<...>,`. Delete the field line (and its doc comment if separable).
|
||||||
|
2. `lib.rs:985` — the `Emitter::new` parameter `module_def_ail_types: &'a BTreeMap<...>,`. Delete the parameter.
|
||||||
|
3. `lib.rs:1045` — the `module_def_ail_types,` field init in the `Self { ... }` literal. Delete.
|
||||||
|
4. `lib.rs:439` — the `&module_def_ail_types,` argument at the `Emitter::new` call site. Delete the argument.
|
||||||
|
5. `lib.rs:401` — `module_def_ail_types.insert(mname.clone(), ail_types);`. Delete; and read upward (`:390-401`) to delete the `ail_types` local it inserts IF that local is built solely to feed this insert (it is the AILang-`Type`-per-fn map; confirm no other use before deleting its construction loop).
|
||||||
|
6. `lib.rs:332` — `let mut module_def_ail_types: BTreeMap<...> = BTreeMap::new();`. Delete.
|
||||||
|
7. `lib.rs:328-330` — the doc comment describing `module_def_ail_types`. Delete (it documents a now-removed field).
|
||||||
|
|
||||||
|
> Implementer note: the compiler is the completeness check here —
|
||||||
|
> after the field/param/init/arg deletions, any residual reference is a
|
||||||
|
> hard error naming the exact line. Work outside-in (field → param →
|
||||||
|
> init → call-arg → construction) and let `cargo build` flag anything
|
||||||
|
> missed. Do NOT touch `module_user_fns`, `module_ctor_index`,
|
||||||
|
> `module_consts`, or `current_param_modes` — those are different
|
||||||
|
> tables with live readers.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Build the workspace (the consuming gate)**
|
||||||
|
|
||||||
|
Run: `cargo build --workspace`
|
||||||
|
Expected: PASS. Zero errors, zero `unused`/`dead_code` warnings for
|
||||||
|
`module_def_ail_types` (it is fully removed) or `ail_types` (its
|
||||||
|
construction removed in Step 2.5). If a `Type`/`ParamMode` import is
|
||||||
|
now unused, the compiler will say so — but both have other live readers
|
||||||
|
(`:1356,:1507`), so they should remain used.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 3: Acceptance — anon-temp drop still fires, now from MArg.mode
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Test: `crates/ailang-check/tests/lower_to_mir_ty.rs` (producer pin)
|
||||||
|
|
||||||
|
- [ ] **Step 1: Producer pin — the borrow-slot App arg carries `Mode::Borrow`**
|
||||||
|
|
||||||
|
Add a test to `crates/ailang-check/tests/lower_to_mir_ty.rs` that
|
||||||
|
elaborates the existing anon-temp witness fixture and asserts the
|
||||||
|
borrow-slot arg's mode. The fixture `examples/raw_buf_drop_min.ail` has
|
||||||
|
`main` body `(app RawBuf.get (app RawBuf.set buf 0 10) 0)` — `RawBuf.get`
|
||||||
|
param 0 is `(borrow (con RawBuf a))`, so the outer App's arg 0 must be
|
||||||
|
`Mode::Borrow`:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[test]
|
||||||
|
fn app_arg_carries_callee_borrow_mode() {
|
||||||
|
// mir.3b: lower_to_mir fills MArg.mode from the callee's param_modes.
|
||||||
|
// In `(app RawBuf.get <set-result> 0)`, RawBuf.get's param 0 is a
|
||||||
|
// borrow receiver, so the outer App's arg 0 must be Mode::Borrow —
|
||||||
|
// the value codegen's anon-temp drop gate now reads off MIR.
|
||||||
|
let ws = elaborate_fixture("raw_buf_drop_min");
|
||||||
|
let m = ws.modules.get("raw_buf_drop_min").expect("module");
|
||||||
|
let main = m.defs.iter().find(|d| d.name == "main").expect("main");
|
||||||
|
// Navigate to the outer App (the `RawBuf.get` call). Read the
|
||||||
|
// sibling tests for the helper that reaches a named call node; the
|
||||||
|
// body is `(let buf … (app RawBuf.get (app RawBuf.set …) 0))`, so
|
||||||
|
// the outer App is the Let body.
|
||||||
|
let outer = find_app_with_callee(&main.body, "RawBuf.get")
|
||||||
|
.expect("RawBuf.get App");
|
||||||
|
if let MTerm::App { args, .. } = outer {
|
||||||
|
assert!(
|
||||||
|
matches!(args[0].mode, Mode::Borrow),
|
||||||
|
"RawBuf.get arg 0 (borrow receiver) must be Mode::Borrow, got {:?}",
|
||||||
|
args[0].mode,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
panic!("expected MTerm::App");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
> Implementer note: match the file's real fixture helper
|
||||||
|
> (`elaborate_fixture`) and add a small navigation helper
|
||||||
|
> `find_app_with_callee(t: &MTerm, name: &str) -> Option<&MTerm>` that
|
||||||
|
> walks the MTerm tree (recursing through Let body/init, App args, If
|
||||||
|
> branches, etc.) returning the first `MTerm::App` whose callee is a
|
||||||
|
> `Callee::Static`/`Builtin` with the given name — OR, simpler, reach
|
||||||
|
> the node structurally by matching `main.body` as
|
||||||
|
> `MTerm::Let { body, .. }` and then the body as the `RawBuf.get` App
|
||||||
|
> (read the fixture: `main` is `(let buf (new RawBuf …) (app RawBuf.get …))`).
|
||||||
|
> Import `MTerm`, `Mode`, `Callee` from `ailang_mir`. The exact
|
||||||
|
> navigation is an implementer choice; the assertion (arg 0 mode ==
|
||||||
|
> Borrow) is the fixed target.
|
||||||
|
|
||||||
|
- [ ] **Step 2: Run the producer pin**
|
||||||
|
|
||||||
|
Run: `cargo test -p ailang-check --test lower_to_mir_ty`
|
||||||
|
Expected: PASS — including `app_arg_carries_callee_borrow_mode`.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Full suite — the anon-temp drop acceptance**
|
||||||
|
|
||||||
|
Run: `cargo test --workspace`
|
||||||
|
Expected: PASS. The drop-correctness witness for this gate is
|
||||||
|
`raw_buf_owned_drop_balances_rc_stats` (`crates/ail/tests/e2e.rs`,
|
||||||
|
fixture `examples/raw_buf_drop_min.ail`, asserts `live == 0`): the
|
||||||
|
Own-ret `RawBuf.set` temp passed into `RawBuf.get`'s borrow slot must
|
||||||
|
still be dropped — now driven by `MArg.mode`. A wrong mode fill →
|
||||||
|
either a leak (`live > 0`, drop skipped) or a double-free (drop fired on
|
||||||
|
an owned slot). The broader RC-stats leak pins
|
||||||
|
(`raw_buf_loop_no_leak_pin`, `loop_recur_heap_binder_no_leak_pin`,
|
||||||
|
`print_no_leak_pin`) stay green; `loop_recur_str_binder_no_leak_pin`
|
||||||
|
stays `#[ignore]` (#49, mir.4); #51/#53 build guards green.
|
||||||
|
|
||||||
|
> Run the FULL workspace suite — this perturbs the call-site drop path.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 4: Record the mir.3b refinements in the spec
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `docs/specs/0060-typed-mir.md`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Note the App-only mode fill + the early table deletion**
|
||||||
|
|
||||||
|
In `docs/specs/0060-typed-mir.md`, after the mir.3-split note (added by
|
||||||
|
the mir.3a plan), append:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
> mir.3b refinement (discovered in planning, `docs/plans/0118-mir.3b-marg-mode-and-table-delete.md`):
|
||||||
|
> `MArg.mode` is filled for App args only — the callee's `Type::Fn.param_modes`
|
||||||
|
> is the sole real per-arg mode source; Do args (`EffectOpSig` has no
|
||||||
|
> param modes), Ctor args (no per-field mode), and Recur args (loop
|
||||||
|
> binders carry no mode) stay `Mode::Owned`. `MTerm::Let.mode` is NOT
|
||||||
|
> filled: a let-binder has no author-declared mode and no codegen
|
||||||
|
> consumer (the let-drop gate reads `consume`, not `Let.mode`), so it
|
||||||
|
> stays `Owned`. Switching `emit_call`'s anon-temp borrow-slot gate
|
||||||
|
> onto `MArg.mode` removes the only reader of codegen's
|
||||||
|
> `module_def_ail_types` table, so that table is **deleted in mir.3b**,
|
||||||
|
> not mir.5 — the mir.5 row's "last re-derivation residue" shrinks to
|
||||||
|
> the element-type / `Term::New` (`New.elem`) work.
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: No-op re-gate**
|
||||||
|
|
||||||
|
Run: `cargo test --workspace`
|
||||||
|
Expected: PASS (doc-only edit).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Self-review (planner, inline)
|
||||||
|
|
||||||
|
1. **Spec coverage.** mir.3 row's `Mode` MIR addition is delivered for
|
||||||
|
the position that has a real source (App args) and wired to its one
|
||||||
|
consumer (the anon-temp gate); the gate switch additionally retires
|
||||||
|
`module_def_ail_types`. The non-filled positions (Do/Ctor/Recur/Let)
|
||||||
|
are justified by the absence of a mode source/consumer, not by
|
||||||
|
effort, and recorded in the spec (Task 4).
|
||||||
|
2. **Placeholder scan.** No "TBD/TODO/similar to". Exact before→after
|
||||||
|
for the substantive edits (the helper, the App arm, the gate); the
|
||||||
|
field-deletion (Task 2 Step 2) is an enumerated outside-in site list
|
||||||
|
the compiler completeness-checks, and the test navigation (Task 3) is
|
||||||
|
a framed implementer-choice with a fixed assertion target — neither
|
||||||
|
is an open design decision.
|
||||||
|
3. **Type/name consistency.** `param_mode_at`, `MArg.mode`, `Mode::Borrow`,
|
||||||
|
`module_def_ail_types`, `arg.mode`, `is_rc_heap_allocated`,
|
||||||
|
`drop_symbol_for_binder` consistent across tasks.
|
||||||
|
4. **Step granularity.** Each step is one edit or one command.
|
||||||
|
5. **No commit steps.** None present.
|
||||||
|
6. **Pin/replacement substring contiguity.** No grep-presence pin paired
|
||||||
|
with a wrapped replacement. The producer pin asserts on
|
||||||
|
`args[0].mode` matching `Mode::Borrow` (a pattern match, not a
|
||||||
|
substring).
|
||||||
|
7. **Compile-gate vs deferred-caller ordering.** Task 1 changes no
|
||||||
|
signature (the `arg()` helper is untouched; the App arm builds `MArg`
|
||||||
|
inline) and its gate is a clean per-crate then workspace build. Task
|
||||||
|
2 deletes the field and threads every construction/init/arg site in
|
||||||
|
the same task, closing with the workspace build — no caller deferred
|
||||||
|
past a build gate. The `Emitter::new` signature change (Step 2.2) and
|
||||||
|
its one call site (Step 2.4) are in the same task.
|
||||||
|
8. **Verification-command filter strings resolve.**
|
||||||
|
`cargo test -p ailang-check --test lower_to_mir_ty` targets a real
|
||||||
|
file; `app_arg_carries_callee_borrow_mode` is the exact new test;
|
||||||
|
`raw_buf_owned_drop_balances_rc_stats` is the real anon-temp witness
|
||||||
|
(recon-confirmed, `e2e.rs`); the workspace gates are unfiltered.
|
||||||
|
9. **Parse-the-bytes gate.** mir.3b inlines NO new `.ail` fixture (the
|
||||||
|
producer pin reuses the committed `examples/raw_buf_drop_min.ail`).
|
||||||
|
Documented no-op for the `spec_validation` gate.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Handoff
|
||||||
|
|
||||||
|
Plan sits unstaged at `docs/plans/0118-mir.3b-marg-mode-and-table-delete.md`.
|
||||||
|
Hand to `implement` (standard mode, all four tasks). The orchestrator
|
||||||
|
commits the plan when handing it forward and the iteration diff at iter
|
||||||
|
close.
|
||||||
Reference in New Issue
Block a user