error handling kickoff

This commit is contained in:
2026-06-29 22:55:56 +02:00
parent 98c303d22c
commit ae5af37b85
14 changed files with 1229 additions and 173 deletions
+162 -19
View File
@@ -90,7 +90,7 @@ main :: func() void { _ = value }
compact_ids_reserve_invalid_values_and_preserve_layout :: proc(t: ^testing.T) {
testing.expect_value(t, size_of(source.Span), 12)
testing.expect_value(t, size_of(token.Token), 24)
testing.expect(t, size_of(ast.Expr) <= 64)
testing.expect(t, size_of(ast.Expr) <= 88)
testing.expect(t, size_of(hir.Expr) <= 88)
testing.expect(t, size_of(ir.Instruction) <= 88)
testing.expect_value(t, size_of(types.Type), 4)
@@ -2169,9 +2169,9 @@ tagged_union_compiles_and_runs :: proc(t: ^testing.T) {
@(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`.
// Native sum tags are global u16 IDs. `Animal` contributes dog/cat/bird (1..3),
// then `Data` contributes dog:i32/bird:i32 (4..5), so `Data{ bird = 99 }`
// writes discriminant `store i16 5` beside the payload.
text := `Animal :: enum {
dog
cat
@@ -2203,8 +2203,8 @@ main :: func() i32 {
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, "{ i16, [2 x i8], i32 }"))
testing.expect(t, strings.contains(llvm_text, "store i16 5,"))
testing.expect(t, strings.contains(llvm_text, "store i32 99,"))
}
@@ -2251,6 +2251,151 @@ main :: func() i32 {
testing.expect(t, found_variant)
}
@(test)
sum_composition_merges_widens_and_rejects_conflicts :: proc(t: ^testing.T) {
text := `A :: enum {
same
left
}
B :: enum {
same
right
}
Both :: alias A | B
UA :: union(enum) {
item i32
}
UB :: union(enum) {
empty void
}
UBoth :: alias UA | UB
pick :: func(value Both) i32 {
match value {
.same: return 1
.left: return 2
.right: return 3
}
}
payload :: func(value UBoth) i32 {
match value {
.item |n|: return n
.empty: return 5
}
}
main :: func() i32 {
a A = .left
b B = .right
u UA = UA{ item = 4 }
v UB = .empty
return pick(.same) + pick(a) + pick(b) + payload(u) + payload(v)
}
`
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, "call void @llvm.memcpy.p0.p0.i64"))
conflict := `A :: union(enum) {
dup i32
}
B :: union(enum) {
dup i64
}
Bad :: alias A | B
main :: func() void {}
`
conflict_source := source.Source{path="conflict.bro", text=conflict}
conflict_diagnostics := source.init_diagnostics(&conflict_source)
defer source.destroy_diagnostics(&conflict_diagnostics)
conflict_symbols := symbol.init_table()
defer symbol.destroy_table(&conflict_symbols)
conflict_stream := lexer.lex(&conflict_source, &conflict_diagnostics, &conflict_symbols)
defer delete(conflict_stream.items)
conflict_module := parser.parse(&conflict_stream, &conflict_source, &conflict_diagnostics)
defer ast.destroy_module(&conflict_module)
found_conflict := false
for diagnostic in conflict_diagnostics.items {
found_conflict = found_conflict || strings.contains(diagnostic.message, "same variant name")
}
testing.expect(t, found_conflict)
backed := `A :: enum(u8) {
a = 1
}
B :: enum {
b
}
Bad :: alias A | B
main :: func() void {}
`
backed_source := source.Source{path="backed.bro", text=backed}
backed_diagnostics := source.init_diagnostics(&backed_source)
defer source.destroy_diagnostics(&backed_diagnostics)
backed_symbols := symbol.init_table()
defer symbol.destroy_table(&backed_symbols)
backed_stream := lexer.lex(&backed_source, &backed_diagnostics, &backed_symbols)
defer delete(backed_stream.items)
backed_module := parser.parse(&backed_stream, &backed_source, &backed_diagnostics)
defer ast.destroy_module(&backed_module)
found_backed := false
for diagnostic in backed_diagnostics.items {
found_backed = found_backed || strings.contains(diagnostic.message, "native unbacked")
}
testing.expect(t, found_backed)
}
@(test)
yield_inference_visits_yielded_calls :: proc(t: ^testing.T) {
text := `identity :: func(value int) int {
return value
}
main :: func() i32 {
value :: {
yield identity(41)
}
return value - 41
}
`
source_file := source.Source{path="yield_call.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)
testing.expect_value(t, len(diagnostics.items), 0)
testing.expect(t, len(hir_module.functions) > 1)
}
@(test)
errors_example_compiles_and_runs :: proc(t: ^testing.T) {
output := "/tmp/brolang-test-errors"
defer _ = os.remove(output)
status := compiler_core.compile_package("examples/programs/errors", output)
testing.expect_value(t, status, 0)
state := run_executable(output)
testing.expect_value(t, state.exit_code, 0)
}
@(test)
match_compiles_and_runs :: proc(t: ^testing.T) {
output := "/tmp/brolang-test-match"
@@ -2268,8 +2413,7 @@ match_compiles_and_runs :: proc(t: ^testing.T) {
match_dispatches_on_the_tag :: proc(t: ^testing.T) {
// A tagged-union match desugars to: read the discriminant once (a load of the tag
// enum at the union's offset 0), then compare it against each variant's tag value.
// `Animal` is dense in a u8 backing (dog=0, bird=2), so the dog arm compares the
// loaded i8 tag against 0.
// Data's runtime tag is the hidden global u16 variant ID.
text := `Animal :: enum {
dog
cat
@@ -2306,9 +2450,8 @@ main :: func() i32 {
defer delete(llvm_text)
testing.expect_value(t, len(diagnostics.items), 0)
// The tag is loaded as an i8 and compared (icmp eq) against the dog variant's tag (0).
testing.expect(t, strings.contains(llvm_text, "load i8"))
testing.expect(t, strings.contains(llvm_text, "icmp eq i8"))
testing.expect(t, strings.contains(llvm_text, "load i16"))
testing.expect(t, strings.contains(llvm_text, "icmp eq i16"))
}
@(test)
@@ -7038,21 +7181,21 @@ main :: func() i32 {
testing.expect_value(t, len(diagnostics.items), 0)
testing.expect(t, animal_ok && nat_ok)
testing.expect(t, types.is_enum(animal, &ast_module.type_store))
testing.expect_value(t, animal_node.child, types.U8)
testing.expect_value(t, animal_node.child, types.U16)
testing.expect(t, !animal_node.explicit_backing)
testing.expect_value(t, nat_node.child, types.U16)
testing.expect(t, nat_node.explicit_backing)
testing.expect_value(t, len(animal_members), 3)
testing.expect_value(t, animal_members[0].value, i128(0))
testing.expect_value(t, animal_members[2].value, i128(2))
testing.expect_value(t, animal_members[0].value, i128(1))
testing.expect_value(t, animal_members[2].value, i128(3))
testing.expect_value(t, nat_members[0].value, i128(1))
testing.expect_value(t, nat_members[1].value, i128(2))
testing.expect_value(t, nat_members[2].value, i128(5))
testing.expect_value(t, types.runtime_representation(animal, &ast_module.type_store), types.U8)
testing.expect_value(t, types.size(animal, &ast_module.type_store), u64(1))
testing.expect_value(t, types.runtime_representation(animal, &ast_module.type_store), types.U16)
testing.expect_value(t, types.size(animal, &ast_module.type_store), u64(2))
testing.expect(t, hir_module.globals[0].is_static)
testing.expect_value(t, hir_module.globals[0].static_value, i64(0))
testing.expect(t, strings.contains(llvm_text, "@bro.g.0 = internal constant i8 0"))
testing.expect_value(t, hir_module.globals[0].static_value, i64(1))
testing.expect(t, strings.contains(llvm_text, "@bro.g.0 = internal constant i16 1"))
found_promotion := false
for function in ir_module.functions {
for instruction in function.instructions {
@@ -7063,7 +7206,7 @@ main :: func() i32 {
}
@(test)
unbacked_enum_selects_the_smallest_fitting_unsigned_backing :: proc(t: ^testing.T) {
unbacked_enum_uses_global_u16_backing :: proc(t: ^testing.T) {
builder := strings.builder_make()
defer strings.builder_destroy(&builder)
strings.write_string(&builder, "Large :: enum {\n")