contextual payload construction

This commit is contained in:
2026-06-30 21:14:59 +02:00
parent 7ca7e33033
commit 5161e99af5
5 changed files with 450 additions and 85 deletions
+175
View File
@@ -2514,6 +2514,181 @@ main func() i32 {
testing.expect_value(t, compiler_core.compile_package(directory, output), 1)
}
@(test)
contextual_payload_variants_compile :: proc(t: ^testing.T) {
text := `DetailError :: union(enum) {
code i32
empty void
}
make_payload func() i32 {
return 7
}
accept func(value DetailError) i32 {
match value {
.code |n|: return n
.empty: return 0
}
}
make_code func(value i32) DetailError {
return .code{value}
}
with_detail func(value i32) i32 ! DetailError {
if (value == 0) return .code{5}
if (value == 1) return .empty
return value
}
main func() i32 {
e DetailError = .code{3}
recovered :: with_detail(0) catch |err| {
match err {
.code |n|: yield n
.empty: yield 9
}
}
return accept(e) + accept(.code{4}) + accept(make_code(5)) + accept(.code{make_payload()}) + recovered - 24
}
`
source_file := source.Source{path="contextual_payload.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"))
}
@(test)
inline_fallible_error_types_compile :: proc(t: ^testing.T) {
text := `inline_enum func(value i32) i32 ! enum {
inline_bad
inline_worse
} {
if (value == 0) return .inline_bad
if (value < 0) return .inline_worse
return value
}
inline_union func(value i32) i32 ! union(enum) {
inline_code i32
inline_empty void
} {
if (value == 0) return .inline_code{6}
if (value == 1) return .inline_empty
return value
}
main func() i32 {
a :: inline_enum(0) catch |e| {
if (e == .inline_bad) {
yield 10
} else {
yield 11
}
}
b :: inline_union(0) catch |e| {
match e {
.inline_code |n|: yield n
.inline_empty: yield 12
}
}
return a + b - 16
}
`
source_file := source.Source{path="inline_errors.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, len(hir_module.functions) > 1)
testing.expect(t, len(llvm_text) > 0)
}
@(test)
contextual_payload_variants_reject_bad_forms :: proc(t: ^testing.T) {
text := `DetailError :: union(enum) {
code i32
empty void
}
missing_payload func() DetailError {
return .code
}
void_payload func() DetailError {
return .empty{1}
}
empty_payload func() DetailError {
return .code{}
}
multi_payload func() DetailError {
return .code{1, 2}
}
unknown_payload func() DetailError {
return .missing{1}
}
no_context func() i32 {
_ = .code{1}
return 0
}
main func() i32 {
_ = missing_payload()
_ = void_payload()
_ = empty_payload()
_ = multi_payload()
_ = unknown_payload()
return no_context()
}
`
source_file := source.Source{path="bad_contextual_payload.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_missing := false
found_void := false
payload_arity_errors := 0
found_unknown := false
found_no_context := false
for diagnostic in diagnostics.items {
found_missing = found_missing || strings.contains(diagnostic.message, "needs a payload")
found_void = found_void || strings.contains(diagnostic.message, "void variant 'empty' takes no value")
payload_arity_errors += 1 if strings.contains(diagnostic.message, "requires exactly one expression") else 0
found_unknown = found_unknown || strings.contains(diagnostic.message, "unknown variant '.missing'")
found_no_context = found_no_context || strings.contains(diagnostic.message, "requires a tagged-union context")
}
testing.expect(t, found_missing)
testing.expect(t, found_void)
testing.expect(t, payload_arity_errors >= 2)
testing.expect(t, found_unknown)
testing.expect(t, found_no_context)
}
@(test)
errors_example_compiles_and_runs :: proc(t: ^testing.T) {
output := "/tmp/brolang-test-errors"