tagged unions

This commit is contained in:
2026-06-28 22:54:20 +02:00
parent f328c44154
commit 981ccb047a
7 changed files with 396 additions and 14 deletions
+99
View File
@@ -2152,6 +2152,105 @@ native_union_compiles_and_runs :: proc(t: ^testing.T) {
testing.expect_value(t, state.exit_code, 42)
}
@(test)
tagged_union_compiles_and_runs :: proc(t: ^testing.T) {
output := "/tmp/brolang-test-tagged-union"
defer _ = os.remove(output)
status := compiler_core.compile_package("examples/programs/tagged_union", output)
testing.expect_value(t, status, 0)
state := run_executable(output)
// Both tagged forms — `union(Animal)` (existing enum tag) and `union(enum)` (synthesized
// tag) — constructed via keyed literals, stored as `{tag, payload}`, with the active
// payload read back at its post-tag offset: 37 + 5 = 42. Non-zero payloads make a wrong
// payload offset (e.g. overlapping the tag) fail the exit code.
testing.expect_value(t, state.exit_code, 42)
}
@(test)
tagged_union_stores_the_discriminant :: proc(t: ^testing.T) {
// The runtime test observes only the payload; this one checks the *tag* is written.
// `Animal` is unbacked/dense (dog=0, cat=1, bird=2) in a u8 backing, so `Data{ bird = 99 }`
// lays out as `{ i8, [3 x i8], i32 }` (tag at 0, i32 payload at offset 4) and writes the
// discriminant `store i8 2` beside the payload `store i32 99`.
text := `Animal :: enum {
dog
cat
bird
}
Data :: union(Animal) {
dog i32
bird i32
}
main :: func() i32 {
x Data = Data{ bird = 99 }
return x.bird
}
`
source_file := source.Source{path="test.bro", text=text}
diagnostics := source.init_diagnostics(&source_file)
defer source.destroy_diagnostics(&diagnostics)
symbols := symbol.init_table()
defer symbol.destroy_table(&symbols)
stream := lexer.lex(&source_file, &diagnostics, &symbols)
defer delete(stream.items)
ast_module := parser.parse(&stream, &source_file, &diagnostics)
defer ast.destroy_module(&ast_module)
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
defer hir.destroy_module(&hir_module)
ir_module := lower.lower(&hir_module)
defer ir.destroy_module(&ir_module)
llvm_text := llvm.emit(&ir_module, &diagnostics, &symbols)
defer delete(llvm_text)
testing.expect_value(t, len(diagnostics.items), 0)
testing.expect(t, strings.contains(llvm_text, "{ i8, [3 x i8], i32 }"))
testing.expect(t, strings.contains(llvm_text, "store i8 2,"))
testing.expect(t, strings.contains(llvm_text, "store i32 99,"))
}
@(test)
tagged_union_validation_is_diagnosed :: proc(t: ^testing.T) {
// A `union(T)` tag must be an enum, and every variant of a `union(Enum)` must name a
// member of that enum.
text := `Color :: struct {
r u8
}
Animal :: enum {
dog
cat
}
BadTag :: union(Color) {
dog i32
}
BadVariant :: union(Animal) {
snake i32
}
main :: func() i32 {
return 0
}
`
source_file := source.Source{path="test.bro", text=text}
diagnostics := source.init_diagnostics(&source_file)
defer source.destroy_diagnostics(&diagnostics)
symbols := symbol.init_table()
defer symbol.destroy_table(&symbols)
stream := lexer.lex(&source_file, &diagnostics, &symbols)
defer delete(stream.items)
ast_module := parser.parse(&stream, &source_file, &diagnostics)
defer ast.destroy_module(&ast_module)
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
defer hir.destroy_module(&hir_module)
found_tag := false
found_variant := false
for diagnostic in diagnostics.items {
found_tag = found_tag || strings.contains(diagnostic.message, "tagged union's tag must be an enum")
found_variant = found_variant || strings.contains(diagnostic.message, "'snake' is not a member of the tag enum")
}
testing.expect(t, found_tag)
testing.expect(t, found_variant)
}
@(test)
yield_misuse_is_diagnosed :: proc(t: ^testing.T) {
// A value block that does not end in `yield`, and a `yield` nested inside an