Tagged-union enums — variant payloads + binding match + exhaustiveness #56
Labels
No labels
area:ci
area:docs
area:input
area:net
area:rendering
area:repo
area:stdlib
area:tooling
area:types
cleanup
dx
priority:high
priority:low
priority:medium
proposal
status:in-progress
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: workshopsoft/ludic#56
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Split out of #53 (types phase 3). The one genuinely deep type-system feature from the #1 proposal: extend
enumfrom int constants to a tagged union whose variants carry payloads, and letmatchdestructure and bind them.Today:
enumis named int constants andmatchis a scalar switch (icmp on i32, with a_default).option(#53) andresult(#46) already give the two most-common concrete sum types as built-ins.What this needs (why it was not landed additively):
enum Tile { Empty, Wall, Door(int), Portal(int, int) }, and constructor call syntaxDoor(3).match t { Door(n) => ..., Portal(x, y) => ..., _ => ... }binding payload names in each arm's scope, with the bound types flowing.ptr, arity/type mismatches are unchecked), so this is the natural companion to introducing one.This is invasive (parse.ludic + AST + emit for enum decl / construction / destructuring match + a checking pass) and must preserve the bootstrap fixpoint and every golden render, so it deserves its own issue rather than riding along with the additive option/result work in #53.
Interim coverage: the
Value.*tree (#44) already models dynamic tagged data (a tag + heterogeneous kids) with a runtimeValue.kindmatch;enum+matchcovers the int-tag state-machine case.Landed in
872f458(pushed tomain).What shipped
enumnow extends from named int constants to a tagged union: a variant may carry a payload, e.g.Door(int),Portal(int, int)), stored asN_PARAMkids on each variant node. All-bare enums parse exactly as before.i32tag at offset 0, then one 8-byte slot per payload position (sized to the widest variant). An all-bare enum keeps its zero-cost compile-time-ordinalName.Variantrepresentation — byte-for-byte unchanged, so every golden render and the bootstrap fixpoint still hold.Door(3),Portal(x, y), bareEmpty. Resolved ahead of the function-call fallback; payloads are coerced to their declared types.Door(n) => …,Portal(x, y) => …), with the declared payload types flowing to the bindings.matchmust cover every variant or end in_, else it is rejected at compile time (match on Tile is not exhaustive: variant Door is unhandled). Constructor arity, pattern arity, and binding-form are checked the same way. The checks run where the scrutinee's type is known.Example
Verification
selfhost/tests/enums.ludic(nullary + payload construction, single/multi-payload binding, default arms, OR-ed nullary patterns, enum through params/returns,varreassignment) — passes.x test85/0,x test-tools30/0,check-impl/check-vocabulary/check-docsclean.out.ll == seed.ll), and all four golden renders (incl. chronorift's plain-enum battle menus) match byte-for-byte.Docs updated:
LANGUAGE.md,docs/language/structure/kw-enum.md,docs/language/control/kw-match.md.Notes / scope: payload slots are 8 bytes (holds any i32/i64/ptr payload); nested/literal sub-patterns inside a variant (e.g.
Door(0)) are intentionally not supported — payload positions bind names. The interimValue.*dynamic-tag path andoption/resultbuilt-ins remain as-is.