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. Specfda9b78, plan7381a42.
This commit is contained in:
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user