tagged unions
This commit is contained in:
@@ -412,7 +412,7 @@
|
||||
a Animal
|
||||
}
|
||||
|
||||
# tagged unions (constrained to the backing enum) — DEFERRED to 21.5
|
||||
# tagged unions (constrained to the backing enum) — see 21.5
|
||||
AnimalNameOrHeight :: union(Animal) { # use just `enum` instead of `Animal` for unconstrained tagged union
|
||||
dog []u8
|
||||
cat []u8
|
||||
@@ -440,10 +440,33 @@
|
||||
runtime `{tag, payload}` layout, tag init at construction, and tag extraction — the real codegen
|
||||
work and the foundation for milestone 22 (`match` with payload unwrapping)
|
||||
|
||||
21.5. tagged unions with a runtime tag (DEFERRED)
|
||||
- `union(Enum)` (variant names constrained to the backing enum's members) and `union(enum)`
|
||||
(auto-generated tag, one member per variant); both store an enum tag beside the payload
|
||||
(`{tag, payload}`), switchable in milestone 22
|
||||
21.5. tagged unions with a runtime tag (implemented; see below)
|
||||
- `union(Enum)` (variant names constrained to an existing enum's members) and `union(enum)`
|
||||
(compiler-synthesized anonymous tag enum, one dense 0-based member per variant); both store
|
||||
the discriminant beside the payload as `{tag, payload}`
|
||||
- a tagged union is a first-class `.Union` type node whose `child` holds the tag enum (untagged
|
||||
unions and structs leave `child` INVALID). its fields are the variants (payload types), still
|
||||
looked up by name. `union(enum)` synthesizes its tag enum up front (`types.enum_anonymous`,
|
||||
variant names as members), so both forms converge on one representation
|
||||
- **key reuse:** the per-variant tag value is *derivable* from `(union type, active variant
|
||||
index)` — the variant's field name matches a member of the tag enum, whose value is the tag.
|
||||
so codegen computes the tag itself and the existing `.Struct`/`.Field` HIR (carrying `type` +
|
||||
active/field `integer`) is sufficient. **no HIR/IR/lowering change** (like 18/19/20); the delta
|
||||
is parser + types layout + one checker validation + three LLVM emit sites
|
||||
- runtime layout `{tag, payload-carrier}`: tag at offset 0, payload carrier at
|
||||
`payload_offset = round_up(sizeof(tag), payload_align)` (`types.union_payload_offset`, shared by
|
||||
`size` and the emitter). field access uses byte-offset GEPs, so offsets stay self-consistent
|
||||
- construction `T{ variant = value }` reuses the union-literal path and additionally stores the
|
||||
derived tag; payload read `x.variant` reuses field access, reading at the payload offset
|
||||
(unchecked reinterpret, Zig-style). safe tag dispatch + payload capture is milestone 22 (`match`)
|
||||
- changes: `parse_struct` parses `union(...)` (`enum` → synthesize; else an existing enum);
|
||||
`types.define_record` takes a `tag` param stored in `child`, plus `is_tagged_union` /
|
||||
`union_tag_enum` / `union_payload_offset` helpers and tagged `size`/`alignment`; the checker
|
||||
record-decl pass requires the tag to be an enum and each variant to name a member of it; the
|
||||
LLVM emitter lays out `{tag, [pad], carrier, [pad]}`, stores the tag in construction, and offsets
|
||||
payload field access
|
||||
- deferred to milestone 22 (`match`): safe tag dispatch + payload capture (`match x { .bird |v| … }`),
|
||||
exhaustiveness checking, and any first-class tag-read accessor
|
||||
|
||||
22. match statements with tagged unions payload unwrapping
|
||||
|
||||
@@ -741,6 +764,93 @@ for 0..10 |i| blk: { # bad, no name binds returned value
|
||||
}
|
||||
```
|
||||
|
||||
## A word on match statements
|
||||
|
||||
```
|
||||
# matching on enums
|
||||
match status {
|
||||
.ok: print("success")
|
||||
.error: print("failure")
|
||||
.pending: {
|
||||
log("still waiting")
|
||||
retry()
|
||||
}
|
||||
}
|
||||
|
||||
# matching on integers and other values
|
||||
match code {
|
||||
0: print("zero")
|
||||
1: print("one")
|
||||
2: print("two")
|
||||
else: print("other") # needed - missing variants
|
||||
}
|
||||
|
||||
Status :: enum { ok, error, pending }
|
||||
|
||||
status :: get_status() # returns a `Status`
|
||||
match status {
|
||||
.ok: handle_ok()
|
||||
.error: handle_error()
|
||||
.pending: handle_pending()
|
||||
# no else needed - all variants covered
|
||||
}
|
||||
|
||||
# matching on tagged unions
|
||||
Result :: union(enum) {
|
||||
success Data
|
||||
failure struct {
|
||||
msg []u8
|
||||
code i32
|
||||
}
|
||||
pending void
|
||||
}
|
||||
|
||||
match result {
|
||||
.success |data|: { # use `|name|` to capture the variant's payload
|
||||
process(data)
|
||||
}
|
||||
.failure |info|: {
|
||||
print("error {d}: {s}", { info.code, info.msg })
|
||||
}
|
||||
.pending: {
|
||||
# void payload - no capture needed
|
||||
wait()
|
||||
}
|
||||
}
|
||||
|
||||
# when a union variant has a `void` payload, omit the capture
|
||||
Event :: union(enum) {
|
||||
click struct { x i32, y i32 }
|
||||
keypress KeyCode
|
||||
quit void
|
||||
}
|
||||
|
||||
event :: get_event() # returns an `Event`
|
||||
match event {
|
||||
.click |pos|: handle_click(pos.x, pos.y)
|
||||
.keypress |key|: handle_key(key)
|
||||
.quit: should_exit = true
|
||||
}
|
||||
|
||||
# single-expression arms yield value implicitly
|
||||
label :: match priority {
|
||||
.critical: "CRIT"
|
||||
.high: "HIGH"
|
||||
.normal: "NORM"
|
||||
.low: " LOW"
|
||||
}
|
||||
|
||||
# multi-statement arms use `yield`
|
||||
message :: match code {
|
||||
0: "success"
|
||||
1: {
|
||||
log("warning encountered")
|
||||
yield "warning"
|
||||
}
|
||||
else: "unknown"
|
||||
}
|
||||
```
|
||||
|
||||
## A word on memory allocation
|
||||
|
||||
(NOTE THAT SYNTAX MAY NOT MATCH BROLANG EXACTLY AND SHOULD BE TAKEN WITH A GRAIN OF SALT - INSPIRATION ONLY)
|
||||
|
||||
Reference in New Issue
Block a user