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
+15 -49
View File
@@ -102,12 +102,17 @@ A proposed feature ships only if all three hold:
the bug class is structurally impossible rather than merely
discouraged. A documentation note is not a reshape; the
discriminator is whether the wrong code fails to typecheck, not
whether a guideline advises against it. The canonical worked
example is the iteration story: a bare `while` over mutable state
passes 1 and 2 yet fails 3 (it reinstates iterated-mutable-state
reasoning); structural recursion over an acyclic ADT and a named
loop/recur point pass all three (the bug class is closed by
construction, not by advice).
whether a guideline advises against it. Worked example: a bare
`while` over mutable state would pass clauses 1 and 2 yet fail
clause 3 (it reinstates iterated-mutable-state reasoning the pure
core exists to remove); a hypothetical "all repetition is either
structurally-decreasing recursion over an acyclic ADT or an
explicit named loop" iteration story would, *if it could be built
without a documented-unenforced precondition*, pass all three —
and the fact that the 2026-05 attempt could not (it forced a
silent-divergence precondition; see
`docs/specs/2026-05-16-iteration-discipline-revert.md`) is itself
the clause-3 mechanism working as intended.
This is the positive complement to the CLAUDE.md rule that
implementation effort is not a rationale: cost is not a reason *for* a
@@ -164,16 +169,8 @@ Advantages:
The default is total, pure functions. Effects are declared as a set in the
function type: `(Int) -> Int ![IO]`. The effect set is row-polymorphic
(`![IO | r]`). Two effects are wired up: `IO` (observable side effects,
raised by `do`-operations), and `Diverge` (non-termination). As of iter
it.2 (2026-05-15) `Diverge` is no longer nominal: it is the effect carried
by any function whose body contains a `loop` (or that calls a
`Diverge`-declaring function), surfaced through the existing
declared-vs-raised reconciliation (an undeclared `Diverge` is the existing
`UndeclaredEffect`, no new diagnostic). Structural recursion is pure and
total and carries no effect — the author who wants a `!Diverge`-free
signature is structurally pulled toward structural recursion and pays
`!Diverge` only when genuinely writing an unbounded `loop`.
(`![IO | r]`). In the MVP only the effects `IO` and `Diverge` (for infinite
loops) are wired up.
This is the most important LLM property: when I read a function, I can trust
its signature without reading the body.
@@ -2418,22 +2415,6 @@ are real surface forms.
{ "t": "assign",
"name": "<id>",
"value": Term }
// Iter it.1: named loop head. `binders` declares one or more
// lexically-scoped, typed, initialised binders (initialised in
// order); `body` is a single Term in scope of all binders. `recur`
// re-enters the lexically nearest enclosing loop, rebinding the
// binders positionally; it must be in tail position of that loop's
// body. The loop's static type is `body`'s static type.
{ "t": "loop",
"binders": [ { "name": "<id>", "type": Type, "init": Term }, ... ],
"body": Term }
// Iter it.1: backward jump to the lexically nearest enclosing loop;
// tail-position-only; binds that loop's binders. The `args` array is
// always present in canonical JSON (even when empty).
{ "t": "recur",
"args": [ Term, ... ] }
```
In the MVP, `do` is only a direct call to a built-in effect op (no
@@ -2449,23 +2430,6 @@ mut-var into a lambda body is rejected at typecheck via
`CheckError::MutVarCapturedByLambda` (iter mut.4-tidy). See
`docs/specs/2026-05-15-mut-local.md`.
`loop`/`recur` are additive as of iter it.1 (2026-05-15): they
parse, print, prose-project, round-trip, typecheck (binder typing,
recur arity/type unification, recur tail-position) and codegen
(loop-header block with one phi per binder, `recur` as a back-edge
`br`) without removing or modifying the existing `tail-app`/`tail-do`
paths. As of iter it.2 (2026-05-15) the structural-recursion
guardedness restriction and the `Diverge` effect are in effect:
a non-structural recursion-by-call is the compile error
`NonStructuralRecursion` (directing the author to `(loop …)` /
`recur`), and any fn whose body contains a `Term::Loop` (or calls a
`Diverge`-declaring callee) must declare `!Diverge` in its effect
row — structural recursion stays pure, total, and effect-free. A
transitional grandfather exempts still-`tail: true`-marked recursive
calls so the corpus type-checks unchanged through it.2.
`tail-app`/`tail-do` are retired in it.3. See
`docs/specs/2026-05-15-iteration-discipline.md`.
**`Literal`**:
```jsonc
@@ -2917,6 +2881,8 @@ What **is** supported (and used as the smoke test for the pipeline):
effect), and introduce composable transducers (`Stateful a b` +
`pipe`). Spec: `docs/specs/2026-05-15-mut-local.md`.
**Accumulator-over-iteration shape (documented idiom, not an enforced rule).** `mut`/`var`/`assign` removes friction from *straight-line* and *cascade-of-if* accumulators, but does **not** by itself express the "one running total updated once per loop iteration" shape: a `var` cannot be captured into a lambda (the seal) and there is no loop construct in the language. Until a future iteration-totality milestone lands (gated behind `Nat`/refinement types — see the deferred roadmap entry), the canonical pattern for the per-iteration accumulator is a tail-recursive helper that threads the running total as an explicit parameter; `examples/mut_counter.ail` is the reference. This is a documented authoring idiom, not a typecheck-enforced constraint — wrong code here does not fail to compile, it is merely less ergonomic, which is exactly why this is documentation and not a language rule.
Pipeline regression smoke tests:
- `examples/sum.ail.json` → prints 55 (recursion, arithmetic).