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
@@ -37,6 +37,11 @@ const EXPECTED: &[&str] = &[
"test_mut_var_unsupported_type.ail.json",
// Iter mut.4-tidy — lambda-capture-of-mut-var rejection
"test_mut_var_captured_by_lambda.ail.json",
// Iter it.1 — recur negative typecheck fixtures
"test_recur_outside_loop.ail.json",
"test_recur_arity_mismatch.ail.json",
"test_recur_type_mismatch.ail.json",
"test_recur_not_in_tail_position.ail.json",
];
fn examples_dir() -> std::path::PathBuf {
@@ -151,6 +151,17 @@ fn design_md_anchors_every_term_variant() {
value: Box::new(Term::Lit { lit: Literal::Unit }),
},
),
(
r#""t": "loop""#,
Term::Loop {
binders: Vec::new(),
body: Box::new(Term::Lit { lit: Literal::Unit }),
},
),
(
r#""t": "recur""#,
Term::Recur { args: Vec::new() },
),
];
for (anchor, term) in exemplars {
@@ -172,6 +183,8 @@ fn design_md_anchors_every_term_variant() {
Term::ReuseAs { .. } => "reuse-as",
Term::Mut { .. } => "mut",
Term::Assign { .. } => "assign",
Term::Loop { .. } => "loop",
Term::Recur { .. } => "recur",
};
assert!(
data_model_section().contains(anchor),
@@ -49,6 +49,8 @@ enum VariantTag {
TermReuseAs,
TermMut,
TermAssign,
TermLoop,
TermRecur,
// Pattern
PatternWild,
PatternVar,
@@ -96,6 +98,8 @@ const EXPECTED_VARIANTS: &[VariantTag] = &[
VariantTag::TermReuseAs,
VariantTag::TermMut,
VariantTag::TermAssign,
VariantTag::TermLoop,
VariantTag::TermRecur,
VariantTag::PatternWild,
VariantTag::PatternVar,
VariantTag::PatternLit,
@@ -244,6 +248,20 @@ fn visit_term(t: &Term, observed: &mut HashSet<VariantTag>) {
observed.insert(VariantTag::TermAssign);
visit_term(value, observed);
}
Term::Loop { binders, body } => {
observed.insert(VariantTag::TermLoop);
for b in binders {
visit_type(&b.ty, observed);
visit_term(&b.init, observed);
}
visit_term(body, observed);
}
Term::Recur { args } => {
observed.insert(VariantTag::TermRecur);
for a in args {
visit_term(a, observed);
}
}
}
}
+13
View File
@@ -128,6 +128,17 @@ fn spec_mentions_every_term_variant() {
value: Box::new(Term::Lit { lit: Literal::Unit }),
},
),
(
"(loop",
Term::Loop {
binders: Vec::new(),
body: Box::new(Term::Lit { lit: Literal::Unit }),
},
),
(
"(recur",
Term::Recur { args: Vec::new() },
),
];
for (anchor, term) in exemplars {
@@ -151,6 +162,8 @@ fn spec_mentions_every_term_variant() {
Term::ReuseAs { .. } => "reuse-as",
Term::Mut { .. } => "mut",
Term::Assign { .. } => "assign",
Term::Loop { .. } => "loop",
Term::Recur { .. } => "recur",
};
assert!(
FORM_A_SPEC.contains(anchor),