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
+34 -9
View File
@@ -584,12 +584,18 @@
- focused coverage lives in `examples/programs/errors` and the fallible ergonomics compiler test
- leave ABI/layout/lint/design polish for later milestones
23.6. contextual payload construction + inline error types
- implement contextual payload `.variant{...}` construction
- allow named fallible signatures to use inline enum / tagged-union error types
- settle qualified same-name disambiguation only if contextual construction needs it
23.6. contextual payload construction + inline error types (implemented)
- contextual payload `.variant{expr}` construction now works in tagged-union contexts
(assignment, return, calls, and fallible error dispatch); bare `.variant` remains the
spelling for void-payload variants
- named fallible signatures can use inline unbacked enum and `union(enum)` error types
after `!`
- qualified same-name disambiguation remains deferred; existing ambiguous fallible return
diagnostics cover the current surface
23.7. sum-type ABI/layout polish
23.7. anonymous struct payloads and keyed payload sugar
23.8. sum-type ABI/layout polish
- consider dynamic tag-width shrinking after the fixed-`u16` ABI has real pressure
- consider all-void channel collapse after fallible channels are otherwise stable
- define cross-module/global-id ABI determinism before multi-module builds depend on it
@@ -976,7 +982,7 @@ message :: match code {
Brolang handles errors as values. There is no hidden control flow — a function that can fail declares this in its signature, and the caller must explicitly handle the possibility of failure.
Milestone 23 v1 implements named error channels, native sum composition, `return`-based error dispatch, exact-channel `try`, and fallback `catch`. Milestone 23.5 adds `catch |e|` blocks and `try` widening across composable error channels. It still defers the `error` keyword shorthand, inline error types, contextual payload construction, and match-on-error shorthand.
Milestone 23 v1 implements named error channels, native sum composition, `return`-based error dispatch, exact-channel `try`, and fallback `catch`. Milestone 23.5 adds `catch |e|` blocks and `try` widening across composable error channels. Milestone 23.6 adds contextual payload construction and inline error types. It still defers the `error` keyword shorthand, anonymous struct payload sugar, and match-on-error shorthand.
### Fallible Functions
@@ -1048,7 +1054,25 @@ ProcessError :: alias (IoError | ParseError)
### Inline Error Types
Inline error types are planned, but not part of milestone 23 v1. Use named enums, named tagged unions, or named aliases for now.
Fallible signatures can define small private error channels inline:
```
read_count func(path []u8) i32 ! union(enum) {
not_found PathErrorInfo
timeout_ms u64
} { ... }
```
Inline enums are also supported:
```
parse_flag func(text []u8) bool ! enum {
empty
invalid
} { ... }
```
Use a named error type when the channel is shared or needs stable public identity.
### Returning Errors
@@ -1061,7 +1085,7 @@ parse_section func(p: @mut Parser) void ! ParseError {
# ... parsing logic ...
if p.pos >= p.input.len or p.input[p.pos] != ']' return ParseError{ unclosed_section = start_line }
if p.pos >= p.input.len or p.input[p.pos] != ']' return .unclosed_section{start_line}
# ... continue on success ...
}
@@ -1071,7 +1095,7 @@ Since errors are just union values, you can also construct them separately:
```
# Construct error value (it's just a union)
e ParseError = ParseError{ timeout = 500 }
e ParseError = .timeout{500}
# Return it via error channel later
return e
@@ -1157,6 +1181,7 @@ data :: read_file(path) catch |e| match e {
| `T ! (E1 | E2)` | Planned direct spelling with parentheses; use a named alias in v1 |
| `return e` | Exit function via error channel when `e : E` |
| `error e` | Planned shorthand, not v1 |
| `.variant{payload}` | Construct a payload-carrying tagged-union variant from context |
| `error .variant{...}` | Planned shorthand, not v1 |
| `try expr` | Unwrap success or propagate an exact/sum-widenable error channel |
| `expr catch fallback` | Provide fallback value on error |
+31 -12
View File
@@ -758,7 +758,7 @@ mark_expr_imports_used :: proc(checker: ^Checker, expr_id: ast.Expr_Id, file: as
if expr.left != ast.INVALID_EXPR {
append(&stack, expr.left)
}
case .Negate, .Not, .Address, .Deref, .Field, .Unwrap, .Try, .Keyed:
case .Negate, .Not, .Address, .Deref, .Field, .Unwrap, .Try, .Keyed, .Enum_Literal:
append(&stack, expr.left)
case .Catch:
append(&stack, expr.left)
@@ -768,7 +768,7 @@ mark_expr_imports_used :: proc(checker: ^Checker, expr_id: ast.Expr_Id, file: as
mark_block_imports_used(checker, expr.body, file)
case .Add, .Sub, .Mul, .Div, .Index, .Orelse, .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or, .Range:
append(&stack, expr.left, expr.right)
case .Invalid, .Integer, .Float, .String, .Bool, .None, .Undefined, .Name, .Enum_Literal:
case .Invalid, .Integer, .Float, .String, .Bool, .None, .Undefined, .Name:
}
}
}
@@ -1331,6 +1331,9 @@ infer_compound_expr :: proc(
case .Undefined:
return types.INVALID
case .Enum_Literal:
if expr.left != ast.INVALID_EXPR {
_ = infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types)
}
return types.INVALID
case .Address:
child := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types)
@@ -3256,29 +3259,45 @@ build_compound_expr :: proc(
)
return invalid_hir_expr(checker, expr.span, id, expected)
case .Enum_Literal:
// A bare enum literal in a tagged-union context constructs a variant. Only a
// void-payload variant can be built this way (it has no value); a payload variant
// must use `T{ variant = ... }`. (`.variant{...}` payload construction is the
// milestone-23 error-channel form.)
if types.is_tagged_union(expected, store) {
index, field, found := find_struct_field(checker, expected, expr.name)
if !found {
id := source.addf(checker.diagnostics, expr.span, "unknown variant '.%s' on '%s'", symbol_text(checker, expr.name), type_label(checker, expected))
return invalid_hir_expr(checker, expr.span, id, expected)
}
if !types.is_void(field.type) {
id := source.addf(checker.diagnostics, expr.span, "variant '.%s' on '%s' needs a payload; only void variants can be built from a bare '.%s'",
symbol_text(checker, expr.name), type_label(checker, expected), symbol_text(checker, expr.name))
return invalid_hir_expr(checker, expr.span, id, expected)
}
values := make([]hir.Expr_Id, 1, checker.allocator)
values[0] = hir.INVALID_EXPR
if expr.left == ast.INVALID_EXPR {
if !types.is_void(field.type) {
id := source.addf(checker.diagnostics, expr.span, "variant '.%s' on '%s' needs a payload; only void variants can be built from a bare '.%s'",
symbol_text(checker, expr.name), type_label(checker, expected), symbol_text(checker, expr.name))
delete(values, checker.allocator)
return invalid_hir_expr(checker, expr.span, id, expected)
}
values[0] = hir.INVALID_EXPR
} else {
if types.is_void(field.type) {
id := source.addf(checker.diagnostics, expr.span, "void variant '%s' takes no value", symbol_text(checker, expr.name))
delete(values, checker.allocator)
return invalid_hir_expr(checker, expr.span, id, expected)
}
values[0] = build_nested_expr(checker, expr.left, locals, global_reads, calls, field.type, pkg, file)
values[0] = coerce_expr(checker, values[0], field.type, checker.module.exprs[values[0]].span)
}
return add_hir_expr(checker, hir.Expr{
kind=.Struct, span=expr.span, type=expected, args=values, integer=i64(index),
target=hir.INVALID_REF, left=hir.INVALID_EXPR, right=hir.INVALID_EXPR,
diagnostic=source.INVALID_DIAGNOSTIC,
})
}
if expr.left != ast.INVALID_EXPR {
id := source.addf(
checker.diagnostics,
expr.span,
"'.%s{...}' requires a tagged-union context",
symbol_text(checker, expr.name),
)
return invalid_hir_expr(checker, expr.span, id, expected)
}
if !types.is_enum(expected, store) {
id := source.addf(
checker.diagnostics,
+173 -59
View File
@@ -366,23 +366,40 @@ parse_type_atom :: proc(parser: ^Parser) -> ast.Type_Syntax {
return types.INVALID
}
parse_type :: proc(parser: ^Parser) -> ast.Type_Syntax {
left := parse_type_atom(parser)
parse_type_pipe_tail :: proc(parser: ^Parser, left: ast.Type_Syntax) -> ast.Type_Syntax {
result := left
for current(parser).kind == .Pipe {
operator := advance(parser)
right := parse_type_atom(parser)
composed, compose_error := types.compose_sum(&parser.module.type_store, left, right)
composed, compose_error := types.compose_sum(&parser.module.type_store, result, right)
if compose_error == .Unsupported {
source.add(parser.diagnostics, operator.span, "only native unbacked enums and tagged unions can be composed with '|'")
left = types.INVALID
result = types.INVALID
} else if compose_error == .Conflict {
source.add(parser.diagnostics, operator.span, "sum composition contains the same variant name with different payload types")
left = types.INVALID
result = types.INVALID
} else {
left = composed
result = composed
}
}
return left
return result
}
parse_type :: proc(parser: ^Parser) -> ast.Type_Syntax {
return parse_type_pipe_tail(parser, parse_type_atom(parser))
}
parse_error_type :: proc(parser: ^Parser) -> ast.Type_Syntax {
left := types.INVALID
#partial switch current(parser).kind {
case .Keyword_Enum:
left = parse_inline_enum_type(parser)
case .Keyword_Union:
left = parse_inline_union_type(parser)
case:
left = parse_type_atom(parser)
}
return parse_type_pipe_tail(parser, left)
}
skip_parenthesized :: proc(parser: ^Parser) -> source.Span {
@@ -650,11 +667,36 @@ parse_primary :: proc(parser: ^Parser, nesting: int) -> ast.Expr_Id {
return invalid_expr(parser, member.span, "expected an enum member after '.'")
}
advance(parser)
payload := ast.INVALID_EXPR
end := member.span
if _, ok := allow(parser, .Left_Brace); ok {
parser.delimiter_depth += 1
skip_newlines(parser)
if current(parser).kind == .Right_Brace {
source.add(parser.diagnostics, current(parser).span, "contextual variant payload requires exactly one expression")
} else {
payload = parse_expression_bp(parser, 0, nesting+1)
skip_newlines(parser)
if _, comma_ok := allow(parser, .Comma); comma_ok {
source.add(parser.diagnostics, current(parser).span, "contextual variant payload requires exactly one expression")
for current(parser).kind != .Right_Brace && current(parser).kind != .Eof {
advance(parser)
}
}
}
right_brace, close_ok := allow(parser, .Right_Brace)
if !close_ok {
source.add(parser.diagnostics, current(parser).span, "expected '}' after contextual variant payload")
right_brace = member
}
parser.delimiter_depth -= 1
end = right_brace.span
}
return add_expr(parser, ast.Expr{
kind=.Enum_Literal,
span=span_from(start.span, member.span),
span=span_from(start.span, end),
name=member.symbol,
left=ast.INVALID_EXPR,
left=payload,
right=ast.INVALID_EXPR,
diagnostic=source.INVALID_DIAGNOSTIC,
})
@@ -1871,7 +1913,7 @@ parse_function :: proc(parser: ^Parser, name: token.Token, c_abi: bool) {
result := parse_type(parser)
error_type := types.INVALID
if _, ok := allow(parser, .Bang); ok {
error_type = parse_type(parser)
error_type = parse_error_type(parser)
}
end := previous(parser)
ended_by_newline := current(parser).kind == .Newline
@@ -1917,6 +1959,67 @@ parse_function :: proc(parser: ^Parser, name: token.Token, c_abi: bool) {
})
}
parse_record_body :: proc(parser: ^Parser, fields: ^[dynamic]types.Field, expected_open: string) -> bool {
if _, ok := allow(parser, .Left_Brace); !ok {
source.add(parser.diagnostics, current(parser).span, expected_open)
return false
}
skip_newlines(parser)
for current(parser).kind != .Right_Brace && current(parser).kind != .Eof {
if current(parser).kind != .Identifier {
source.add(parser.diagnostics, current(parser).span, "expected a struct field name")
for current(parser).kind != .Newline &&
current(parser).kind != .Right_Brace &&
current(parser).kind != .Eof {
advance(parser)
}
skip_newlines(parser)
continue
}
field_name := advance(parser)
field_type := parse_type(parser)
append(fields, types.Field{name=u32(field_name.symbol), type=field_type})
if _, ok := allow(parser, .Comma); ok {
skip_newlines(parser)
continue
}
_ = finish_statement(parser, true)
}
if _, ok := allow(parser, .Right_Brace); !ok {
source.add(parser.diagnostics, current(parser).span, "expected '}' after struct fields")
}
return true
}
parse_inline_union_type :: proc(parser: ^Parser) -> types.Type {
advance(parser)
valid := true
if _, ok := allow(parser, .Left_Paren); !ok {
source.add(parser.diagnostics, current(parser).span, "expected '(enum)' after inline union error type")
valid = false
} else {
if _, enum_ok := allow(parser, .Keyword_Enum); !enum_ok {
source.add(parser.diagnostics, current(parser).span, "inline union error types must use 'union(enum)'")
if current(parser).kind != .Right_Paren {
_ = parse_type(parser)
}
valid = false
}
if _, close_ok := allow(parser, .Right_Paren); !close_ok {
source.add(parser.diagnostics, current(parser).span, "expected ')' after union tag")
valid = false
}
}
fields: [dynamic]types.Field
fields.allocator = parser.module.allocator
defer delete(fields)
if !parse_record_body(parser, &fields, "expected '{' after inline union error type") || !valid {
return types.INVALID
}
tag := synthesize_union_tag(parser, fields[:])
return types.union_anonymous(&parser.module.type_store, fields[:], tag)
}
parse_struct :: proc(parser: ^Parser, name: token.Token, c_layout: bool, is_union := false) {
start := advance(parser)
id := types.named(&parser.module.type_store, u32(parser.pkg), u32(name.symbol))
@@ -1954,34 +2057,10 @@ parse_struct :: proc(parser: ^Parser, name: token.Token, c_layout: bool, is_unio
}
return
}
advance(parser)
fields: [dynamic]types.Field
fields.allocator = parser.module.allocator
defer delete(fields)
skip_newlines(parser)
for current(parser).kind != .Right_Brace && current(parser).kind != .Eof {
if current(parser).kind != .Identifier {
source.add(parser.diagnostics, current(parser).span, "expected a struct field name")
for current(parser).kind != .Newline &&
current(parser).kind != .Right_Brace &&
current(parser).kind != .Eof {
advance(parser)
}
skip_newlines(parser)
continue
}
field_name := advance(parser)
field_type := parse_type(parser)
append(&fields, types.Field{name=u32(field_name.symbol), type=field_type})
if _, ok := allow(parser, .Comma); ok {
skip_newlines(parser)
continue
}
_ = finish_statement(parser, true)
}
if _, ok := allow(parser, .Right_Brace); !ok {
source.add(parser.diagnostics, current(parser).span, "expected '}' after struct fields")
}
_ = parse_record_body(parser, &fields, "expected '{' after struct fields")
if is_union && (inferred_tag || types.is_valid(declared_tag)) {
tag = synthesize_union_tag(parser, fields[:])
}
@@ -2033,26 +2112,19 @@ parse_alias :: proc(parser: ^Parser, name: token.Token) {
_ = finish_statement(parser)
}
parse_enum :: proc(parser: ^Parser, name: token.Token) {
start := advance(parser)
explicit_backing := false
backing := types.INVALID
if _, ok := allow(parser, .Left_Paren); ok {
explicit_backing = true
backing = parse_type(parser)
if _, close_ok := allow(parser, .Right_Paren); !close_ok {
source.add(parser.diagnostics, current(parser).span, "expected ')' after enum backing type")
}
}
parse_enum_body :: proc(
parser: ^Parser,
start: source.Span,
explicit_backing: bool,
backing: ^types.Type,
members: ^[dynamic]types.Enum_Member,
expected_open: string,
) -> bool {
skip_newlines(parser)
if _, ok := allow(parser, .Left_Brace); !ok {
source.add(parser.diagnostics, current(parser).span, "expected '{' after enum declaration")
_ = finish_statement(parser)
return
source.add(parser.diagnostics, current(parser).span, expected_open)
return false
}
members: [dynamic]types.Enum_Member
members.allocator = parser.module.allocator
defer delete(members)
next_value: i128
previous_value: i128
has_previous := false
@@ -2070,7 +2142,7 @@ parse_enum :: proc(parser: ^Parser, name: token.Token) {
}
member := advance(parser)
duplicate := false
for existing in members {
for existing in members^ {
if existing.name == u32(member.symbol) {
duplicate = true
break
@@ -2108,7 +2180,7 @@ parse_enum :: proc(parser: ^Parser, name: token.Token) {
source.add(parser.diagnostics, member.span, "enum values must be strictly increasing")
}
if !duplicate {
append(&members, types.Enum_Member{name=u32(member.symbol), value=value})
append(members, types.Enum_Member{name=u32(member.symbol), value=value})
}
previous_value = value
has_previous = true
@@ -2122,19 +2194,61 @@ parse_enum :: proc(parser: ^Parser, name: token.Token) {
if _, ok := allow(parser, .Right_Brace); !ok {
source.add(parser.diagnostics, current(parser).span, "expected '}' after enum members")
}
if len(members) == 0 {
source.add(parser.diagnostics, start.span, "enum declarations require at least one member")
if len(members^) == 0 {
source.add(parser.diagnostics, start, "enum declarations require at least one member")
}
if !explicit_backing {
backing = types.U16
for &member in members {
backing^ = types.U16
for &member in members^ {
id, ok := types.variant_id(&parser.module.type_store, member.name, types.VOID)
if !ok {
source.add(parser.diagnostics, start.span, "too many global sum variants for u16 tags")
source.add(parser.diagnostics, start, "too many global sum variants for u16 tags")
}
member.value = i128(id)
}
}
return true
}
parse_inline_enum_type :: proc(parser: ^Parser) -> types.Type {
start := advance(parser)
valid := true
if _, ok := allow(parser, .Left_Paren); ok {
source.add(parser.diagnostics, start.span, "inline enum error types cannot declare a backing type")
_ = parse_type(parser)
if _, close_ok := allow(parser, .Right_Paren); !close_ok {
source.add(parser.diagnostics, current(parser).span, "expected ')' after enum backing type")
}
valid = false
}
backing := types.INVALID
members: [dynamic]types.Enum_Member
members.allocator = parser.module.allocator
defer delete(members)
if !parse_enum_body(parser, start.span, false, &backing, &members, "expected '{' after inline enum error type") || !valid {
return types.INVALID
}
return types.enum_anonymous(&parser.module.type_store, members[:], backing)
}
parse_enum :: proc(parser: ^Parser, name: token.Token) {
start := advance(parser)
explicit_backing := false
backing := types.INVALID
if _, ok := allow(parser, .Left_Paren); ok {
explicit_backing = true
backing = parse_type(parser)
if _, close_ok := allow(parser, .Right_Paren); !close_ok {
source.add(parser.diagnostics, current(parser).span, "expected ')' after enum backing type")
}
}
members: [dynamic]types.Enum_Member
members.allocator = parser.module.allocator
defer delete(members)
if !parse_enum_body(parser, start.span, explicit_backing, &backing, &members, "expected '{' after enum declaration") {
_ = finish_statement(parser)
return
}
id := types.named(&parser.module.type_store, u32(parser.pkg), u32(name.symbol))
if !types.define_enum(&parser.module.type_store, id, backing, members[:], explicit_backing) {
source.addf(parser.diagnostics, name.span, "duplicate type declaration '%s'", token_text(parser, name))
+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"
+37 -5
View File
@@ -42,7 +42,7 @@ via_try func(value i32) i32 ! BasicError {
}
with_detail func(value i32) i32 ! DetailError {
if (value == 0) return DetailError{ code = 5 }
if (value == 0) return .code{5}
if (value == 1) return .empty
return value
}
@@ -96,6 +96,24 @@ payload func(value Box) i32 {
}
}
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_detail 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 {
acc i32 = 0
@@ -112,16 +130,30 @@ main func() i32 {
k :: via_widen(3) catch 99
l :: catch_widen(0)
m :: catch_widen(-1)
n :: inline_enum(0) catch |e| {
if (e == .inline_bad) {
yield 60
} else {
yield 61
}
}
o :: inline_detail(0) catch |e| {
match e {
.inline_code |value|: yield value + 70
.inline_empty: yield 80
}
}
acc = acc + a + b + c + d + e + f + g + h + i + j + k + l + m
acc = acc + a + b + c + d + e + f + g + h + i + j + k + l + m + n + o
acc = acc + pick(.left)
r Right = .right
acc = acc + pick(r)
box BoxA = BoxA{ a = 8 }
box BoxA = .a{8}
acc = acc + payload(box)
empty BoxB = .b
acc = acc + payload(empty)
acc = acc + payload(.a{9})
# 7 + 5 + 5 + 11 + 13 + 2 + 21 + 22 + 35 + 40 + 6 + 50 + 51 + 10 + 20 + 8 + 3 = 309
return acc - 309
# 7 + 5 + 5 + 11 + 13 + 2 + 21 + 22 + 35 + 40 + 6 + 50 + 51 + 60 + 76 + 10 + 20 + 8 + 3 + 9 = 454
return acc - 454
}