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
|
|
@ -539,6 +539,30 @@ function emit_ns_call(ns: pointer, meth: pointer, e: Node) -> Val {
|
|||
return emit_call(e)
|
||||
}
|
||||
|
||||
# construct a tagged-enum variant box (issue #56): malloc the box, store the tag
|
||||
# at offset 0, then each payload into its 8-byte slot (slot k at byte 8*(k+1)),
|
||||
# coerced to the variant's declared payload type. `args` are the constructor
|
||||
# argument nodes (empty for a bare nullary variant). Returns the box as an
|
||||
# `en.s`-typed pointer so it flows through lets/params/returns like any handle.
|
||||
function emit_variant_new(en: Node, ord: int, args: []Node) -> Val {
|
||||
let variant = en.kids[ord]
|
||||
let arity = len(variant.kids)
|
||||
if (len(args) != arity) { perr(`enum variant {variant.s} takes {itoa(arity)} payload(s), got {itoa(len(args))}`) }
|
||||
let box = emit_bind(`call ptr @malloc(i64 {itoa(enum_box_size(en))})`)
|
||||
emit(" store i32 "); emit(itoa(ord)); emit(", ptr "); emit(box); emit("\n")
|
||||
var k = 0
|
||||
while k < arity {
|
||||
let pty = variant.kids[k].ty
|
||||
let v = emit_expr(args[k])
|
||||
let cv = coerce_code(v, pty) # emit any widening BEFORE the store line
|
||||
let p = nreg(); emit(" "); emit(p); emit(" = getelementptr inbounds i8, ptr "); emit(box)
|
||||
emit(", i32 "); emit(itoa(8 * (k + 1))); emit("\n")
|
||||
emit(" store "); emit(llty(pty)); emit(" "); emit(cv); emit(", ptr "); emit(p); emit("\n")
|
||||
k = k + 1
|
||||
}
|
||||
return val(box, en.s)
|
||||
}
|
||||
|
||||
function emit_call(e: Node) -> Val {
|
||||
# `Subject.action(...)` — a namespaced builtin (Screen/Random/Input).
|
||||
if e.a.kind == E_MEMBER {
|
||||
|
|
@ -865,6 +889,11 @@ function emit_call(e: Node) -> Val {
|
|||
emit(")\n")
|
||||
return val(erreg, ext.ty)
|
||||
}
|
||||
# a tagged-enum variant constructor with a payload: `Door(3)`, `Portal(x, y)`.
|
||||
# A user function of the same name would have been resolved above; variants are
|
||||
# capitalized by convention, so this rarely competes.
|
||||
let ctor = variant_enum(name)
|
||||
if (ctor != null) { return emit_variant_new(ctor, g_var_ord, e.kids) }
|
||||
var fn2 = find_fn(name)
|
||||
var cname = name
|
||||
if (fn2 == null) {
|
||||
|
|
@ -935,6 +964,10 @@ function emit_expr(e: Node) -> Val {
|
|||
}
|
||||
# a UI_<name> that is not a const/var resolves to its widget index
|
||||
if is_ui_ident(e.s) { return val(itoa(ui_index_of(e.s)), "int") }
|
||||
# a bare payload-less tagged-enum variant: `Empty` boxes a tag with no
|
||||
# payload. Locals/globals were checked first, so a same-named binding wins.
|
||||
let nv = variant_enum(e.s)
|
||||
if (nv != null) { return emit_variant_new(nv, g_var_ord, new []Node) }
|
||||
perr(`unknown identifier {e.s}`)
|
||||
}
|
||||
if e.kind == E_MEMBER {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue