iter it.1: loop/recur additive — Term::Loop/Term::Recur/LoopBinder end-to-end

Iteration-discipline milestone, 1 of 3. Adds named loop + recur as
strictly-additive first-class AST nodes: parse/print/prose/serde/
round-trip/schema lockstep, typecheck (binder typing + recur
arity/type unification via loop_stack threaded as mut.2's
mut_scope_stack; recur-tail-position via verify_loop_body), codegen
(loop-header + one phi per binder; recur back-edge br with a NEW
parallel block_terminated setter; lambda-boundary loop_frames
save/restore). Four Recur* CheckError variants. Strictly additive:
zero deletions touch tail-app/tail-do or the seven existing
block_terminated sites — this is what makes the destructive it.3
safe. recur synth = fresh metavar (resolves the plan's flagged
Type::unit() risk). loop_counter->55, loop_in_lambda->49, four
negatives fire, tail-app byte-identical, cargo test --workspace
green. Spec fda9b78, plan 7381a42.
This commit is contained in:
2026-05-15 14:38:55 +02:00
parent 7381a4233b
commit 96db54d15d
38 changed files with 1891 additions and 32 deletions
+61
View File
@@ -1544,6 +1544,26 @@ fn walk_term(
}
walk_term(value, out, builtins, scope);
}
// Iter it.1: loop binder names bind inside the body and later
// binder inits, mirroring `Term::Mut`. Recur args are uses.
Term::Loop { binders, body } => {
let mut newly = Vec::new();
for b in binders {
walk_term(&b.init, out, builtins, scope);
if scope.insert(b.name.clone()) {
newly.push(b.name.clone());
}
}
walk_term(body, out, builtins, scope);
for n in newly {
scope.remove(&n);
}
}
Term::Recur { args } => {
for a in args {
walk_term(a, out, builtins, scope);
}
}
}
}
@@ -2785,6 +2805,47 @@ fn rewrite_def(
changed,
);
}
// Iter it.1: rewrite types embedded in each
// `LoopBinder.ty` (loop binders carry full Type
// annotations like mut-vars) and recurse into each
// binder's `init` and the body. `Term::Recur` has no
// embedded type.
Term::Loop { binders, body } => {
for b in binders {
rewrite_type(
&mut b.ty,
owning_module,
local_types,
import_names,
changed,
);
rewrite_term(
&mut b.init,
owning_module,
local_types,
import_names,
changed,
);
}
rewrite_term(
body,
owning_module,
local_types,
import_names,
changed,
);
}
Term::Recur { args } => {
for a in args {
rewrite_term(
a,
owning_module,
local_types,
import_names,
changed,
);
}
}
}
}
@@ -101,6 +101,13 @@ fn synthesised_print_uses_user_module_show_via_fallback() {
|| contains_xmod_show_var(body)
}
Term::Assign { value, .. } => contains_xmod_show_var(value),
// Iter it.1: a `Term::Loop` cannot itself host a synth'd
// cross-module reference, but recurse defensively.
Term::Loop { binders, body } => {
binders.iter().any(|b| contains_xmod_show_var(&b.init))
|| contains_xmod_show_var(body)
}
Term::Recur { args } => args.iter().any(contains_xmod_show_var),
Term::Lit { .. } => false,
}
}
+25
View File
@@ -2828,6 +2828,31 @@ fn mut_counter_prints_55() {
assert_eq!(stdout.trim(), "55", "mut_counter must print 55, got {stdout:?}");
}
/// Iter it.1: `examples/loop_counter.ail` exercises `Term::Loop` +
/// `Term::Recur` codegen — the loop-header block with one phi per
/// binder and `recur` as a back-edge `br`. The body
/// `(loop ((var acc Int 0) (var i Int 1)) (if (> i 10) acc (recur ...)))`
/// sums 1..10 and prints 55. End-to-end gate for the loop-header /
/// phi / recur-back-edge lowering.
#[test]
fn loop_counter_runs_and_prints_55() {
let stdout = build_and_run("loop_counter.ail");
assert_eq!(stdout.trim(), "55", "loop_counter must print 55, got {stdout:?}");
}
/// Iter it.1: a `Term::Loop` inside a `Term::Lam` body, invoked via
/// a returned closure. Protects the lambda-boundary invariant: the
/// closure thunk scopes its own loop header / phis (the `loop_frames`
/// save+reset+restore across the lambda boundary, mut.3 analogue) —
/// a `recur` inside the lambda must back-edge to the lambda's own
/// loop header, not the outer fn's. The lambda computes x*x by
/// summing x exactly x times; apply 7 prints 49.
#[test]
fn loop_in_lambda_runs_and_prints_49() {
let stdout = build_and_run("loop_in_lambda_e2e.ail");
assert_eq!(stdout.trim(), "49", "loop_in_lambda must print 49, got {stdout:?}");
}
/// Iter mut.3: Float twin of `mut_counter_prints_55`. The mut-var
/// is `Float`, init is `0.0`, the recursive helper returns the
/// sum 1.0+...+10.0 = 55.0. The polymorphic `print` routes through