feat(types): tagged-union enums — variant payloads + binding match + exhaustiveness (#56)
Extend `enum` from named int constants to a tagged union: a variant may
carry a payload (`enum Tile { Empty, Wall, Door(int), Portal(int, int) }`).
Such enums box to a heap record (an i32 tag at offset 0, then one 8-byte
slot per payload position); an all-bare enum keeps its zero-cost compile-
time-ordinal representation, byte-for-byte unchanged (every golden render
and the bootstrap fixpoint still hold).
- Parser: variant payload declarations, stored as N_PARAM kids on the
variant node.
- Construction: by name — `Door(3)`, `Portal(x, y)`, bare `Empty` — resolved
ahead of the function-call fallback and boxed with the payloads coerced to
their declared types.
- match: destructures a tagged scrutinee, switching on the tag and binding
each arm's payload names in a scoped local frame.
- Checking pass: a tagged `match` must be exhaustive (cover every variant or
end in `_`), and constructor/pattern arities and binding forms are checked
— all reported where the scrutinee's type is known.
Adds selfhost/tests/enums.ludic to the regression suite and documents the
feature in LANGUAGE.md and the enum/match pages.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
7c91d24595
commit
872f458cb2
11 changed files with 17990 additions and 16481 deletions
31
LANGUAGE.md
31
LANGUAGE.md
|
|
@ -738,12 +738,33 @@ match reg(R_CUR) { Action.Attack => attack() Action.Guard => guard() _ => wait
|
|||
if reg(R_MODE) == Mode.Battle { … }
|
||||
```
|
||||
|
||||
A variant is a **compile-time `int`** accessed as `Enum.Variant` (`Action.Guard`
|
||||
A bare variant is a **compile-time `int`** accessed as `Enum.Variant` (`Action.Guard`
|
||||
is `1`), numbered from `0` by declaration order, so it works anywhere an int does
|
||||
— `match` patterns, comparisons, `set_reg`. Enums are a naming layer over `int`:
|
||||
there is no distinct enum runtime type yet, so an enum value lives in an ordinary
|
||||
`int` or register (and is saved with it). See `examples/games/chronorift/combat.ludic`,
|
||||
whose battle menus dispatch on `KnightAct`/`MageAct` instead of `0..3`.
|
||||
— `match` patterns, comparisons, `set_reg`. A plain (all-bare) enum is a naming
|
||||
layer over `int`: an enum value lives in an ordinary `int` or register (and is
|
||||
saved with it). See `examples/games/chronorift/combat.ludic`, whose battle menus
|
||||
dispatch on `KnightAct`/`MageAct` instead of `0..3`.
|
||||
|
||||
A variant may instead carry a **payload**, which makes the enum a *tagged union*:
|
||||
|
||||
```ludic
|
||||
# doc-check: skip — composite: a declaration plus its uses
|
||||
enum Tile { Empty, Wall, Door(int), Portal(int, int) }
|
||||
|
||||
let t: Tile = Door(3) # constructed by name; bare Empty for no payload
|
||||
match t {
|
||||
Empty => rest()
|
||||
Wall => block()
|
||||
Door(n) => open(n) # payload bound as `n` in this arm
|
||||
Portal(x, y) => teleport(x, y) # both fields bound
|
||||
}
|
||||
```
|
||||
|
||||
A payloaded value is boxed (a tag plus its payload slots) and carries the enum's
|
||||
type, so it flows through `let`, params and returns. A tagged `match` is checked
|
||||
for **exhaustiveness** — every variant must be handled or a `_` arm given — and
|
||||
constructor/pattern arities are checked, so adding a variant flags each match that
|
||||
must learn it. Bare enums are untouched by this and keep their zero-cost form.
|
||||
|
||||
## Expressions
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue