iter revert: back out the Iteration-discipline milestone (it.1 + it.2)
One forward iteration; main never rewound.1ff7e81(the pre-9973546 commit) is the per-region byte oracle — every reverted source/test file is byte-identical to it; crates/ailang-check/src/lib.rs fully so. Root cause being corrected: the Iteration-discipline milestone was an over-escalation of fieldtest finding F1 (a [friction] item whose own minimal recommendation was a DESIGN.md note). Its totality dichotomy made the maximally-LLM-natural build(d:Int)=Node(1,build(d-1), build(d-1)) inexpressible (it.3 BLOCKED); the only in-thesis escape (A1/it.2b) conceded the language's first documented-unenforced totality precondition — a purity-pillar dilution the user rejected in favour of a full revert + rebuild. Removed: Term::Loop/Term::Recur/LoopBinder; the verify_structural_ recursion guardedness pass + term_contains_loop + Diverge-injection + the transitively-it.2 module_fns plumbing; the five Recur*/ NonStructuralRecursion CheckError variants (+ code() + the 3 dedicated ctx() arms); the it.1 codegen loop-header/phi/back-edge + parallel block_terminated setter; all Loop/Recur walker arms; 16 it.1/it.2 fixtures; 2 pin files; bench/it3-oracle/. Restored: 2 RC fixtures to1ff7e81content. Surgically kept (not in1ff7e81, landed with the milestone but independently sound): feature-acceptance clause 3 in DESIGN.md and skills/brainstorm/SKILL.md, with its worked example de-claimed from "shipped" to hypothetical-illustration form; the F3 P2 todo. bench/orchestrator-stats/2026-05-15-iter-it.{1,2,3}.json kept as historical record (like journals/plans). Sole net addition: an honest F1/F4 documented-idiom note in DESIGN.md (the tail-recursive accumulator fallback; examples/mut_counter.ail), guarded by a doc-presence test — "a documentation note is not a reshape", asserts nothing at the typecheck level. Roadmap: the Iteration-discipline block + blocking-fork section removed; the genuine total-Int-recursion ambition preserved as a deferred P2 milestone sequenced behind a future Nat/refinement-types milestone (not abandoned — correctly sequenced after the type machinery it needs). 2026-05-15-iteration-discipline.md carries a superseded header; it.1/it.2/it.3 journals + plans stay as history. Correctness gate PRISTINE: 164 surviving 1ff7e81-era fixtures ail check/ail run byte-identical to pre-milestone behaviour (verified against a1ff7e81worktree reference compiler, zero drift); cargo test --workspace 600/0; zero residual it.1/it.2 production surface. Spec docs/specs/2026-05-16-iteration-discipline-revert.md (b3853bf), plan docs/plans/2026-05-16-iter-revert.md (abf0013).
This commit is contained in:
@@ -537,19 +537,6 @@ pub enum Term {
|
||||
name: String,
|
||||
value: Box<Term>,
|
||||
},
|
||||
/// Named loop head. The only repetition form besides structural
|
||||
/// recursion. Iter it.1. `recur` re-enters the lexically
|
||||
/// nearest enclosing `Loop`, rebinding `binders` positionally.
|
||||
Loop {
|
||||
binders: Vec<LoopBinder>,
|
||||
body: Box<Term>,
|
||||
},
|
||||
/// Backward jump to the lexically nearest enclosing `Loop`.
|
||||
/// Must be in tail position of that loop's body. Iter it.1.
|
||||
Recur {
|
||||
#[serde(default)]
|
||||
args: Vec<Term>,
|
||||
},
|
||||
}
|
||||
|
||||
/// One arm of a [`Term::Match`].
|
||||
@@ -593,17 +580,6 @@ pub struct MutVar {
|
||||
pub init: Term,
|
||||
}
|
||||
|
||||
/// One named, typed, initialised binder of a [`Term::Loop`].
|
||||
/// Mirrors [`MutVar`] (DD-2): no `PartialEq` — codegen maps the
|
||||
/// binder name to an SSA value, never compares binders.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct LoopBinder {
|
||||
pub name: String,
|
||||
#[serde(rename = "type")]
|
||||
pub ty: Type,
|
||||
pub init: Box<Term>,
|
||||
}
|
||||
|
||||
/// A match pattern.
|
||||
///
|
||||
/// The JSON discriminator is the `p` field. Patterns are linear: each
|
||||
@@ -970,41 +946,4 @@ mod tests {
|
||||
other => panic!("variant mismatch: {other:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Iter it.1: round-trip a `Term::Loop` through JSON. Pins the
|
||||
/// canonical-form shape and the `LoopBinder.ty` serde rename
|
||||
/// (`"type"`); a future rename or a `skip_serializing_if` on the
|
||||
/// binder fields would break the content-addressed identity this
|
||||
/// pin protects.
|
||||
#[test]
|
||||
fn term_loop_round_trips_through_json() {
|
||||
let t = Term::Loop {
|
||||
binders: vec![LoopBinder {
|
||||
name: "i".into(),
|
||||
ty: Type::int(),
|
||||
init: Box::new(Term::Lit {
|
||||
lit: Literal::Int { value: 0 },
|
||||
}),
|
||||
}],
|
||||
body: Box::new(Term::Recur {
|
||||
args: vec![Term::Var { name: "i".into() }],
|
||||
}),
|
||||
};
|
||||
let j = serde_json::to_string(&t).expect("serialise");
|
||||
assert!(j.contains(r#""t":"loop""#));
|
||||
assert!(j.contains(r#""type":"#)); // LoopBinder.ty serde rename
|
||||
let back: Term = serde_json::from_str(&j).expect("deserialise");
|
||||
assert_eq!(serde_json::to_string(&back).expect("re-serialise"), j);
|
||||
}
|
||||
|
||||
/// Iter it.1: a zero-arg `Term::Recur` round-trips. `args` carries
|
||||
/// `#[serde(default)]` so an absent `args` key still deserialises;
|
||||
/// the empty vector serialises explicitly.
|
||||
#[test]
|
||||
fn term_recur_empty_args_round_trips() {
|
||||
let t = Term::Recur { args: vec![] };
|
||||
let j = serde_json::to_string(&t).expect("serialise");
|
||||
let back: Term = serde_json::from_str(&j).expect("deserialise");
|
||||
assert_eq!(serde_json::to_string(&back).expect("re-serialise"), j);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -363,18 +363,6 @@ fn collect_used_in_term(t: &Term, used: &mut BTreeSet<String>) {
|
||||
used.insert(name.clone());
|
||||
collect_used_in_term(value, used);
|
||||
}
|
||||
Term::Loop { binders, body } => {
|
||||
for b in binders {
|
||||
used.insert(b.name.clone());
|
||||
collect_used_in_term(&b.init, used);
|
||||
}
|
||||
collect_used_in_term(body, used);
|
||||
}
|
||||
Term::Recur { args } => {
|
||||
for a in args {
|
||||
collect_used_in_term(a, used);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -602,32 +590,6 @@ impl Desugarer {
|
||||
name: name.clone(),
|
||||
value: Box::new(self.desugar_term(value, scope)),
|
||||
},
|
||||
Term::Loop { binders, body } => {
|
||||
// Iter it.1: loop binders introduce source-level names
|
||||
// like mut-vars; extend the scope per-binder with a
|
||||
// `LetBound` sentinel so a generated fresh name cannot
|
||||
// collide. Binder inits see earlier binders.
|
||||
let mut inner = scope.clone();
|
||||
let new_binders: Vec<LoopBinder> = binders
|
||||
.iter()
|
||||
.map(|b| {
|
||||
let init = self.desugar_term(&b.init, &inner);
|
||||
inner.insert(b.name.clone(), ScopeEntry::LetBound);
|
||||
LoopBinder {
|
||||
name: b.name.clone(),
|
||||
ty: b.ty.clone(),
|
||||
init: Box::new(init),
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
Term::Loop {
|
||||
binders: new_binders,
|
||||
body: Box::new(self.desugar_term(body, &inner)),
|
||||
}
|
||||
}
|
||||
Term::Recur { args } => Term::Recur {
|
||||
args: args.iter().map(|a| self.desugar_term(a, scope)).collect(),
|
||||
},
|
||||
Term::LetRec { name, ty, params, body, in_term } => {
|
||||
// Iter 16b.1: lift to a synthetic top-level fn (no-capture).
|
||||
// Iter 16b.2: extend the lift to the path-1 safe subset —
|
||||
@@ -1272,23 +1234,6 @@ pub fn free_vars_in_term(t: &Term, bound: &BTreeSet<String>, out: &mut BTreeSet<
|
||||
}
|
||||
free_vars_in_term(value, bound, out);
|
||||
}
|
||||
Term::Loop { binders, body } => {
|
||||
// Iter it.1: a loop binder name binds inside the body.
|
||||
// Each binder's `init` is evaluated in scope of the OUTER
|
||||
// environment plus the already-declared binders; the body
|
||||
// is in scope of all binders. Mirrors `Term::Mut`.
|
||||
let mut b = bound.clone();
|
||||
for binder in binders {
|
||||
free_vars_in_term(&binder.init, &b, out);
|
||||
b.insert(binder.name.clone());
|
||||
}
|
||||
free_vars_in_term(body, &b, out);
|
||||
}
|
||||
Term::Recur { args } => {
|
||||
for a in args {
|
||||
free_vars_in_term(a, bound, out);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1482,43 +1427,6 @@ pub fn subst_var(t: &Term, from: &str, to: &str) -> Term {
|
||||
name: name.clone(),
|
||||
value: Box::new(subst_var(value, from, to)),
|
||||
},
|
||||
Term::Loop { binders, body } => {
|
||||
// Iter it.1: loop binders lexically shadow outer names,
|
||||
// exactly like mut-vars (above). Once a binder named
|
||||
// `from` is declared, later inits AND the body stop
|
||||
// substituting.
|
||||
let mut shadowed = false;
|
||||
let new_binders: Vec<LoopBinder> = binders
|
||||
.iter()
|
||||
.map(|b| {
|
||||
let init = if shadowed {
|
||||
(*b.init).clone()
|
||||
} else {
|
||||
subst_var(&b.init, from, to)
|
||||
};
|
||||
if b.name == from {
|
||||
shadowed = true;
|
||||
}
|
||||
LoopBinder {
|
||||
name: b.name.clone(),
|
||||
ty: b.ty.clone(),
|
||||
init: Box::new(init),
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
let body_rw = if shadowed {
|
||||
(**body).clone()
|
||||
} else {
|
||||
subst_var(body, from, to)
|
||||
};
|
||||
Term::Loop {
|
||||
binders: new_binders,
|
||||
body: Box::new(body_rw),
|
||||
}
|
||||
}
|
||||
Term::Recur { args } => Term::Recur {
|
||||
args: args.iter().map(|a| subst_var(a, from, to)).collect(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1660,23 +1568,6 @@ pub fn subst_call_with_extras(t: &Term, name: &str, lifted: &str, extras: &[Stri
|
||||
name: assign_name.clone(),
|
||||
value: Box::new(subst_call_with_extras(value, name, lifted, extras)),
|
||||
},
|
||||
Term::Loop { binders, body } => Term::Loop {
|
||||
binders: binders
|
||||
.iter()
|
||||
.map(|b| LoopBinder {
|
||||
name: b.name.clone(),
|
||||
ty: b.ty.clone(),
|
||||
init: Box::new(subst_call_with_extras(&b.init, name, lifted, extras)),
|
||||
})
|
||||
.collect(),
|
||||
body: Box::new(subst_call_with_extras(body, name, lifted, extras)),
|
||||
},
|
||||
Term::Recur { args } => Term::Recur {
|
||||
args: args
|
||||
.iter()
|
||||
.map(|a| subst_call_with_extras(a, name, lifted, extras))
|
||||
.collect(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1749,14 +1640,6 @@ pub fn find_non_callee_use(t: &Term, name: &str) -> Option<Term> {
|
||||
find_non_callee_use(value, name)
|
||||
}
|
||||
}
|
||||
// Iter it.1: scan each binder's init plus the body. Loop
|
||||
// binders never appear in callee position, so any matching
|
||||
// `Term::Var` inside a loop is non-callee by construction.
|
||||
Term::Loop { binders, body } => binders
|
||||
.iter()
|
||||
.find_map(|b| find_non_callee_use(&b.init, name))
|
||||
.or_else(|| find_non_callee_use(body, name)),
|
||||
Term::Recur { args } => args.iter().find_map(|a| find_non_callee_use(a, name)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1804,10 +1687,6 @@ mod tests {
|
||||
vars.iter().any(|v| any_nested_ctor(&v.init)) || any_nested_ctor(body)
|
||||
}
|
||||
Term::Assign { value, .. } => any_nested_ctor(value),
|
||||
Term::Loop { binders, body } => {
|
||||
binders.iter().any(|b| any_nested_ctor(&b.init)) || any_nested_ctor(body)
|
||||
}
|
||||
Term::Recur { args } => args.iter().any(any_nested_ctor),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1838,10 +1717,6 @@ mod tests {
|
||||
vars.iter().any(|v| any_let_rec(&v.init)) || any_let_rec(body)
|
||||
}
|
||||
Term::Assign { value, .. } => any_let_rec(value),
|
||||
Term::Loop { binders, body } => {
|
||||
binders.iter().any(|b| any_let_rec(&b.init)) || any_let_rec(body)
|
||||
}
|
||||
Term::Recur { args } => args.iter().any(any_let_rec),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2963,10 +2838,6 @@ mod tests {
|
||||
vars.iter().any(|v| any_lit_pattern(&v.init)) || any_lit_pattern(body)
|
||||
}
|
||||
Term::Assign { value, .. } => any_lit_pattern(value),
|
||||
Term::Loop { binders, body } => {
|
||||
binders.iter().any(|b| any_lit_pattern(&b.init)) || any_lit_pattern(body)
|
||||
}
|
||||
Term::Recur { args } => args.iter().any(any_lit_pattern),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1259,22 +1259,6 @@ where
|
||||
walk_term_embedded_types(body, f)
|
||||
}
|
||||
Term::Assign { value, .. } => walk_term_embedded_types(value, f),
|
||||
// Iter it.1: each `LoopBinder.ty` is an embedded type — walk
|
||||
// it, then recurse into each binder's `init` and the body.
|
||||
// `Term::Recur` carries no embedded type.
|
||||
Term::Loop { binders, body } => {
|
||||
for b in binders {
|
||||
walk_type(&b.ty, f)?;
|
||||
walk_term_embedded_types(&b.init, f)?;
|
||||
}
|
||||
walk_term_embedded_types(body, f)
|
||||
}
|
||||
Term::Recur { args } => {
|
||||
for a in args {
|
||||
walk_term_embedded_types(a, f)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1408,18 +1392,6 @@ where
|
||||
walk_term(body, f)
|
||||
}
|
||||
Term::Assign { value, .. } => walk_term(value, f),
|
||||
Term::Loop { binders, body } => {
|
||||
for b in binders {
|
||||
walk_term(&b.init, f)?;
|
||||
}
|
||||
walk_term(body, f)
|
||||
}
|
||||
Term::Recur { args } => {
|
||||
for a in args {
|
||||
walk_term(a, f)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user