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
-12
View File
@@ -1,12 +0,0 @@
(module loop_counter
(fn main
(doc "Iter it.1 — sum 1..10 via an accumulator loop. Iter it.2: loop-bearing ⇒ !Diverge added (D2). Expected stdout: 55.")
(type (fn-type (params) (ret (con Unit)) (effects IO Diverge)))
(params)
(body
(app print
(loop ((var acc (con Int) 0) (var i (con Int) 1))
(if (app > i 10)
acc
(recur (app + acc i) (app + i 1))))))))
-30
View File
@@ -1,30 +0,0 @@
(module loop_in_lambda_e2e
(fn apply
(doc "apply a fn-of-Int to an Int. Iter it.2: the supplied closure is loop-bearing, so its arrow carries !Diverge — the higher-order param type and apply's own effect row must reflect that (effects are part of the function type).")
(type (fn-type (params (fn-type (params (con Int)) (ret (con Int)) (effects Diverge)) (con Int)) (ret (con Int)) (effects Diverge)))
(params f x)
(body (app f x)))
(fn main
(doc "Iter it.1 — a loop inside a lambda body, invoked via a closure. The lambda computes x*x by summing x exactly x times through an accumulator loop; apply 7 prints 49. Codegen-soundness gate for the lambda-boundary loop_frames save/restore. Iter it.2: !Diverge propagates out through apply (callee-effect propagation).")
(type (fn-type (params) (ret (con Unit)) (effects IO Diverge)))
(params)
(body
(app print
(app apply
; Iter it.2: the loop lives in THIS lambda's body, so the
; lambda's arrow carries !Diverge (DD-4 lam boundary). The
; loop does not leak Diverge to `main` — `main`'s body does
; not syntactically contain the loop (it stops at the lam
; edge, exactly as !IO does).
(lam
(params (typed x (con Int)))
(ret (con Int))
(effects Diverge)
(body
(loop ((var acc (con Int) 0) (var k (con Int) 0))
(if (app == k x)
acc
(recur (app + acc x) (app + k 1))))))
7)))))
-18
View File
@@ -1,18 +0,0 @@
; Iter it.2 positive (DD-4 / D2): a fn whose body contains a
; `(loop …)` must declare `!Diverge`. This one does — its effect row
; carries `Diverge` (and `IO`, since it `print`s the result), so the
; existing declared-vs-raised reconciliation accepts it. Modelled on
; `loop_counter.ail` (it.1) with `Diverge` added to the effect row.
(module loop_needs_diverge
(fn main
(doc "Sum 1..10 via an accumulator loop; loop-bearing ⇒ !Diverge. Expected stdout: 55.")
(type (fn-type (params) (ret (con Unit)) (effects IO Diverge)))
(params)
(body
(app print
(loop ((var acc (con Int) 0) (var i (con Int) 1))
(if (app > i 10)
acc
(recur (app + acc i) (app + i 1))))))))
-16
View File
@@ -1,16 +0,0 @@
(module loop_nested_in_lambda
(fn make_adder
(doc "Iter it.1 — a loop inside a lambda body (lambda-boundary analogue). Iter it.2: the returned closure is loop-bearing, so its arrow carries !Diverge (DD-4 lam boundary); make_adder's return type reflects that. make_adder's own body does not contain the loop (it stops at the lam edge), so make_adder itself stays effect-free.")
(type (fn-type (params (con Int)) (ret (fn-type (params (con Int)) (ret (con Int)) (effects Diverge)))))
(params base)
(body
(lam
(params (typed x (con Int)))
(ret (con Int))
(effects Diverge)
(body
(loop ((var acc (con Int) base) (var k (con Int) 0))
(if (app == k x)
acc
(recur (app + acc 1) (app + k 1)))))))))
-11
View File
@@ -1,11 +0,0 @@
(module loop_smoke
(fn count_to
(doc "Iter it.1 — single counted loop; counts i up to n and returns n. Iter it.2: loop-bearing ⇒ !Diverge (D2).")
(type (fn-type (params (con Int)) (ret (con Int)) (effects Diverge)))
(params n)
(body
(loop ((var i (con Int) 0))
(if (app == i n)
i
(recur (app + i 1)))))))
+1 -9
View File
@@ -59,14 +59,6 @@
(case (pat-ctor TLeaf) 0)
(case (pat-ctor TNode v l r) 1)))))
; Iter it.2: integer-counter recursion holding the ADT param `t`
; constant — non-structural by the it.2 guardedness check (D2). The
; recursive call is in tail position, so it joins the transitional
; `tail-app` grandfather (spec it.2 "Transitional grandfather") as
; the rest of the corpus does until it.3 migrates such recursions
; to `(loop …)`. The 18g let-alias-mode regression this fixture
; guards lives in `pin_aliased`'s match arm-close, unaffected by
; this tail marking.
(fn loop
(type
(fn-type
@@ -77,7 +69,7 @@
(if (app == n 0)
0
(let _v (app pin_aliased t)
(tail-app loop (app - n 1) t)))))
(app loop (app - n 1) t)))))
(fn main
(type (fn-type (params) (ret (con Unit)) (effects IO)))
+1 -9
View File
@@ -38,14 +38,6 @@
(case (pat-ctor TLeaf) 0)
(case (pat-ctor TNode v l r) 1))))
; Iter it.2: this is integer-counter recursion holding the ADT
; param `t` constant — non-structural by the it.2 guardedness
; check (D2). The recursive call is in tail position, so it joins
; the transitional `tail-app` grandfather (spec it.2 "Transitional
; grandfather") exactly as the rest of the corpus does until it.3
; migrates such recursions to `(loop …)`. The 18d.4 regression this
; fixture guards lives in `pin`'s match arm-close, unaffected by
; this tail marking.
(fn loop
(type (fn-type (params (con Int) (con Tree)) (ret (con Int))))
(params n t)
@@ -53,7 +45,7 @@
(if (app == n 0)
0
(let _v (app pin t)
(tail-app loop (app - n 1) t)))))
(app loop (app - n 1) t)))))
(fn main
(type (fn-type (params) (ret (con Unit)) (effects IO)))
-26
View File
@@ -1,26 +0,0 @@
; Iter it.2 positive (spec D1): foldl-shape accumulator. `go` recurses
; on the Cons-tail `t` at position 0 (structurally guarded) while
; threading an unconstrained accumulator `acc` at position 1. The
; accumulator position is never examined by the guardedness check, so
; this classifies as structural recursion: pure, total, Diverge-free,
; with NO `tail-app` marker. This is the single most LLM-natural
; iteration shape and must stay structural (spec D1).
(module struct_rec_foldl_sum
(data IntList
(ctor INil)
(ctor ICons (con Int) (con IntList)))
(fn go
(doc "Sum via a foldl-shape accumulator; structural on the tail.")
(type
(fn-type
(params (con IntList) (con Int))
(ret (con Int))))
(params xs acc)
(body
(match xs
(case (pat-ctor INil) acc)
(case (pat-ctor ICons h t)
(app go t (app + acc h)))))))
-24
View File
@@ -1,24 +0,0 @@
; Iter it.2 positive: structural list length. `len` recurses on the
; Cons-tail `t` (a constructor sub-component of `xs`) via a plain,
; non-tail `(app len t)`. Structurally guarded at position 0, pure,
; total, Diverge-free. No `tail-app` marker — this is the structural
; recursion form the it.2 guardedness check must accept on its own.
(module struct_rec_list_len
(data IntList
(ctor INil)
(ctor ICons (con Int) (con IntList)))
(fn len
(doc "Length of an IntList via structural recursion on the tail.")
(type
(fn-type
(params (con IntList))
(ret (con Int))))
(params xs)
(body
(match xs
(case (pat-ctor INil) 0)
(case (pat-ctor ICons h t)
(app + 1 (app len t)))))))
-41
View File
@@ -1,41 +0,0 @@
; Iter it.2 Phase-3 e2e: an it.2-clean structural recursion that
; actually RUNS. `sum` recurses on the Cons-tail `t` via a plain,
; non-tail `(app sum t)` — structurally guarded ⇒ the it.2 check
; classifies it pure + total, so it carries NO `!Diverge` and uses
; NO `tail-app` marker. This proves the structural-recursion
; classification is behaviourally sound end-to-end (the "total"
; verdict is not just a typecheck assertion): build [1,2,3,4,5],
; sum it structurally, print 15.
(module struct_rec_sum_e2e
(data IntList
(ctor INil)
(ctor ICons (con Int) (con IntList)))
(fn sum
(doc "Structural sum: plain non-tail recursion on the Cons tail. No !Diverge, no tail-app.")
(type
(fn-type
(params (con IntList))
(ret (con Int))))
(params xs)
(body
(match xs
(case (pat-ctor INil) 0)
(case (pat-ctor ICons h t)
(app + h (app sum t))))))
(fn main
(doc "Build [1,2,3,4,5] and print its structural sum. Expected stdout: 15.")
(type (fn-type (params) (ret (con Unit)) (effects IO)))
(params)
(body
(app print
(app sum
(term-ctor IntList ICons 1
(term-ctor IntList ICons 2
(term-ctor IntList ICons 3
(term-ctor IntList ICons 4
(term-ctor IntList ICons 5
(term-ctor IntList INil)))))))))))
-43
View File
@@ -1,43 +0,0 @@
; Iter it.2 positive (spec D1, DD-3): mutual structural recursion
; over one ADT family. `Tree` references `Forest` (the `Node` field)
; and `Forest` references `Tree` and `Forest` (the `Cons` fields), so
; the union-find of the ADT type-reference graph puts {Tree, Forest}
; in a single connected component. `tree_size` recurses into
; `forest_size` on the `Node`-bound `f` (structurally smaller); each
; `forest_size` self/cross call passes a `Cons`-bound sub-component.
; The whole mutual group is structural: pure, total, Diverge-free,
; no `tail-app` markers.
(module struct_rec_tree_forest
(data Tree
(ctor Node (con Int) (con Forest)))
(data Forest
(ctor FNil)
(ctor FCons (con Tree) (con Forest)))
(fn tree_size
(doc "Node count of a tree; recurses into forest_size on the Node forest.")
(type
(fn-type
(params (con Tree))
(ret (con Int))))
(params tr)
(body
(match tr
(case (pat-ctor Node v f)
(app + 1 (app forest_size f))))))
(fn forest_size
(doc "Node count of a forest; mutual with tree_size, self on the tail.")
(type
(fn-type
(params (con Forest))
(ret (con Int))))
(params fo)
(body
(match fo
(case (pat-ctor FNil) 0)
(case (pat-ctor FCons t rest)
(app + (app tree_size t) (app forest_size rest)))))))
@@ -1 +0,0 @@
{"defs":[{"body":{"args":[{"binders":[{"init":{"lit":{"kind":"int","value":0},"t":"lit"},"name":"acc","type":{"k":"con","name":"Int"}},{"init":{"lit":{"kind":"int","value":1},"t":"lit"},"name":"i","type":{"k":"con","name":"Int"}}],"body":{"cond":{"args":[{"name":"i","t":"var"},{"lit":{"kind":"int","value":10},"t":"lit"}],"fn":{"name":">","t":"var"},"t":"app"},"else":{"args":[{"args":[{"name":"acc","t":"var"},{"name":"i","t":"var"}],"fn":{"name":"+","t":"var"},"t":"app"},{"args":[{"name":"i","t":"var"},{"lit":{"kind":"int","value":1},"t":"lit"}],"fn":{"name":"+","t":"var"},"t":"app"}],"t":"recur"},"t":"if","then":{"name":"acc","t":"var"}},"t":"loop"}],"fn":{"name":"print","t":"var"},"t":"app"},"doc":"Loop-bearing fn that omits !Diverge from its effect row — must fire undeclared-effect.","kind":"fn","name":"main","params":[],"type":{"effects":["IO"],"k":"fn","params":[],"ret":{"k":"con","name":"Unit"}}}],"imports":[],"name":"test_loop_missing_diverge","schema":"ailang/v0"}
@@ -1 +0,0 @@
{"defs":[{"ctors":[{"fields":[],"name":"LNil"},{"fields":[{"k":"con","name":"Int"},{"k":"con","name":"MyList"}],"name":"LCons"}],"kind":"type","name":"MyList"},{"ctors":[{"fields":[],"name":"TLeaf"},{"fields":[{"k":"con","name":"MyTree"},{"k":"con","name":"MyTree"}],"name":"TBranch"}],"kind":"type","name":"MyTree"},{"body":{"arms":[{"body":{"lit":{"kind":"int","value":0},"t":"lit"},"pat":{"ctor":"LNil","fields":[],"p":"ctor"}},{"body":{"args":[{"name":"h","t":"var"},{"args":[{"args":[{"name":"t","t":"var"}],"fn":{"name":"mk_tree","t":"var"},"t":"app"}],"fn":{"name":"g","t":"var"},"t":"app"}],"fn":{"name":"+","t":"var"},"t":"app"},"pat":{"ctor":"LCons","fields":[{"name":"h","p":"var"},{"name":"t","p":"var"}],"p":"ctor"}}],"scrutinee":{"name":"xs","t":"var"},"t":"match"},"doc":"f recurses into g passing a List sub-component, but g's structural param is an unrelated Tree family.","kind":"fn","name":"f","params":["xs"],"type":{"effects":[],"k":"fn","params":[{"k":"con","name":"MyList"}],"ret":{"k":"con","name":"Int"}}},{"body":{"args":[],"ctor":"TLeaf","t":"ctor","type":"MyTree"},"kind":"fn","name":"mk_tree","params":["xs"],"type":{"effects":[],"k":"fn","params":[{"k":"con","name":"MyList"}],"ret":{"k":"con","name":"MyTree"}}},{"body":{"arms":[{"body":{"lit":{"kind":"int","value":0},"t":"lit"},"pat":{"ctor":"TLeaf","fields":[],"p":"ctor"}},{"body":{"args":[{"args":[{"name":"l","t":"var"}],"fn":{"name":"g","t":"var"},"t":"app"},{"args":[{"args":[],"ctor":"LNil","t":"ctor","type":"MyList"}],"fn":{"name":"f","t":"var"},"t":"app"}],"fn":{"name":"+","t":"var"},"t":"app"},"pat":{"ctor":"TBranch","fields":[{"name":"l","p":"var"},{"name":"r","p":"var"}],"p":"ctor"}}],"scrutinee":{"name":"tr","t":"var"},"t":"match"},"doc":"g recurses into f, but its own structural param is a Tree (different ADT family).","kind":"fn","name":"g","params":["tr"],"type":{"effects":[],"k":"fn","params":[{"k":"con","name":"MyTree"}],"ret":{"k":"con","name":"Int"}}}],"imports":[],"name":"test_mutual_cross_family","schema":"ailang/v0"}
@@ -1 +0,0 @@
{"defs":[{"ctors":[{"fields":[],"name":"INil"},{"fields":[{"k":"con","name":"Int"},{"k":"con","name":"IntList"}],"name":"ICons"}],"kind":"type","name":"IntList"},{"body":{"arms":[{"body":{"lit":{"kind":"int","value":0},"t":"lit"},"pat":{"ctor":"INil","fields":[],"p":"ctor"}},{"body":{"args":[{"name":"h","t":"var"},{"args":[{"name":"xs","t":"var"}],"fn":{"name":"f","t":"var"},"t":"app"}],"fn":{"name":"+","t":"var"},"t":"app"},"pat":{"ctor":"ICons","fields":[{"name":"h","p":"var"},{"name":"t","p":"var"}],"p":"ctor"}}],"scrutinee":{"name":"xs","t":"var"},"t":"match"},"doc":"Non-structural: recurses on the SAME xs, not a sub-component. Not tail-marked.","kind":"fn","name":"f","params":["xs"],"type":{"effects":[],"k":"fn","params":[{"k":"con","name":"IntList"}],"ret":{"k":"con","name":"Int"}}}],"imports":[],"name":"test_non_structural_recursion","schema":"ailang/v0"}
@@ -1,60 +0,0 @@
{
"schema": "ailang/v0",
"name": "test_recur_arity_mismatch",
"imports": [],
"defs": [
{
"kind": "fn",
"name": "main",
"type": {
"k": "fn",
"params": [],
"ret": {
"k": "con",
"name": "Int"
},
"effects": []
},
"params": [],
"doc": "Iter it.1: recur passing 2 args to a 1-binder loop fires recur-arity-mismatch.",
"body": {
"t": "loop",
"binders": [
{
"name": "i",
"type": {
"k": "con",
"name": "Int"
},
"init": {
"t": "lit",
"lit": {
"kind": "int",
"value": 0
}
}
}
],
"body": {
"t": "recur",
"args": [
{
"t": "lit",
"lit": {
"kind": "int",
"value": 1
}
},
{
"t": "lit",
"lit": {
"kind": "int",
"value": 2
}
}
]
}
}
}
]
}
@@ -1,60 +0,0 @@
{
"schema": "ailang/v0",
"name": "test_recur_not_in_tail_position",
"imports": [],
"defs": [
{
"kind": "fn",
"name": "main",
"type": {
"k": "fn",
"params": [],
"ret": {
"k": "con",
"name": "Int"
},
"effects": []
},
"params": [],
"doc": "Iter it.1: recur in the lhs of a seq (non-tail) fires recur-not-in-tail-position.",
"body": {
"t": "loop",
"binders": [
{
"name": "i",
"type": {
"k": "con",
"name": "Int"
},
"init": {
"t": "lit",
"lit": {
"kind": "int",
"value": 0
}
}
}
],
"body": {
"t": "seq",
"lhs": {
"t": "recur",
"args": [
{
"t": "var",
"name": "i"
}
]
},
"rhs": {
"t": "lit",
"lit": {
"kind": "int",
"value": 0
}
}
}
}
}
]
}
-34
View File
@@ -1,34 +0,0 @@
{
"schema": "ailang/v0",
"name": "test_recur_outside_loop",
"imports": [],
"defs": [
{
"kind": "fn",
"name": "main",
"type": {
"k": "fn",
"params": [],
"ret": {
"k": "con",
"name": "Int"
},
"effects": []
},
"params": [],
"doc": "Iter it.1: a recur with no enclosing loop fires recur-outside-loop.",
"body": {
"t": "recur",
"args": [
{
"t": "lit",
"lit": {
"kind": "int",
"value": 1
}
}
]
}
}
]
}
@@ -1,53 +0,0 @@
{
"schema": "ailang/v0",
"name": "test_recur_type_mismatch",
"imports": [],
"defs": [
{
"kind": "fn",
"name": "main",
"type": {
"k": "fn",
"params": [],
"ret": {
"k": "con",
"name": "Int"
},
"effects": []
},
"params": [],
"doc": "Iter it.1: recur passing a Bool to an Int binder fires recur-type-mismatch.",
"body": {
"t": "loop",
"binders": [
{
"name": "i",
"type": {
"k": "con",
"name": "Int"
},
"init": {
"t": "lit",
"lit": {
"kind": "int",
"value": 0
}
}
}
],
"body": {
"t": "recur",
"args": [
{
"t": "lit",
"lit": {
"kind": "bool",
"value": true
}
}
]
}
}
}
]
}