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:
+15
-49
@@ -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).
|
||||
|
||||
Reference in New Issue
Block a user