iter loop-recur.tidy GREEN: reject lambda-captures-loop-binder at check + milestone-close audit resolution
GREEN side of the RED audit-trail commit 39380d3. The loop/recur
milestone-close audit found a [high] correctness defect — a lambda
capturing a loop binder passed `ail check` then panicked
unreachable!() at codegen (crates/ailang-codegen/src/lambda.rs:102)
on type-correct input. Fixed symmetrically to mut.4-tidy: new
CheckError::LoopBinderCapturedByLambda (code
loop-binder-captured-by-lambda, bracket-free F2 Display), the
Term::Lam escape guard gained a parallel loop_stack pass. Minimal
mechanism: loop_stack element Vec<Type> -> Vec<(String,Type)> so
the already-threaded per-loop frame carries binder names; recur
still reads .1 by position (Boss-call-2 positional invariant
preserved verbatim — the name is a second field for the escape
guard only, a consumer iter-2 did not anticipate). Codegen
byte-unchanged (typecheck-only fix). carve_out 17->18 + ct1-F2 +
DESIGN.md note lockstep.
Folded in (Boss-side, audit resolution): the two [medium]
doc-honesty edits the audit surfaced (mut_var_allocas rustdoc now
states its mut-var+loop-binder dual use; Term::Loop doc-comment
now describes the real shipped state, not iter-1's stale
"per-binder phi" forward-look) + a [low] P2 roadmap todo
(plan-recon undercount countermeasure, pairs with planner Step-5
items 7+8). Bench gate: all 3 scripts exit 0, 25 metrics 0
regressed — pristine, carry-on (no baseline/ratify).
cargo test --workspace 619 -> 622 / 0 red (Boss-reran
independently, hash_pin 11/0). The loop/recur milestone is
audit-clean; fieldtest is the only remaining step before close.
This commit is contained in:
@@ -548,12 +548,18 @@ pub enum Term {
|
||||
/// additive: pre-existing fixtures hash bit-identically because
|
||||
/// none carry the `"t":"loop"` tag. `binders` has no
|
||||
/// `skip_serializing_if` (mirrors `Term::Mut.vars` — the field
|
||||
/// is part of the shape). Typecheck binder/recur semantics land
|
||||
/// in loop-recur iter 2 (`synth` stubs with `CheckError::Internal`
|
||||
/// in this iter); codegen (loop-header + per-binder phi +
|
||||
/// back-edge) in iter 3 (`lower_term` stubs with
|
||||
/// `CodegenError::Internal`). No totality claim — an infinite
|
||||
/// loop is legal. See `docs/specs/2026-05-17-loop-recur.md`.
|
||||
/// is part of the shape). Typecheck (loop-recur.2): `synth`
|
||||
/// types each binder init, the loop's type is the body's type;
|
||||
/// `recur` arity/type is checked positionally via `loop_stack`
|
||||
/// and `verify_loop_body` enforces `recur`-in-tail-position; a
|
||||
/// lambda capturing a binder is rejected
|
||||
/// (`loop-binder-captured-by-lambda`, loop-recur.tidy). Codegen
|
||||
/// (loop-recur.3): binders lower to entry-block allocas reached
|
||||
/// from a `loop.header` block; `recur` stores + back-edges; the
|
||||
/// loop-carried SSA / phi form is produced by `clang -O2`
|
||||
/// mem2reg (NOT hand-emitted phi). No totality claim — an
|
||||
/// infinite loop is legal. See
|
||||
/// `docs/specs/2026-05-17-loop-recur.md`.
|
||||
Loop {
|
||||
binders: Vec<LoopBinder>,
|
||||
body: Box<Term>,
|
||||
|
||||
Reference in New Issue
Block a user