refine returned match errors

This commit is contained in:
2026-07-22 02:04:48 +02:00
parent 2c2a310e6d
commit 8e153fa84e
9 changed files with 586 additions and 16 deletions
+186
View File
@@ -6349,6 +6349,192 @@ main func() i32 {
testing.expect_value(t, state.exit_code, 0)
}
@(test)
matched_error_residuals_return_at_runtime_and_comptime :: proc(t: ^testing.T) {
text := `KeyError :: enum { key_exists }
AllocError :: enum { out_of_memory }
AError :: enum { a }
BError :: enum { b }
CError :: enum { c }
DetailError :: union(enum) { out_of_memory i32 }
key_or_alloc func(code i32) void ! (KeyError | AllocError) {
if code == 1 { return .key_exists }
if code == 2 { return .out_of_memory }
}
abc func(code i32) void ! (AError | BError | CError) {
if code == 1 { return .a }
if code == 2 { return .b }
if code == 3 { return .c }
}
detail func() void ! (KeyError | DetailError) { return .out_of_memory{41} }
via_else func() i32 ! AllocError {
key_or_alloc(2) catch |err| {
match err {
.key_exists: unreachable
else: return err
}
}
return 0
}
via_group func() i32 ! (AError | BError) {
abc(2) catch |err| {
match err {
.a, .b: return err
.c: unreachable
}
}
return 0
}
via_nested func() i32 ! AError {
abc(1) catch |err| {
match err {
.a, .b: match err {
.a: return err
else: unreachable
}
.c: unreachable
}
}
return 0
}
via_expand func() i32 ! AError {
abc(1) catch |err| {
match err {
expand |tag|: match tag {
.a: return err
else: unreachable
}
}
}
return 0
}
via_payload func() i32 ! DetailError {
detail() catch |err| {
match err {
.key_exists: unreachable
else: return err
}
}
return 0
}
ct_project func() i32 ! AError {
abc(1) catch |err| {
match err {
.a: return err
else: unreachable
}
}
return 0
}
ct_recover func() i32 {
return ct_project() catch |_| 5
}
main func() i32 {
a :: via_else() catch |_| 1
b :: via_group() catch |err| match err {
.a: 1
.b: 2
}
c :: via_nested() catch |_| 3
d :: via_expand() catch |_| 4
e :: via_payload() catch |err| match err { .out_of_memory |code|: code }
f i32 :: $ct_recover()
return a + b + c + d + e + f - 56
}
`
directory := "/tmp/brolang-test-matched-error-residuals"
main_path := "/tmp/brolang-test-matched-error-residuals/main.bro"
output := "/tmp/brolang-test-matched-error-residuals-output"
_ = os2.remove_all(directory)
defer _ = os2.remove_all(directory)
defer _ = os.remove(output)
testing.expect(t, os.make_directory(directory) == nil)
testing.expect(t, os.write_entire_file(main_path, transmute([]byte)text))
testing.expect_value(t, compiler_core.compile_package(directory, output), 0)
state := run_executable(output)
testing.expect_value(t, state.exit_code, 0)
}
@(test)
unproven_error_returns_are_rejected_against_both_channels :: proc(t: ^testing.T) {
text := `BadError :: enum { bad }
GoodError :: enum { good }
Combined :: alias (BadError | GoodError)
source func() void ! Combined { return .good }
unrefined func() usize ! GoodError {
source() catch |err| { return err }
return 0
}
incompatible func() usize ! GoodError {
source() catch |err| {
match err {
.bad: return err
else: unreachable
}
}
return 0
}
aliased func() usize ! GoodError {
source() catch |err| {
match err {
.bad: unreachable
else: {
copy :: err
return copy
}
}
}
return 0
}
mutable_subject func() usize ! GoodError {
source() catch |err| {
value Combined = err
match value {
.bad: unreachable
else: return value
}
}
return 0
}
main func() void {
_ = unrefined() catch 0
_ = incompatible() catch 0
_ = aliased() catch 0
_ = mutable_subject() catch 0
}
`
directory := "/tmp/brolang-test-unproven-error-returns"
main_path := "/tmp/brolang-test-unproven-error-returns/main.bro"
_ = os2.remove_all(directory)
defer _ = os2.remove_all(directory)
testing.expect(t, os.make_directory(directory) == nil)
testing.expect(t, os.write_entire_file(main_path, transmute([]byte)text))
sources := source.init_store()
defer source.destroy_store(&sources)
diagnostics := source.init_store_diagnostics(&sources)
defer source.destroy_diagnostics(&diagnostics)
symbols := symbol.init_table()
defer symbol.destroy_table(&symbols)
ast_module, loaded := loader.load(directory, &sources, &diagnostics, &symbols)
defer ast.destroy_module(&ast_module)
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
defer hir.destroy_module(&hir_module)
testing.expect(t, loaded)
return_errors := 0
for _, diagnostic_index in diagnostics.items {
message := source.format(&diagnostics, source.diagnostic_id(diagnostic_index))
if strings.contains(message, "cannot return") &&
strings.contains(message, "as success type usize or error type GoodError") {
return_errors += 1
}
delete(message)
}
testing.expect_value(t, return_errors, 4)
}
@(test)
conversion_diagnostics_render_source_types :: proc(t: ^testing.T) {
text := `Allocator :: struct {