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 to
1ff7e81 content.

Surgically kept (not in 1ff7e81, 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 a 1ff7e81 worktree 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:
2026-05-16 01:28:47 +02:00
parent abf00131c1
commit 37ac704bf3
93 changed files with 307 additions and 5014 deletions
-121
View File
@@ -935,37 +935,6 @@ fn write_term_prec(out: &mut String, t: &Term, level: usize, parent_prec: u8, ow
out.push_str(" := ");
write_term(out, value, level, owning_module);
}
Term::Loop { binders, body } => {
// Iter it.1: prose-side minimal-correctness rendering for
// the `(loop ...)` head, mirroring the `mut` block shape
// above. The prose surface for loops is not yet fully
// designed; a follow-on prose iter refines it once the
// LLM-author signal arrives.
out.push_str("loop {\n");
for b in binders {
indent(out, level + 1);
out.push_str("var ");
out.push_str(&b.name);
out.push_str(" = ");
write_term(out, &b.init, level + 1, owning_module);
out.push_str(";\n");
}
indent(out, level + 1);
write_term(out, body, level + 1, owning_module);
out.push('\n');
indent(out, level);
out.push('}');
}
Term::Recur { args } => {
out.push_str("recur(");
for (i, a) in args.iter().enumerate() {
if i > 0 {
out.push_str(", ");
}
write_term(out, a, level, owning_module);
}
out.push(')');
}
}
}
@@ -1164,26 +1133,6 @@ fn count_free_var(name: &str, t: &Term) -> usize {
let n_use = if assign_name == name { 1 } else { 0 };
n_use + count_free_var(name, value)
}
// Iter it.1: a loop binder named `name` shadows the outer
// binding for both later binder inits and the body, exactly
// like `Term::Mut`.
Term::Loop { binders, body } => {
let mut total = 0usize;
let mut shadowed = false;
for b in binders {
if !shadowed {
total += count_free_var(name, &b.init);
}
if b.name == name {
shadowed = true;
}
}
if !shadowed {
total += count_free_var(name, body);
}
total
}
Term::Recur { args } => args.iter().map(|a| count_free_var(name, a)).sum(),
}
}
@@ -1336,39 +1285,6 @@ fn subst_var_with_term(t: &Term, name: &str, replacement: &Term) -> Term {
name: assign_name.clone(),
value: Box::new(subst_var_with_term(value, name, replacement)),
},
Term::Loop { binders, body } => {
let mut shadowed = false;
let new_binders: Vec<ailang_core::ast::LoopBinder> = binders
.iter()
.map(|b| {
let init = if shadowed {
(*b.init).clone()
} else {
subst_var_with_term(&b.init, name, replacement)
};
if b.name == name {
shadowed = true;
}
ailang_core::ast::LoopBinder {
name: b.name.clone(),
ty: b.ty.clone(),
init: Box::new(init),
}
})
.collect();
let body_rw = if shadowed {
(**body).clone()
} else {
subst_var_with_term(body, name, replacement)
};
Term::Loop {
binders: new_binders,
body: Box::new(body_rw),
}
}
Term::Recur { args } => Term::Recur {
args: args.iter().map(|a| subst_var_with_term(a, name, replacement)).collect(),
},
}
}
@@ -2185,43 +2101,6 @@ mod tests {
assert_eq!(render_term(&t), "tail not(x)");
}
/// Iter it.1: prose-projection lockstep for `Term::Loop` /
/// `Term::Recur`. The property protected: the prose renderer
/// projects a loop's binders + body and a recur's args without
/// dropping or reordering them (free-var / subst arms are
/// exercised by the existing prose suite). Prose is a one-way
/// projection (no Form-B parser exists — see CLAUDE.md), so an
/// AST-equality round-trip is not expressible; this render
/// assertion is the feasible lockstep, using the same
/// `render_term` harness the neighbouring render tests use.
#[test]
fn loop_and_recur_project_to_prose() {
let t = Term::Loop {
binders: vec![ailang_core::ast::LoopBinder {
name: "i".into(),
ty: ailang_core::ast::Type::Con {
name: "Int".into(),
args: vec![],
},
init: Box::new(Term::Lit {
lit: ailang_core::ast::Literal::Int { value: 0 },
}),
}],
body: Box::new(Term::Recur {
args: vec![ivar("i")],
}),
};
let rendered = render_term(&t);
assert!(
rendered.contains("loop {") && rendered.contains("var i = 0"),
"loop head not projected, got:\n{rendered}"
);
assert!(
rendered.contains("recur(i)"),
"recur not projected, got:\n{rendered}"
);
}
// ---- Polish 4: long doc-string wrap ----
#[test]