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
-61
View File
@@ -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);
}
}