iter it.2: structural-guardedness checker + first real Diverge effect

Iteration-discipline milestone, 2 of 3. Strictly additive (nothing
tail-related removed; that is it.3). New whole-body pass
verify_structural_recursion sibling of verify_tail_positions
(DD-1): smaller-set algorithm with implicit candidate inference +
unconstrained accumulators (DD-2, foldl=structural), self/mutual
via inline ADT-family union-find (DD-3), it.2-only tail==false
grandfather. CheckError::NonStructuralRecursion. term_contains_loop
(stops at Term::Lam, DD-4) injects Diverge so existing
UndeclaredEffect enforces it, no new variant; lam-arrow + LetRec
sub-effect sites wired. DESIGN.md Decision 3 synced. Four it.1
loop fixtures gained !Diverge.

Two spec-premise boundary defects surfaced + resolved within the
additive invariant (corpus clean, check not weakened), recorded as
corrected it.3 corpus-migration scope: (1) the "21 tail-app
fixtures" grandfather premise under-counts the corpus —
no-ADT-candidate counter recursions have no structural position to
verify, deferred to it.3; (2) two RC-regression fixtures joined the
spec's transitional tail-app grandfather as the other 20 do (RC==GC
guards verified still green). cargo test --workspace 622/0; 9
acceptance pins non-vacuous. Spec fda9b78, plan bc9f512.
This commit is contained in:
2026-05-15 15:29:43 +02:00
parent bc9f512003
commit a4be1e58a3
22 changed files with 1402 additions and 22 deletions
+20 -4
View File
@@ -164,8 +164,16 @@ 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]`). In the MVP only the effects `IO` and `Diverge` (for infinite
loops) are wired up.
(`![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`.
This is the most important LLM property: when I read a function, I can trust
its signature without reading the body.
@@ -2446,8 +2454,16 @@ 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. The structural-recursion restriction and the `Diverge`
effect land in it.2; `tail-app`/`tail-do` are retired in it.3. See
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`**: