docs(contracts): make 0012 tail-context list code-exhaustive

The §Typecheck enumeration in 0012-tail-calls.md presented the
tail-position rule as exhaustive ("The flag is `false` for: ...") but
omitted `Term::If` entirely — even though `verify_tail_positions`
propagates `is_tail` into both `If` branches (ailang-check/src/lib.rs),
and 0012's own motivating example (`if n == 0 then () else loop(n-1)`)
relies on the `else` branch being a tail position.

Restate the rule by principle (a sub-term is in tail position iff it is
the value of the whole expression) and enumerate the actual checker's
arms: `If` branches, `Match` arms, `Seq` right, `Let`/`LetRec`
in-clause, `Lam` body, and the identity/`Loop` propagators; every
argument position (incl. `If` condition, `Recur`/`New` args) is
non-tail. Matches the present `verify_tail_positions` walker.
This commit is contained in:
2026-06-02 11:16:49 +02:00
parent 625fe849be
commit 56e076b2ca
+11 -6
View File
@@ -19,12 +19,17 @@ Solution: explicit, verified tail calls.
- **Typecheck.** A new pass `verify_tail_positions(fn_body)` - **Typecheck.** A new pass `verify_tail_positions(fn_body)`
runs after the main type-check. It walks the body with an runs after the main type-check. It walks the body with an
`is_tail_context: bool` threaded down. The flag is `true` at `is_tail_context: bool` threaded down. The flag is `true` at
the start, `true` for the body of every `Term::Match` arm, the start and propagates to every sub-term that is the value of
`true` for the right operand of `Term::Seq`, `true` for the the whole expression: the body of every `Term::Match` arm, both
body of `Term::Let`, `true` for the body of `Term::Lam` (each branches of `Term::If`, the right operand of `Term::Seq`, the
Lam opens its own tail scope). The flag is `false` for: args body of `Term::Let` and the in-clause of `Term::LetRec`, and the
of any `App`/`Do`/`Ctor`, scrutinee of `Match`, left of body of `Term::Lam` (each Lam opens its own tail scope); the
`Seq`, condition of `Let`-bound expression. When the walker identity nodes `Term::Clone` / `Term::ReuseAs` and a `Term::Loop`
body propagate the enclosing flag unchanged. The flag is `false`
for every argument position: args of any
`App`/`Do`/`Ctor`/`Recur`/`New`, the scrutinee of `Match`, the
condition of `If`, the left operand of `Seq`, and the value of a
`Let`/`LetRec` binding. When the walker
visits an `App { tail: true }` or `Do { tail: true }`, the visits an `App { tail: true }` or `Do { tail: true }`, the
flag must be `true` at that visit; otherwise emit diagnostic flag must be `true` at that visit; otherwise emit diagnostic
`tail-call-not-in-tail-position`. `tail-call-not-in-tail-position`.