contextual payload construction
This commit is contained in:
@@ -584,12 +584,18 @@
|
|||||||
- focused coverage lives in `examples/programs/errors` and the fallible ergonomics compiler test
|
- focused coverage lives in `examples/programs/errors` and the fallible ergonomics compiler test
|
||||||
- leave ABI/layout/lint/design polish for later milestones
|
- leave ABI/layout/lint/design polish for later milestones
|
||||||
|
|
||||||
23.6. contextual payload construction + inline error types
|
23.6. contextual payload construction + inline error types (implemented)
|
||||||
- implement contextual payload `.variant{...}` construction
|
- contextual payload `.variant{expr}` construction now works in tagged-union contexts
|
||||||
- allow named fallible signatures to use inline enum / tagged-union error types
|
(assignment, return, calls, and fallible error dispatch); bare `.variant` remains the
|
||||||
- settle qualified same-name disambiguation only if contextual construction needs it
|
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 dynamic tag-width shrinking after the fixed-`u16` ABI has real pressure
|
||||||
- consider all-void channel collapse after fallible channels are otherwise stable
|
- 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
|
- 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.
|
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
|
### Fallible Functions
|
||||||
|
|
||||||
@@ -1048,7 +1054,25 @@ ProcessError :: alias (IoError | ParseError)
|
|||||||
|
|
||||||
### Inline Error Types
|
### 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
|
### Returning Errors
|
||||||
|
|
||||||
@@ -1061,7 +1085,7 @@ parse_section func(p: @mut Parser) void ! ParseError {
|
|||||||
|
|
||||||
# ... parsing logic ...
|
# ... 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 ...
|
# ... 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)
|
# Construct error value (it's just a union)
|
||||||
e ParseError = ParseError{ timeout = 500 }
|
e ParseError = .timeout{500}
|
||||||
|
|
||||||
# Return it via error channel later
|
# Return it via error channel later
|
||||||
return e
|
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 |
|
| `T ! (E1 | E2)` | Planned direct spelling with parentheses; use a named alias in v1 |
|
||||||
| `return e` | Exit function via error channel when `e : E` |
|
| `return e` | Exit function via error channel when `e : E` |
|
||||||
| `error e` | Planned shorthand, not v1 |
|
| `error e` | Planned shorthand, not v1 |
|
||||||
|
| `.variant{payload}` | Construct a payload-carrying tagged-union variant from context |
|
||||||
| `error .variant{...}` | Planned shorthand, not v1 |
|
| `error .variant{...}` | Planned shorthand, not v1 |
|
||||||
| `try expr` | Unwrap success or propagate an exact/sum-widenable error channel |
|
| `try expr` | Unwrap success or propagate an exact/sum-widenable error channel |
|
||||||
| `expr catch fallback` | Provide fallback value on error |
|
| `expr catch fallback` | Provide fallback value on error |
|
||||||
|
|||||||
@@ -758,7 +758,7 @@ mark_expr_imports_used :: proc(checker: ^Checker, expr_id: ast.Expr_Id, file: as
|
|||||||
if expr.left != ast.INVALID_EXPR {
|
if expr.left != ast.INVALID_EXPR {
|
||||||
append(&stack, expr.left)
|
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)
|
append(&stack, expr.left)
|
||||||
case .Catch:
|
case .Catch:
|
||||||
append(&stack, expr.left)
|
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)
|
mark_block_imports_used(checker, expr.body, file)
|
||||||
case .Add, .Sub, .Mul, .Div, .Index, .Orelse, .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or, .Range:
|
case .Add, .Sub, .Mul, .Div, .Index, .Orelse, .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or, .Range:
|
||||||
append(&stack, expr.left, expr.right)
|
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:
|
case .Undefined:
|
||||||
return types.INVALID
|
return types.INVALID
|
||||||
case .Enum_Literal:
|
case .Enum_Literal:
|
||||||
|
if expr.left != ast.INVALID_EXPR {
|
||||||
|
_ = infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types)
|
||||||
|
}
|
||||||
return types.INVALID
|
return types.INVALID
|
||||||
case .Address:
|
case .Address:
|
||||||
child := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types)
|
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)
|
return invalid_hir_expr(checker, expr.span, id, expected)
|
||||||
case .Enum_Literal:
|
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) {
|
if types.is_tagged_union(expected, store) {
|
||||||
index, field, found := find_struct_field(checker, expected, expr.name)
|
index, field, found := find_struct_field(checker, expected, expr.name)
|
||||||
if !found {
|
if !found {
|
||||||
id := source.addf(checker.diagnostics, expr.span, "unknown variant '.%s' on '%s'", symbol_text(checker, expr.name), type_label(checker, expected))
|
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)
|
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 := 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{
|
return add_hir_expr(checker, hir.Expr{
|
||||||
kind=.Struct, span=expr.span, type=expected, args=values, integer=i64(index),
|
kind=.Struct, span=expr.span, type=expected, args=values, integer=i64(index),
|
||||||
target=hir.INVALID_REF, left=hir.INVALID_EXPR, right=hir.INVALID_EXPR,
|
target=hir.INVALID_REF, left=hir.INVALID_EXPR, right=hir.INVALID_EXPR,
|
||||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
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) {
|
if !types.is_enum(expected, store) {
|
||||||
id := source.addf(
|
id := source.addf(
|
||||||
checker.diagnostics,
|
checker.diagnostics,
|
||||||
|
|||||||
+173
-59
@@ -366,23 +366,40 @@ parse_type_atom :: proc(parser: ^Parser) -> ast.Type_Syntax {
|
|||||||
return types.INVALID
|
return types.INVALID
|
||||||
}
|
}
|
||||||
|
|
||||||
parse_type :: proc(parser: ^Parser) -> ast.Type_Syntax {
|
parse_type_pipe_tail :: proc(parser: ^Parser, left: ast.Type_Syntax) -> ast.Type_Syntax {
|
||||||
left := parse_type_atom(parser)
|
result := left
|
||||||
for current(parser).kind == .Pipe {
|
for current(parser).kind == .Pipe {
|
||||||
operator := advance(parser)
|
operator := advance(parser)
|
||||||
right := parse_type_atom(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 {
|
if compose_error == .Unsupported {
|
||||||
source.add(parser.diagnostics, operator.span, "only native unbacked enums and tagged unions can be composed with '|'")
|
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 {
|
} else if compose_error == .Conflict {
|
||||||
source.add(parser.diagnostics, operator.span, "sum composition contains the same variant name with different payload types")
|
source.add(parser.diagnostics, operator.span, "sum composition contains the same variant name with different payload types")
|
||||||
left = types.INVALID
|
result = types.INVALID
|
||||||
} else {
|
} 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 {
|
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 '.'")
|
return invalid_expr(parser, member.span, "expected an enum member after '.'")
|
||||||
}
|
}
|
||||||
advance(parser)
|
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{
|
return add_expr(parser, ast.Expr{
|
||||||
kind=.Enum_Literal,
|
kind=.Enum_Literal,
|
||||||
span=span_from(start.span, member.span),
|
span=span_from(start.span, end),
|
||||||
name=member.symbol,
|
name=member.symbol,
|
||||||
left=ast.INVALID_EXPR,
|
left=payload,
|
||||||
right=ast.INVALID_EXPR,
|
right=ast.INVALID_EXPR,
|
||||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
})
|
})
|
||||||
@@ -1871,7 +1913,7 @@ parse_function :: proc(parser: ^Parser, name: token.Token, c_abi: bool) {
|
|||||||
result := parse_type(parser)
|
result := parse_type(parser)
|
||||||
error_type := types.INVALID
|
error_type := types.INVALID
|
||||||
if _, ok := allow(parser, .Bang); ok {
|
if _, ok := allow(parser, .Bang); ok {
|
||||||
error_type = parse_type(parser)
|
error_type = parse_error_type(parser)
|
||||||
}
|
}
|
||||||
end := previous(parser)
|
end := previous(parser)
|
||||||
ended_by_newline := current(parser).kind == .Newline
|
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) {
|
parse_struct :: proc(parser: ^Parser, name: token.Token, c_layout: bool, is_union := false) {
|
||||||
start := advance(parser)
|
start := advance(parser)
|
||||||
id := types.named(&parser.module.type_store, u32(parser.pkg), u32(name.symbol))
|
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
|
return
|
||||||
}
|
}
|
||||||
advance(parser)
|
|
||||||
fields: [dynamic]types.Field
|
fields: [dynamic]types.Field
|
||||||
fields.allocator = parser.module.allocator
|
fields.allocator = parser.module.allocator
|
||||||
defer delete(fields)
|
defer delete(fields)
|
||||||
skip_newlines(parser)
|
_ = parse_record_body(parser, &fields, "expected '{' after struct fields")
|
||||||
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")
|
|
||||||
}
|
|
||||||
if is_union && (inferred_tag || types.is_valid(declared_tag)) {
|
if is_union && (inferred_tag || types.is_valid(declared_tag)) {
|
||||||
tag = synthesize_union_tag(parser, fields[:])
|
tag = synthesize_union_tag(parser, fields[:])
|
||||||
}
|
}
|
||||||
@@ -2033,26 +2112,19 @@ parse_alias :: proc(parser: ^Parser, name: token.Token) {
|
|||||||
_ = finish_statement(parser)
|
_ = finish_statement(parser)
|
||||||
}
|
}
|
||||||
|
|
||||||
parse_enum :: proc(parser: ^Parser, name: token.Token) {
|
parse_enum_body :: proc(
|
||||||
start := advance(parser)
|
parser: ^Parser,
|
||||||
explicit_backing := false
|
start: source.Span,
|
||||||
backing := types.INVALID
|
explicit_backing: bool,
|
||||||
if _, ok := allow(parser, .Left_Paren); ok {
|
backing: ^types.Type,
|
||||||
explicit_backing = true
|
members: ^[dynamic]types.Enum_Member,
|
||||||
backing = parse_type(parser)
|
expected_open: string,
|
||||||
if _, close_ok := allow(parser, .Right_Paren); !close_ok {
|
) -> bool {
|
||||||
source.add(parser.diagnostics, current(parser).span, "expected ')' after enum backing type")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
skip_newlines(parser)
|
skip_newlines(parser)
|
||||||
if _, ok := allow(parser, .Left_Brace); !ok {
|
if _, ok := allow(parser, .Left_Brace); !ok {
|
||||||
source.add(parser.diagnostics, current(parser).span, "expected '{' after enum declaration")
|
source.add(parser.diagnostics, current(parser).span, expected_open)
|
||||||
_ = finish_statement(parser)
|
return false
|
||||||
return
|
|
||||||
}
|
}
|
||||||
members: [dynamic]types.Enum_Member
|
|
||||||
members.allocator = parser.module.allocator
|
|
||||||
defer delete(members)
|
|
||||||
next_value: i128
|
next_value: i128
|
||||||
previous_value: i128
|
previous_value: i128
|
||||||
has_previous := false
|
has_previous := false
|
||||||
@@ -2070,7 +2142,7 @@ parse_enum :: proc(parser: ^Parser, name: token.Token) {
|
|||||||
}
|
}
|
||||||
member := advance(parser)
|
member := advance(parser)
|
||||||
duplicate := false
|
duplicate := false
|
||||||
for existing in members {
|
for existing in members^ {
|
||||||
if existing.name == u32(member.symbol) {
|
if existing.name == u32(member.symbol) {
|
||||||
duplicate = true
|
duplicate = true
|
||||||
break
|
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")
|
source.add(parser.diagnostics, member.span, "enum values must be strictly increasing")
|
||||||
}
|
}
|
||||||
if !duplicate {
|
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
|
previous_value = value
|
||||||
has_previous = true
|
has_previous = true
|
||||||
@@ -2122,19 +2194,61 @@ parse_enum :: proc(parser: ^Parser, name: token.Token) {
|
|||||||
if _, ok := allow(parser, .Right_Brace); !ok {
|
if _, ok := allow(parser, .Right_Brace); !ok {
|
||||||
source.add(parser.diagnostics, current(parser).span, "expected '}' after enum members")
|
source.add(parser.diagnostics, current(parser).span, "expected '}' after enum members")
|
||||||
}
|
}
|
||||||
if len(members) == 0 {
|
if len(members^) == 0 {
|
||||||
source.add(parser.diagnostics, start.span, "enum declarations require at least one member")
|
source.add(parser.diagnostics, start, "enum declarations require at least one member")
|
||||||
}
|
}
|
||||||
if !explicit_backing {
|
if !explicit_backing {
|
||||||
backing = types.U16
|
backing^ = types.U16
|
||||||
for &member in members {
|
for &member in members^ {
|
||||||
id, ok := types.variant_id(&parser.module.type_store, member.name, types.VOID)
|
id, ok := types.variant_id(&parser.module.type_store, member.name, types.VOID)
|
||||||
if !ok {
|
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)
|
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))
|
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) {
|
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))
|
source.addf(parser.diagnostics, name.span, "duplicate type declaration '%s'", token_text(parser, name))
|
||||||
|
|||||||
@@ -2514,6 +2514,181 @@ main func() i32 {
|
|||||||
testing.expect_value(t, compiler_core.compile_package(directory, output), 1)
|
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)
|
@(test)
|
||||||
errors_example_compiles_and_runs :: proc(t: ^testing.T) {
|
errors_example_compiles_and_runs :: proc(t: ^testing.T) {
|
||||||
output := "/tmp/brolang-test-errors"
|
output := "/tmp/brolang-test-errors"
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ via_try func(value i32) i32 ! BasicError {
|
|||||||
}
|
}
|
||||||
|
|
||||||
with_detail func(value i32) i32 ! DetailError {
|
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
|
if (value == 1) return .empty
|
||||||
return value
|
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 {
|
main func() i32 {
|
||||||
acc i32 = 0
|
acc i32 = 0
|
||||||
|
|
||||||
@@ -112,16 +130,30 @@ main func() i32 {
|
|||||||
k :: via_widen(3) catch 99
|
k :: via_widen(3) catch 99
|
||||||
l :: catch_widen(0)
|
l :: catch_widen(0)
|
||||||
m :: catch_widen(-1)
|
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)
|
acc = acc + pick(.left)
|
||||||
r Right = .right
|
r Right = .right
|
||||||
acc = acc + pick(r)
|
acc = acc + pick(r)
|
||||||
box BoxA = BoxA{ a = 8 }
|
box BoxA = .a{8}
|
||||||
acc = acc + payload(box)
|
acc = acc + payload(box)
|
||||||
empty BoxB = .b
|
empty BoxB = .b
|
||||||
acc = acc + payload(empty)
|
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
|
# 7 + 5 + 5 + 11 + 13 + 2 + 21 + 22 + 35 + 40 + 6 + 50 + 51 + 60 + 76 + 10 + 20 + 8 + 3 + 9 = 454
|
||||||
return acc - 309
|
return acc - 454
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user