Iter 16d: chain-terminator via __unreachable__ builtin

Eliminates the Unit-typed chain default that 16c left in place.
Adds __unreachable__ : forall a. a — codegen lowers to LLVM
unreachable. The 16a/16c chain machinery now uses it as the
deepest fall-through, so exhaustive matches with non-Unit arms
no longer need a (case _ ...) workaround.

- check/builtins.rs: register __unreachable__ as Forall(a, a).
- codegen: Term::Var "__unreachable__" emits LLVM unreachable
  and sets block_terminated; If/Match/fn-body gate downstream
  work on that flag.
- desugar: chain default switched from Lit{Unit} to Var.
- examples/lit_pat: categorize_first's trailing _ arm removed.
  Hash changes from c4faec3abc2ed388 to 644de0c0ec15fc17.
- examples/unreachable_demo: positive fixture using safe_div
  with __unreachable__ in the impossible branch.
- e2e + desugar tests: 122 → 124 (+2).

Path-a from 16b.2 planning: real primitive over a desugar-time
exhaustiveness pre-check (which would need cross-module type
registry access from desugar).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-07 23:00:23 +02:00
parent e5f9828a04
commit af35612c1d
10 changed files with 353 additions and 14 deletions
+1 -1
View File
@@ -1 +1 @@
{"defs":[{"ctors":[{"fields":[],"name":"Nil"},{"fields":[{"k":"con","name":"Int"},{"k":"con","name":"IntList"}],"name":"Cons"}],"kind":"type","name":"IntList"},{"body":{"arms":[{"body":{"lit":{"kind":"int","value":100},"t":"lit"},"pat":{"lit":{"kind":"int","value":0},"p":"lit"}},{"body":{"lit":{"kind":"int","value":200},"t":"lit"},"pat":{"lit":{"kind":"int","value":1},"p":"lit"}},{"body":{"lit":{"kind":"int","value":999},"t":"lit"},"pat":{"p":"wild"}}],"scrutinee":{"name":"n","t":"var"},"t":"match"},"doc":"Map 0->100, 1->200, anything else->999.","kind":"fn","name":"classify","params":["n"],"type":{"effects":[],"k":"fn","params":[{"k":"con","name":"Int"}],"ret":{"k":"con","name":"Int"}}},{"body":{"arms":[{"body":{"lit":{"kind":"int","value":-1},"t":"lit"},"pat":{"ctor":"Nil","fields":[],"p":"ctor"}},{"body":{"lit":{"kind":"int","value":0},"t":"lit"},"pat":{"ctor":"Cons","fields":[{"lit":{"kind":"int","value":0},"p":"lit"},{"p":"wild"}],"p":"ctor"}},{"body":{"name":"h","t":"var"},"pat":{"ctor":"Cons","fields":[{"name":"h","p":"var"},{"p":"wild"}],"p":"ctor"}},{"body":{"lit":{"kind":"int","value":0},"t":"lit"},"pat":{"p":"wild"}}],"scrutinee":{"name":"xs","t":"var"},"t":"match"},"doc":"Empty -> -1; first element 0 -> 0; otherwise return the head.","kind":"fn","name":"categorize_first","params":["xs"],"type":{"effects":[],"k":"fn","params":[{"k":"con","name":"IntList"}],"ret":{"k":"con","name":"Int"}}},{"body":{"lhs":{"args":[{"args":[{"lit":{"kind":"int","value":0},"t":"lit"}],"fn":{"name":"classify","t":"var"},"t":"app"}],"op":"io/print_int","t":"do"},"rhs":{"lhs":{"args":[{"args":[{"lit":{"kind":"int","value":1},"t":"lit"}],"fn":{"name":"classify","t":"var"},"t":"app"}],"op":"io/print_int","t":"do"},"rhs":{"lhs":{"args":[{"args":[{"lit":{"kind":"int","value":17},"t":"lit"}],"fn":{"name":"classify","t":"var"},"t":"app"}],"op":"io/print_int","t":"do"},"rhs":{"lhs":{"args":[{"args":[{"args":[],"ctor":"Nil","t":"ctor","type":"IntList"}],"fn":{"name":"categorize_first","t":"var"},"t":"app"}],"op":"io/print_int","t":"do"},"rhs":{"lhs":{"args":[{"args":[{"args":[{"lit":{"kind":"int","value":0},"t":"lit"},{"args":[],"ctor":"Nil","t":"ctor","type":"IntList"}],"ctor":"Cons","t":"ctor","type":"IntList"}],"fn":{"name":"categorize_first","t":"var"},"t":"app"}],"op":"io/print_int","t":"do"},"rhs":{"args":[{"args":[{"args":[{"lit":{"kind":"int","value":7},"t":"lit"},{"args":[],"ctor":"Nil","t":"ctor","type":"IntList"}],"ctor":"Cons","t":"ctor","type":"IntList"}],"fn":{"name":"categorize_first","t":"var"},"t":"app"}],"op":"io/print_int","t":"do"},"t":"seq"},"t":"seq"},"t":"seq"},"t":"seq"},"t":"seq"},"doc":"Drive both fns. Expected (per line): 100, 200, 999, -1, 0, 7.","kind":"fn","name":"main","params":[],"type":{"effects":["IO"],"k":"fn","params":[],"ret":{"k":"con","name":"Unit"}}}],"imports":[],"name":"lit_pat","schema":"ailang/v0"}
{"defs":[{"ctors":[{"fields":[],"name":"Nil"},{"fields":[{"k":"con","name":"Int"},{"k":"con","name":"IntList"}],"name":"Cons"}],"kind":"type","name":"IntList"},{"body":{"arms":[{"body":{"lit":{"kind":"int","value":100},"t":"lit"},"pat":{"lit":{"kind":"int","value":0},"p":"lit"}},{"body":{"lit":{"kind":"int","value":200},"t":"lit"},"pat":{"lit":{"kind":"int","value":1},"p":"lit"}},{"body":{"lit":{"kind":"int","value":999},"t":"lit"},"pat":{"p":"wild"}}],"scrutinee":{"name":"n","t":"var"},"t":"match"},"doc":"Map 0->100, 1->200, anything else->999.","kind":"fn","name":"classify","params":["n"],"type":{"effects":[],"k":"fn","params":[{"k":"con","name":"Int"}],"ret":{"k":"con","name":"Int"}}},{"body":{"arms":[{"body":{"lit":{"kind":"int","value":-1},"t":"lit"},"pat":{"ctor":"Nil","fields":[],"p":"ctor"}},{"body":{"lit":{"kind":"int","value":0},"t":"lit"},"pat":{"ctor":"Cons","fields":[{"lit":{"kind":"int","value":0},"p":"lit"},{"p":"wild"}],"p":"ctor"}},{"body":{"name":"h","t":"var"},"pat":{"ctor":"Cons","fields":[{"name":"h","p":"var"},{"p":"wild"}],"p":"ctor"}}],"scrutinee":{"name":"xs","t":"var"},"t":"match"},"doc":"Empty -> -1; first element 0 -> 0; otherwise return the head.","kind":"fn","name":"categorize_first","params":["xs"],"type":{"effects":[],"k":"fn","params":[{"k":"con","name":"IntList"}],"ret":{"k":"con","name":"Int"}}},{"body":{"lhs":{"args":[{"args":[{"lit":{"kind":"int","value":0},"t":"lit"}],"fn":{"name":"classify","t":"var"},"t":"app"}],"op":"io/print_int","t":"do"},"rhs":{"lhs":{"args":[{"args":[{"lit":{"kind":"int","value":1},"t":"lit"}],"fn":{"name":"classify","t":"var"},"t":"app"}],"op":"io/print_int","t":"do"},"rhs":{"lhs":{"args":[{"args":[{"lit":{"kind":"int","value":17},"t":"lit"}],"fn":{"name":"classify","t":"var"},"t":"app"}],"op":"io/print_int","t":"do"},"rhs":{"lhs":{"args":[{"args":[{"args":[],"ctor":"Nil","t":"ctor","type":"IntList"}],"fn":{"name":"categorize_first","t":"var"},"t":"app"}],"op":"io/print_int","t":"do"},"rhs":{"lhs":{"args":[{"args":[{"args":[{"lit":{"kind":"int","value":0},"t":"lit"},{"args":[],"ctor":"Nil","t":"ctor","type":"IntList"}],"ctor":"Cons","t":"ctor","type":"IntList"}],"fn":{"name":"categorize_first","t":"var"},"t":"app"}],"op":"io/print_int","t":"do"},"rhs":{"args":[{"args":[{"args":[{"lit":{"kind":"int","value":7},"t":"lit"},{"args":[],"ctor":"Nil","t":"ctor","type":"IntList"}],"ctor":"Cons","t":"ctor","type":"IntList"}],"fn":{"name":"categorize_first","t":"var"},"t":"app"}],"op":"io/print_int","t":"do"},"t":"seq"},"t":"seq"},"t":"seq"},"t":"seq"},"t":"seq"},"doc":"Drive both fns. Expected (per line): 100, 200, 999, -1, 0, 7.","kind":"fn","name":"main","params":[],"type":{"effects":["IO"],"k":"fn","params":[],"ret":{"k":"con","name":"Unit"}}}],"imports":[],"name":"lit_pat","schema":"ailang/v0"}
+8 -6
View File
@@ -5,10 +5,13 @@
; inside Pat-Ctor's first field. Both shapes are now handled by the
; 16c desugar pass; codegen sees only If/Match-on-Ctor combinations.
;
; The trailing `_` catch-all in `categorize_first` is required by the
; 16a chain machinery: the chain's terminator is a `Unit` literal,
; reachable when no arm matches. A `_` arm dominates the chain so the
; terminator stays unreachable in practice and the match type-checks.
; Iter 16d update — the chain machinery's terminator is now the
; polymorphic `__unreachable__` builtin (`forall a. a`, lowered to
; LLVM `unreachable`) instead of a `Unit` literal. That removed the
; pre-16d need for a trailing `(case _ 0)` arm in `categorize_first`
; whose only purpose was to dominate the synthetic `Unit`-typed
; terminator. The two ctor arms (`Nil` and `Cons h _`) are
; exhaustive on `IntList`; the chain default is never reached.
;
; Expected stdout (one per line): 100, 200, 999, -1, 0, 7.
@@ -36,8 +39,7 @@
(match xs
(case (pat-ctor Nil) -1)
(case (pat-ctor Cons (pat-lit 0) _) 0)
(case (pat-ctor Cons h _) h)
(case _ 0))))
(case (pat-ctor Cons h _) h))))
(fn main
(doc "Drive both fns. Expected (per line): 100, 200, 999, -1, 0, 7.")
+1
View File
@@ -0,0 +1 @@
{"defs":[{"body":{"cond":{"args":[{"name":"b","t":"var"},{"lit":{"kind":"int","value":0},"t":"lit"}],"fn":{"name":"==","t":"var"},"t":"app"},"else":{"args":[{"name":"a","t":"var"},{"name":"b","t":"var"}],"fn":{"name":"/","t":"var"},"t":"app"},"t":"if","then":{"name":"__unreachable__","t":"var"}},"doc":"a/b for b != 0; otherwise panic via __unreachable__.","kind":"fn","name":"safe_div","params":["a","b"],"type":{"effects":[],"k":"fn","params":[{"k":"con","name":"Int"},{"k":"con","name":"Int"}],"ret":{"k":"con","name":"Int"}}},{"body":{"lhs":{"args":[{"args":[{"lit":{"kind":"int","value":8},"t":"lit"},{"lit":{"kind":"int","value":2},"t":"lit"}],"fn":{"name":"safe_div","t":"var"},"t":"app"}],"op":"io/print_int","t":"do"},"rhs":{"args":[{"args":[{"lit":{"kind":"int","value":15},"t":"lit"},{"lit":{"kind":"int","value":3},"t":"lit"}],"fn":{"name":"safe_div","t":"var"},"t":"app"}],"op":"io/print_int","t":"do"},"t":"seq"},"doc":"Drive safe_div with non-zero divisors. Expected: 4, 5.","kind":"fn","name":"main","params":[],"type":{"effects":["IO"],"k":"fn","params":[],"ret":{"k":"con","name":"Unit"}}}],"imports":[],"name":"unreachable_demo","schema":"ailang/v0"}
+35
View File
@@ -0,0 +1,35 @@
; Iter 16d — `__unreachable__` as an explicit user-callable primitive.
; Type: forall a. a (lowered to LLVM `unreachable`).
;
; This fixture exercises `__unreachable__` in a value position that is
; statically unreachable but still type-checks: the impossible branch
; of an if. `safe_div(a, b)` returns `a / b` when `b != 0` and panics
; via `__unreachable__` otherwise. The driver only ever calls it with
; non-zero divisors, so the panic branch is never executed.
;
; The point of the fixture is the type-system + codegen surface:
; `__unreachable__` unifies against `Int` (the fn's return type) at
; the typechecker, and codegen lowers it to `unreachable` followed by
; the rest of the if's join machinery, which is sound because the
; surrounding if's `block_terminated` flag is honoured.
;
; Expected stdout (one per line): 4, 5.
(module unreachable_demo
(fn safe_div
(doc "a/b for b != 0; otherwise panic via __unreachable__.")
(type (fn-type (params (con Int) (con Int)) (ret (con Int))))
(params a b)
(body
(if (app == b 0)
__unreachable__
(app / a b))))
(fn main
(doc "Drive safe_div with non-zero divisors. Expected: 4, 5.")
(type (fn-type (params) (ret (con Unit)) (effects IO)))
(params)
(body
(seq (do io/print_int (app safe_div 8 2))
(do io/print_int (app safe_div 15 3))))))