iter loop-recur.1: additive Term::Loop / Term::Recur / LoopBinder foundation

First of three iterations of the standalone loop/recur milestone
(spec 97c1ed1). loop/recur become real parseable / printable /
round-trippable / hash-stable strictly-additive AST nodes across
every no-wildcard exhaustive Term match in all six crates, plus
parse/print, prose arms, DESIGN.md + form_a.md schema blocks,
drift/coverage/hash anchors, and the spec's worked sum_to program
as a green round-trip fixture. NO typecheck semantics and NO real
codegen this iter by design: synth and lower_term are stubbed
CheckError::Internal / CodegenError::Internal exactly as mut.1
(real typecheck = iter 2, real loop-header/phi/back-edge = iter 3).

Two binding Boss design calls recorded in the plan header and the
journal: (1) verify_tail_positions "byte-unchanged" = its tail-app
verification role is unchanged, not its source — it is an
exhaustive no-wildcard match so two descent-only arms are
mandatory; acceptance evidence is the tail-app non-regression test.
(2) codegen is in iter-1 scope as stubs/pass-throughs because the
workspace must compile for cargo test --workspace.

Three plan-pseudo-vs-reality substitutions (serde Type::Con
literal, bare Int -> (con Int), unqualified LoopBinder) and one
recon-undercount integration-test site, all intent-preserving and
journalled. Boss forward-fix folded in: the committed specs
themselves wrote bare Int in the loop-binder snippets (parses as
Type::Var) — corrected the three headline snippets to (con Int) for
doc-honesty (no design/schema change).

cargo test --workspace 600 -> 608 / 0 red (Boss-reran
independently); iter13a + new loop_recur hash pins green.
This commit is contained in:
2026-05-17 23:00:15 +02:00
parent a5eebcd5fd
commit a179ec30a0
30 changed files with 1113 additions and 4 deletions
+15
View File
@@ -1837,6 +1837,14 @@ impl<'a> Emitter<'a> {
));
Ok(("0".into(), "i8".into()))
}
// loop-recur iter 1: real codegen (loop-header block,
// per-binder phi, back-edge br) lands in iter 3. This
// iter stubs the dispatch so the workspace compiles; no
// loop/recur program is codegen'd in iter 1. Mirrors
// mut.1's lower_term stub.
Term::Loop { .. } | Term::Recur { .. } => Err(CodegenError::Internal(
"Term::Loop/Term::Recur lowering lands in loop-recur iter 3".into(),
)),
}
}
@@ -3093,6 +3101,13 @@ impl<'a> Emitter<'a> {
// is exhaustive on Term so the arms must exist.
Term::Mut { body, .. } => self.synth_with_extras(body, extras),
Term::Assign { .. } => Ok(Type::unit()),
// loop-recur iter 1: a Term::Loop's static type is the
// body's type (mirror Term::Mut). Term::Recur does not
// fall through; a Unit stub is safe — this arm is never
// hit on the shipping path because lower_term stubs
// Loop/Recur, and iter 1 codegens no loop/recur program.
Term::Loop { body, .. } => self.synth_with_extras(body, extras),
Term::Recur { .. } => Ok(Type::unit()),
}
}
}