unions (untagged)
This commit is contained in:
@@ -402,7 +402,48 @@
|
|||||||
loop) whose concrete yield references a local declared *past* the first yield (annotate);
|
loop) whose concrete yield references a local declared *past* the first yield (annotate);
|
||||||
same-label loop/block shadowing resolves innermost-wins
|
same-label loop/block shadowing resolves innermost-wins
|
||||||
|
|
||||||
21. unions and tagged unions
|
21. unions and tagged unions (implemented; first pass — native untagged unions only; see below)
|
||||||
|
- inspired by zig
|
||||||
|
- ```
|
||||||
|
# unions (untagged; named fields like a struct — Zig union members are always named)
|
||||||
|
SomeStuff :: union {
|
||||||
|
f float
|
||||||
|
i int
|
||||||
|
a Animal
|
||||||
|
}
|
||||||
|
|
||||||
|
# tagged unions (constrained to the backing enum) — DEFERRED to 21.5
|
||||||
|
AnimalNameOrHeight :: union(Animal) { # use just `enum` instead of `Animal` for unconstrained tagged union
|
||||||
|
dog []u8
|
||||||
|
cat []u8
|
||||||
|
bird int
|
||||||
|
lizard int
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- this first pass ships **native untagged unions with named fields**. an untagged union is a
|
||||||
|
carrier sized to its largest/most-aligned member with no runtime tag (a C union); reading a
|
||||||
|
non-active field reinterprets the bytes (unsafe, like a Zig `union {}` in ReleaseFast) and an
|
||||||
|
untagged union is not matchable. the TODO's original bare type-only spelling
|
||||||
|
(`union { float, int, Animal }`) was dropped in favor of named fields to match Zig and to
|
||||||
|
reuse the existing field-access path
|
||||||
|
- the entire untagged-union backend already existed from the C-interop work (milestone 4): the
|
||||||
|
`.Union` type kind, carrier-based LLVM layout (`emit_types`), `size`/`alignment`, keyed-literal
|
||||||
|
construction `Val{ field = value }` (exactly one initializer; the active-field index rides in
|
||||||
|
`hir.Expr.integer`), and field access (GEP-at-offset-0 reinterpret). none of these are gated on
|
||||||
|
`c_layout`, so a *native* union flows through them unchanged. record-declaration validation
|
||||||
|
(`is_runtime_value` per field) already covers native unions too
|
||||||
|
- the change is front-end only: a new `union` keyword (token/lexer), and `parse_union` is just
|
||||||
|
`parse_struct` parametrized with `is_union=true` routing through `types.define_record`
|
||||||
|
(native-only, so `c_layout=false` and the no-body case errors). no `types`/`checker`/`hir`/
|
||||||
|
`lower`/`llvm` change (mirrors the minimal-footprint slices of 18/19/20)
|
||||||
|
- deferred to **21.5**: tagged unions (`union(Enum)` constrained + `union(enum)` inferred) with a
|
||||||
|
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
|
||||||
|
|
||||||
22. match statements with tagged unions payload unwrapping
|
22. match statements with tagged unions payload unwrapping
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ keyword_kind :: proc(text: string) -> token.Kind {
|
|||||||
case "c_func": return .Keyword_C_Func
|
case "c_func": return .Keyword_C_Func
|
||||||
case "struct": return .Keyword_Struct
|
case "struct": return .Keyword_Struct
|
||||||
case "c_struct": return .Keyword_C_Struct
|
case "c_struct": return .Keyword_C_Struct
|
||||||
|
case "union": return .Keyword_Union
|
||||||
case "enum": return .Keyword_Enum
|
case "enum": return .Keyword_Enum
|
||||||
case "distinct": return .Keyword_Distinct
|
case "distinct": return .Keyword_Distinct
|
||||||
case "alias": return .Keyword_Alias
|
case "alias": return .Keyword_Alias
|
||||||
|
|||||||
@@ -1727,16 +1727,19 @@ parse_function :: proc(parser: ^Parser, name: token.Token, c_abi: bool) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
parse_struct :: proc(parser: ^Parser, name: token.Token, c_layout: bool) {
|
parse_struct :: proc(parser: ^Parser, name: token.Token, c_layout: bool, is_union := false) {
|
||||||
start := advance(parser)
|
start := advance(parser)
|
||||||
id := types.named(&parser.module.type_store, u32(parser.pkg), u32(name.symbol))
|
id := types.named(&parser.module.type_store, u32(parser.pkg), u32(name.symbol))
|
||||||
ended_by_newline := current(parser).kind == .Newline
|
ended_by_newline := current(parser).kind == .Newline
|
||||||
skip_newlines(parser)
|
skip_newlines(parser)
|
||||||
if current(parser).kind != .Left_Brace {
|
if current(parser).kind != .Left_Brace {
|
||||||
if !c_layout {
|
if !c_layout {
|
||||||
source.add(parser.diagnostics, start.span, "native struct declarations require a body")
|
source.add(
|
||||||
|
parser.diagnostics, start.span,
|
||||||
|
"native union declarations require a body" if is_union else "native struct declarations require a body",
|
||||||
|
)
|
||||||
}
|
}
|
||||||
if !types.define_struct(&parser.module.type_store, id, nil, c_layout, true) {
|
if !types.define_record(&parser.module.type_store, id, nil, c_layout, true, is_union) {
|
||||||
source.addf(parser.diagnostics, name.span, "duplicate type declaration '%s'", token_text(parser, name))
|
source.addf(parser.diagnostics, name.span, "duplicate type declaration '%s'", token_text(parser, name))
|
||||||
}
|
}
|
||||||
if !ended_by_newline {
|
if !ended_by_newline {
|
||||||
@@ -1772,7 +1775,7 @@ parse_struct :: proc(parser: ^Parser, name: token.Token, c_layout: bool) {
|
|||||||
if _, ok := allow(parser, .Right_Brace); !ok {
|
if _, ok := allow(parser, .Right_Brace); !ok {
|
||||||
source.add(parser.diagnostics, current(parser).span, "expected '}' after struct fields")
|
source.add(parser.diagnostics, current(parser).span, "expected '}' after struct fields")
|
||||||
}
|
}
|
||||||
if !types.define_struct(&parser.module.type_store, id, fields[:], c_layout, false) {
|
if !types.define_record(&parser.module.type_store, id, fields[:], c_layout, false, is_union) {
|
||||||
source.addf(parser.diagnostics, name.span, "duplicate type declaration '%s'", token_text(parser, name))
|
source.addf(parser.diagnostics, name.span, "duplicate type declaration '%s'", token_text(parser, name))
|
||||||
}
|
}
|
||||||
_ = finish_statement(parser)
|
_ = finish_statement(parser)
|
||||||
@@ -2054,6 +2057,10 @@ parse_top_level :: proc(parser: ^Parser) {
|
|||||||
parse_struct(parser, name, current(parser).kind == .Keyword_C_Struct)
|
parse_struct(parser, name, current(parser).kind == .Keyword_C_Struct)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if operator.kind == .Colon_Colon && current(parser).kind == .Keyword_Union {
|
||||||
|
parse_struct(parser, name, false, is_union=true)
|
||||||
|
return
|
||||||
|
}
|
||||||
if operator.kind == .Colon_Colon && current(parser).kind == .Keyword_Enum {
|
if operator.kind == .Colon_Colon && current(parser).kind == .Keyword_Enum {
|
||||||
parse_enum(parser, name)
|
parse_enum(parser, name)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ Kind :: enum u8 {
|
|||||||
Keyword_C_Func,
|
Keyword_C_Func,
|
||||||
Keyword_Struct,
|
Keyword_Struct,
|
||||||
Keyword_C_Struct,
|
Keyword_C_Struct,
|
||||||
|
Keyword_Union,
|
||||||
Keyword_Enum,
|
Keyword_Enum,
|
||||||
Keyword_Distinct,
|
Keyword_Distinct,
|
||||||
Keyword_Alias,
|
Keyword_Alias,
|
||||||
|
|||||||
@@ -2139,6 +2139,19 @@ yield_compiles_and_runs :: proc(t: ^testing.T) {
|
|||||||
testing.expect_value(t, state.exit_code, 42)
|
testing.expect_value(t, state.exit_code, 42)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
native_union_compiles_and_runs :: proc(t: ^testing.T) {
|
||||||
|
output := "/tmp/brolang-test-unions"
|
||||||
|
defer _ = os.remove(output)
|
||||||
|
status := compiler_core.compile_package("examples/programs/unions", output)
|
||||||
|
testing.expect_value(t, status, 0)
|
||||||
|
state := run_executable(output)
|
||||||
|
// A native untagged union (`Val :: union { n i32, f f64 }`) constructed via a keyed
|
||||||
|
// literal, stored into a local, and read back through field access reinterprets the
|
||||||
|
// carrier and yields 42 (declaration → construct → store → load → field read).
|
||||||
|
testing.expect_value(t, state.exit_code, 42)
|
||||||
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
yield_misuse_is_diagnosed :: proc(t: ^testing.T) {
|
yield_misuse_is_diagnosed :: proc(t: ^testing.T) {
|
||||||
// A value block that does not end in `yield`, and a `yield` nested inside an
|
// A value block that does not end in `yield`, and a `yield` nested inside an
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
Val :: union {
|
||||||
|
n i32
|
||||||
|
f f64
|
||||||
|
}
|
||||||
|
|
||||||
|
main :: func() i32 {
|
||||||
|
x Val = Val{ n = 42 }
|
||||||
|
return x.n
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user