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:
+17
-137
@@ -40,143 +40,6 @@ clean. Pick the next milestone from P2.)_
|
||||
|
||||
## P1 — Next
|
||||
|
||||
- [~] **\[milestone\]** Iteration discipline — structural recursion +
|
||||
named `loop`/`recur`; recursion-by-call restricted; `tail-app`
|
||||
retired. Spec `docs/specs/2026-05-15-iteration-discipline.md`
|
||||
(approved fda9b78; scope-corrected 2018178/10a0595). **Status
|
||||
2026-05-15: it.1 SHIPPED (96db54d — loop/recur additive), it.2
|
||||
SHIPPED (a4be1e5 — guardedness checker + first real Diverge
|
||||
effect), it.3 BLOCKED on a surfaced fundamental design fork (see
|
||||
"The blocking fork" below — needs a user design decision + a new
|
||||
iteration it.2b before it.3 can re-dispatch).** The iteration
|
||||
story of the language, decided in principle 2026-05-15. Two
|
||||
constructs survive, one is removed:
|
||||
- **Structural recursion stays — and becomes total-by-construction.**
|
||||
A recursive call is permitted iff it is structurally guarded:
|
||||
each recursive call is on a constructor-smaller component of an
|
||||
algebraic argument. Decision 10 already guarantees ADTs are
|
||||
acyclic, so a structurally-guarded recursion terminates *by the
|
||||
invariant the language already holds* — permitting it costs
|
||||
nothing on the provability pillar and it stays locally checkable.
|
||||
This is the most LLM-natural correct pattern (tree / AST / JSON /
|
||||
list `match`-and-recurse, saturated in every FP corpus) and must
|
||||
not be amputated.
|
||||
- **All other repetition must go through a named `(loop ...)` head +
|
||||
`recur`.** `recur` is the single explicit backward jump; it
|
||||
targets a lexically-enclosing named loop, never an implicit fn
|
||||
head (Clojure permits the implicit form — AILang does not, for
|
||||
the same no-outward-inference reason that motivated explicit
|
||||
`tail-app`). Accumulator loops, counted iteration, streaming,
|
||||
state machines — none are structurally decreasing, so all are
|
||||
forced into the explicit named-loop form.
|
||||
- **`tail-app` is retired.** `Term::App { tail: bool }` and the
|
||||
`musttail`-lowering surface go away: a call is either structural
|
||||
recursion (a plain call, checked total) or it is `loop`/`recur`
|
||||
(the only loop). No marked-tail-call concept survives.
|
||||
|
||||
**Motivation.** Discharges fieldtest finding F1 — the most
|
||||
consequential mut-local finding: mut-local's own motivation
|
||||
("removes friction from accumulators that today need tail-recursive
|
||||
helpers") was structurally unmet, because without a loop the only
|
||||
iteration mechanism remained the very tail-rec helper. `loop`/`recur`
|
||||
closes that honestly without introducing an imperative construct
|
||||
(it is pure: no mutable loop variable, no effect, composes into pure
|
||||
contexts freely). It also closes the residual "forgot to mark tail"
|
||||
trap *completely* — a recursive call that is not structurally
|
||||
guarded is a compile error that names itself, not a silent
|
||||
stack-overflow. And it unifies with Decision 10: acyclic values
|
||||
*and* an acyclic implicit call graph; the only cycles anywhere in a
|
||||
program are explicit, named, typed `loop` points. Both surviving
|
||||
constructs pass all three feature-acceptance clauses including the
|
||||
2026-05-15 clause 3 (structural recursion and named loop/recur are
|
||||
LLM-natural *and* their bug class is closed by construction; a bare
|
||||
`while` is exactly what clause 3 rejects).
|
||||
|
||||
**Original open spec questions — RESOLVED in it.2 (a4be1e5).**
|
||||
(1) *mutual* structural recursion → the conservative same-ADT-family
|
||||
rule (connected-components union-find over the type-reference
|
||||
graph; admits tree/forest, even/odd-over-Nat, mutual JSON; a
|
||||
general size-measure ordering stays out of scope). (2) the
|
||||
`foldl`-shaped accumulator-carrying structural walk → **counts as
|
||||
structural recursion** (plain, pure, total — only the structural
|
||||
argument must decrease; accumulator positions are unconstrained).
|
||||
Both shipped and pinned.
|
||||
|
||||
**The blocking fork (it.3-surfaced 2026-05-15 — needs a USER
|
||||
design decision; touches the purity pillar the user co-designed).**
|
||||
The milestone's totality thesis recognised exactly ONE
|
||||
total-by-construction recursion scheme: structural-over-an-acyclic-
|
||||
ADT (Decision 10). The it.3 mandated corpus sweep proved this is
|
||||
*incomplete*. Six corpus fixtures contain the single most
|
||||
LLM-natural tree-builder shape:
|
||||
`build(d: Int) = if d==0 then Leaf else Node(1, build(d-1), build(d-1))`.
|
||||
It is provably terminating, but: **not** ADT-structural (`d` is
|
||||
`Int`, no constructor-smaller component) and **not** `loop`/`recur`-
|
||||
able (it is *branching/double* recursion; `recur` is a single
|
||||
tail-position back-edge — a balanced-tree build is not a tail
|
||||
loop; the only `loop` form is a hand-rolled explicit-stack
|
||||
worklist, which no LLM writes). So the milestone *as-specified*
|
||||
makes a trivially-total, maximally-natural function
|
||||
**inexpressible** — failing its own feature-acceptance criterion
|
||||
(clause 1: the LLM reaches for `build(d-1),build(d-1)`; clause 2:
|
||||
the milestone would *remove* expressivity). This is a real
|
||||
design-thesis gap, not a migration nuisance: there is a SECOND
|
||||
canonical total-by-construction scheme the design overlooked —
|
||||
**well-founded recursion on a strictly-decreasing non-negative
|
||||
integer measure** (`f(0)=base; f(n)=…f(n-1)…`, branching allowed).
|
||||
|
||||
**Decision the user must make** (it trades against the purity
|
||||
pillar — Int ≠ ℕ; AILang has no `Nat`/refinement types, so
|
||||
Int-bounded recursion is total only for *non-negative entry*):
|
||||
- **A1 (RECOMMENDED).** Make the totality story a *trichotomy*:
|
||||
ADT-structural | Int-bounded-well-founded | explicit `loop`/`recur`.
|
||||
Accept Int-bounded recursion (recursive call on `p - <pos-lit>`
|
||||
with a non-recursing base guard, branching allowed) as a
|
||||
*second total-by-construction class*, with one documented
|
||||
obligation: non-negative entry (a negative seed diverges — the
|
||||
same precondition class as array-bounds the language already
|
||||
lives with). Restores the entire `f(n-1)` recursion family (the
|
||||
second-most-common shape in any corpus); keeps all 6 fixtures
|
||||
verbatim; the trichotomy is *more* complete, not less principled.
|
||||
Rationale is the language's own gate: a milestone that makes
|
||||
`build(d)` inexpressible cannot pass the feature-acceptance
|
||||
criterion. *Why this is a user call:* it concedes "total by
|
||||
construction, sign nothing" → "total by construction modulo one
|
||||
documented non-negative-entry obligation" — a dilution of a
|
||||
purity pillar the user personally weighted in the original
|
||||
Socratic design dialogue.
|
||||
- **A2.** Introduce a `Nat` type; accept Int-bounded recursion
|
||||
only on `Nat` — preserves zero-precondition purity, but `Nat`
|
||||
is its own sub-milestone and an LLM writes `depth: Int` unless
|
||||
the prelude makes `Nat` the natural default (clause-1 friction).
|
||||
- **A3 / B / C (not recommended).** Strict-dichotomy +
|
||||
explicit-worklist (fails clause 1 — no LLM hand-rolls a stack
|
||||
for tree-build); a narrow carve-out predicate (the patch smell);
|
||||
reshaping the LLM-natural fixtures (forbidden —
|
||||
`feedback_dont_adapt_tests_to_bugs`).
|
||||
|
||||
**If A1 (or A2): new iteration it.2b** — additive widening of the
|
||||
it.2 guardedness checker to also accept Int-bounded (incl.
|
||||
branching) recursion as total; no removal. After it.2b the 6
|
||||
fixtures check clean with zero migration; it.3 then re-dispatches
|
||||
from c992eb9 (the `bench/it3-oracle/` 40-fixture behavioural
|
||||
oracle is preserved and reused; nothing in it.3 was destructive).
|
||||
The it.3 RC-RSS/18g.1 load-bearing risk (Task 5.5/5b) is still
|
||||
UNTESTED — must not be treated as resolved by the milestone-close
|
||||
audit.
|
||||
|
||||
**Relation to Stateful islands.** This entry owns the iteration
|
||||
story and *supersedes* the "while-loops legal" scope bullet of the
|
||||
Stateful-islands milestone below. The genuinely-escaping
|
||||
mutable-state path (`ref a` / `MutArray a` / `Stateful a b` /
|
||||
`pipe`) is unaffected and stays in Stateful-islands.
|
||||
|
||||
- context: spec `docs/specs/2026-05-15-iteration-discipline.md`;
|
||||
per-iter journals 2026-05-15-iter-it.1 (shipped), -it.2
|
||||
(shipped), -it.3 (BLOCKED — the design fork above). Next action
|
||||
is a USER decision on the blocking fork (A1 recommended), then
|
||||
iteration it.2b, then it.3 re-dispatch from c992eb9.
|
||||
|
||||
- [x] **\[milestone\]** Heap-`Str` ABI — runtime infrastructure for
|
||||
malloc-backed, refcounted `Str` values alongside the existing
|
||||
static `@.str_*` globals. Today the `Str` path is static-only
|
||||
@@ -213,6 +76,23 @@ clean. Pick the next milestone from P2.)_
|
||||
|
||||
## P2 — Medium-term
|
||||
|
||||
- [ ] **\[milestone\]** Iteration-totality story — structural +
|
||||
Int-bounded total recursion with *enforced* non-negativity.
|
||||
AILang's iteration story stays as-is (structural / tail recursion;
|
||||
`tail-app` intact). The genuine ambition — make `f(n-1)`-family
|
||||
recursion (incl. branching tree builders) total *by construction*
|
||||
with the non-negative-entry precondition **enforced**, not merely
|
||||
documented — is deferred here because doing it without a purity-
|
||||
pillar concession requires refinement/`Nat` type machinery the
|
||||
language has not built (Decision 4 keeps refinements opaque, no
|
||||
SMT). Not abandoned; correctly sequenced after the type machinery.
|
||||
- depends on: a future `Nat`/refinement-types milestone (no spec
|
||||
yet).
|
||||
- context: `docs/specs/2026-05-16-iteration-discipline-revert.md`
|
||||
(why the 2026-05 attempt was reverted) and
|
||||
`docs/journals/2026-05-15-iter-it.3.md` (the branching-builder
|
||||
counter-example that surfaced the gap).
|
||||
|
||||
- [x] **\[milestone\]** Retire `io/print_int` / `io/print_bool` /
|
||||
`io/print_float` effect-ops + migrate example corpus to `print`.
|
||||
Shipped 2026-05-14 as iter rpe.1.
|
||||
|
||||
Reference in New Issue
Block a user