anonymous struct payloads
This commit is contained in:
@@ -595,7 +595,13 @@
|
|||||||
- named fallible signatures can use inline unbacked enum and `union(enum)` error types
|
- named fallible signatures can use inline unbacked enum and `union(enum)` error types
|
||||||
after `!`
|
after `!`
|
||||||
|
|
||||||
23.7. anonymous struct payloads and keyed payload sugar
|
23.7. anonymous struct payloads and keyed payload sugar (implemented)
|
||||||
|
- tagged-union variants can use anonymous `struct { ... }` payloads, scoped to variant payload
|
||||||
|
declarations rather than general anonymous type syntax
|
||||||
|
- contextual `.variant{field = value, ...}` constructs struct payloads by key, reusing ordinary
|
||||||
|
struct-literal validation for unknown, duplicate, missing, and mismatched fields
|
||||||
|
- structurally identical anonymous struct payloads share type identity, so sum composition merges
|
||||||
|
matching variants and still rejects same-name variants with different payload shapes
|
||||||
|
|
||||||
24. dynamic heap allocation
|
24. dynamic heap allocation
|
||||||
- see below for direction
|
- see below for direction
|
||||||
@@ -978,7 +984,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. 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.
|
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. Milestone 23.7 adds anonymous struct payloads and keyed payload sugar. It still defers the `error` keyword shorthand and match-on-error shorthand.
|
||||||
|
|
||||||
### Fallible Functions
|
### Fallible Functions
|
||||||
|
|
||||||
@@ -1081,7 +1087,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 .unclosed_section{start_line}
|
if p.pos >= p.input.len or p.input[p.pos] != ']' return .unclosed_section{line = start_line}
|
||||||
|
|
||||||
# ... continue on success ...
|
# ... continue on success ...
|
||||||
}
|
}
|
||||||
@@ -1177,7 +1183,8 @@ 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 |
|
| `.variant{payload}` | Construct a payload-carrying tagged-union variant from one payload expression |
|
||||||
|
| `.variant{field = value, ...}` | Construct a struct-payload 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 |
|
||||||
|
|||||||
@@ -1417,6 +1417,9 @@ infer_compound_expr :: proc(
|
|||||||
for keyed in expr.args {
|
for keyed in expr.args {
|
||||||
_ = infer_nested_expr(checker, checker.ast_module.exprs[keyed].left, locals, pkg, file, demanded, local_types)
|
_ = infer_nested_expr(checker, checker.ast_module.exprs[keyed].left, locals, pkg, file, demanded, local_types)
|
||||||
}
|
}
|
||||||
|
if !symbol.is_valid(expr.name) {
|
||||||
|
return types.INVALID
|
||||||
|
}
|
||||||
target_pkg, available := expr_package(checker, expr, pkg, file)
|
target_pkg, available := expr_package(checker, expr, pkg, file)
|
||||||
value := types.find_named(store, u32(target_pkg), u32(expr.name)) if available else types.INVALID
|
value := types.find_named(store, u32(target_pkg), u32(expr.name)) if available else types.INVALID
|
||||||
return types.resolve_alias(value, store)
|
return types.resolve_alias(value, store)
|
||||||
@@ -3659,12 +3662,21 @@ build_compound_expr :: proc(
|
|||||||
target=hir.INVALID_REF, diagnostic=source.INVALID_DIAGNOSTIC,
|
target=hir.INVALID_REF, diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
})
|
})
|
||||||
case .Struct_Literal:
|
case .Struct_Literal:
|
||||||
target_pkg, available := expr_package(checker, expr, pkg, file, true)
|
struct_type := types.INVALID
|
||||||
struct_type := types.find_named(store, u32(target_pkg), u32(expr.name)) if available else types.INVALID
|
if symbol.is_valid(expr.name) {
|
||||||
struct_type = types.resolve_alias(struct_type, store)
|
target_pkg, available := expr_package(checker, expr, pkg, file, true)
|
||||||
if !types.is_record(struct_type, store) || types.is_opaque_struct(struct_type, store) {
|
struct_type = types.find_named(store, u32(target_pkg), u32(expr.name)) if available else types.INVALID
|
||||||
id := source.addf(checker.diagnostics, expr.span, "unknown or opaque record type '%s'", symbol_text(checker, expr.name))
|
struct_type = types.resolve_alias(struct_type, store)
|
||||||
return invalid_hir_expr(checker, expr.span, id)
|
if !types.is_record(struct_type, store) || types.is_opaque_struct(struct_type, store) {
|
||||||
|
id := source.addf(checker.diagnostics, expr.span, "unknown or opaque record type '%s'", symbol_text(checker, expr.name))
|
||||||
|
return invalid_hir_expr(checker, expr.span, id)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
struct_type = types.resolve_alias(expected, store)
|
||||||
|
if !types.is_struct(struct_type, store) || types.is_opaque_struct(struct_type, store) {
|
||||||
|
id := source.add(checker.diagnostics, expr.span, "keyed contextual payload requires a struct payload")
|
||||||
|
return invalid_hir_expr(checker, expr.span, id, expected)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
fields := types.fields_for(store, struct_type)
|
fields := types.fields_for(store, struct_type)
|
||||||
union_record := types.is_union(struct_type, store)
|
union_record := types.is_union(struct_type, store)
|
||||||
|
|||||||
+74
-19
@@ -494,13 +494,12 @@ parse_array_literal :: proc(parser: ^Parser, nesting: int) -> ast.Expr_Id {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
parse_struct_literal :: proc(
|
parse_keyed_initializers :: proc(
|
||||||
parser: ^Parser,
|
parser: ^Parser,
|
||||||
qualifier: symbol.Id,
|
left_brace: token.Token,
|
||||||
first, name: token.Token,
|
|
||||||
nesting: int,
|
nesting: int,
|
||||||
) -> ast.Expr_Id {
|
close_message: string,
|
||||||
left_brace := advance(parser)
|
) -> ([]ast.Expr_Id, token.Token) {
|
||||||
parser.delimiter_depth += 1
|
parser.delimiter_depth += 1
|
||||||
defer parser.delimiter_depth -= 1
|
defer parser.delimiter_depth -= 1
|
||||||
args: [dynamic]ast.Expr_Id
|
args: [dynamic]ast.Expr_Id
|
||||||
@@ -539,9 +538,20 @@ parse_struct_literal :: proc(
|
|||||||
}
|
}
|
||||||
right_brace, ok := allow(parser, .Right_Brace)
|
right_brace, ok := allow(parser, .Right_Brace)
|
||||||
if !ok {
|
if !ok {
|
||||||
source.add(parser.diagnostics, current(parser).span, "expected '}' after struct literal")
|
source.add(parser.diagnostics, current(parser).span, close_message)
|
||||||
right_brace = left_brace
|
right_brace = left_brace
|
||||||
}
|
}
|
||||||
|
return args[:], right_brace
|
||||||
|
}
|
||||||
|
|
||||||
|
parse_struct_literal :: proc(
|
||||||
|
parser: ^Parser,
|
||||||
|
qualifier: symbol.Id,
|
||||||
|
first, name: token.Token,
|
||||||
|
nesting: int,
|
||||||
|
) -> ast.Expr_Id {
|
||||||
|
left_brace := advance(parser)
|
||||||
|
args, right_brace := parse_keyed_initializers(parser, left_brace, nesting, "expected '}' after struct literal")
|
||||||
return add_expr(parser, ast.Expr{
|
return add_expr(parser, ast.Expr{
|
||||||
kind=.Struct_Literal,
|
kind=.Struct_Literal,
|
||||||
span=source.Span{file=name.span.file, start=first.span.start, end=right_brace.span.end},
|
span=source.Span{file=name.span.file, start=first.span.start, end=right_brace.span.end},
|
||||||
@@ -669,11 +679,32 @@ parse_primary :: proc(parser: ^Parser, nesting: int) -> ast.Expr_Id {
|
|||||||
advance(parser)
|
advance(parser)
|
||||||
payload := ast.INVALID_EXPR
|
payload := ast.INVALID_EXPR
|
||||||
end := member.span
|
end := member.span
|
||||||
if _, ok := allow(parser, .Left_Brace); ok {
|
if left_brace, ok := allow(parser, .Left_Brace); ok {
|
||||||
parser.delimiter_depth += 1
|
parser.delimiter_depth += 1
|
||||||
skip_newlines(parser)
|
skip_newlines(parser)
|
||||||
if current(parser).kind == .Right_Brace {
|
if current(parser).kind == .Identifier && peek(parser).kind == .Equal {
|
||||||
|
parser.delimiter_depth -= 1
|
||||||
|
args, right_brace := parse_keyed_initializers(parser, left_brace, nesting, "expected '}' after contextual variant payload")
|
||||||
|
payload = add_expr(parser, ast.Expr{
|
||||||
|
kind=.Struct_Literal,
|
||||||
|
span=span_from(left_brace.span, right_brace.span),
|
||||||
|
args=args,
|
||||||
|
qualifier=symbol.INVALID,
|
||||||
|
name=symbol.INVALID,
|
||||||
|
left=ast.INVALID_EXPR,
|
||||||
|
right=ast.INVALID_EXPR,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
end = right_brace.span
|
||||||
|
} else if current(parser).kind == .Right_Brace {
|
||||||
source.add(parser.diagnostics, current(parser).span, "contextual variant payload requires exactly one expression")
|
source.add(parser.diagnostics, current(parser).span, "contextual variant payload requires exactly one expression")
|
||||||
|
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
|
||||||
} else {
|
} else {
|
||||||
payload = parse_expression_bp(parser, 0, nesting+1)
|
payload = parse_expression_bp(parser, 0, nesting+1)
|
||||||
skip_newlines(parser)
|
skip_newlines(parser)
|
||||||
@@ -683,14 +714,14 @@ parse_primary :: proc(parser: ^Parser, nesting: int) -> ast.Expr_Id {
|
|||||||
advance(parser)
|
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
|
||||||
}
|
}
|
||||||
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,
|
||||||
@@ -1959,7 +1990,19 @@ 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 {
|
parse_record_field_type :: proc(parser: ^Parser, allow_anonymous_struct_payload: bool) -> types.Type {
|
||||||
|
if allow_anonymous_struct_payload && current(parser).kind == .Keyword_Struct {
|
||||||
|
return parse_inline_struct_payload_type(parser)
|
||||||
|
}
|
||||||
|
return parse_type(parser)
|
||||||
|
}
|
||||||
|
|
||||||
|
parse_record_body :: proc(
|
||||||
|
parser: ^Parser,
|
||||||
|
fields: ^[dynamic]types.Field,
|
||||||
|
expected_open: string,
|
||||||
|
allow_anonymous_struct_payload := false,
|
||||||
|
) -> bool {
|
||||||
if _, ok := allow(parser, .Left_Brace); !ok {
|
if _, ok := allow(parser, .Left_Brace); !ok {
|
||||||
source.add(parser.diagnostics, current(parser).span, expected_open)
|
source.add(parser.diagnostics, current(parser).span, expected_open)
|
||||||
return false
|
return false
|
||||||
@@ -1977,7 +2020,7 @@ parse_record_body :: proc(parser: ^Parser, fields: ^[dynamic]types.Field, expect
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
field_name := advance(parser)
|
field_name := advance(parser)
|
||||||
field_type := parse_type(parser)
|
field_type := parse_record_field_type(parser, allow_anonymous_struct_payload)
|
||||||
append(fields, types.Field{name=u32(field_name.symbol), type=field_type})
|
append(fields, types.Field{name=u32(field_name.symbol), type=field_type})
|
||||||
if _, ok := allow(parser, .Comma); ok {
|
if _, ok := allow(parser, .Comma); ok {
|
||||||
skip_newlines(parser)
|
skip_newlines(parser)
|
||||||
@@ -1991,6 +2034,17 @@ parse_record_body :: proc(parser: ^Parser, fields: ^[dynamic]types.Field, expect
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
parse_inline_struct_payload_type :: proc(parser: ^Parser) -> types.Type {
|
||||||
|
advance(parser)
|
||||||
|
fields: [dynamic]types.Field
|
||||||
|
fields.allocator = parser.module.allocator
|
||||||
|
defer delete(fields)
|
||||||
|
if !parse_record_body(parser, &fields, "expected '{' after anonymous struct payload") {
|
||||||
|
return types.INVALID
|
||||||
|
}
|
||||||
|
return types.struct_anonymous(&parser.module.type_store, fields[:])
|
||||||
|
}
|
||||||
|
|
||||||
parse_inline_union_type :: proc(parser: ^Parser) -> types.Type {
|
parse_inline_union_type :: proc(parser: ^Parser) -> types.Type {
|
||||||
advance(parser)
|
advance(parser)
|
||||||
valid := true
|
valid := true
|
||||||
@@ -2013,7 +2067,7 @@ parse_inline_union_type :: proc(parser: ^Parser) -> types.Type {
|
|||||||
fields: [dynamic]types.Field
|
fields: [dynamic]types.Field
|
||||||
fields.allocator = parser.module.allocator
|
fields.allocator = parser.module.allocator
|
||||||
defer delete(fields)
|
defer delete(fields)
|
||||||
if !parse_record_body(parser, &fields, "expected '{' after inline union error type") || !valid {
|
if !parse_record_body(parser, &fields, "expected '{' after inline union error type", true) || !valid {
|
||||||
return types.INVALID
|
return types.INVALID
|
||||||
}
|
}
|
||||||
tag := synthesize_union_tag(parser, fields[:])
|
tag := synthesize_union_tag(parser, fields[:])
|
||||||
@@ -2060,7 +2114,8 @@ parse_struct :: proc(parser: ^Parser, name: token.Token, c_layout: bool, is_unio
|
|||||||
fields: [dynamic]types.Field
|
fields: [dynamic]types.Field
|
||||||
fields.allocator = parser.module.allocator
|
fields.allocator = parser.module.allocator
|
||||||
defer delete(fields)
|
defer delete(fields)
|
||||||
_ = parse_record_body(parser, &fields, "expected '{' after struct fields")
|
allow_anonymous_struct_payload := is_union && (inferred_tag || types.is_valid(declared_tag))
|
||||||
|
_ = parse_record_body(parser, &fields, "expected '{' after struct fields", allow_anonymous_struct_payload)
|
||||||
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[:])
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -306,6 +306,42 @@ union_anonymous :: proc(store: ^Store, fields: []Field, tag: Type) -> Type {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
anonymous_struct_fields_equal :: proc(store: ^Store, item: Node, fields: []Field) -> bool {
|
||||||
|
if item.field_count != u32(len(fields)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
start := int(item.field_start)
|
||||||
|
end := start+int(item.field_count)
|
||||||
|
if start < 0 || end > len(store.fields) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for field, index in fields {
|
||||||
|
existing := store.fields[start+index]
|
||||||
|
if existing.name != field.name || existing.type != field.type {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
struct_anonymous :: proc(store: ^Store, fields: []Field) -> Type {
|
||||||
|
for existing, index in store.nodes {
|
||||||
|
if existing.kind == .Struct && existing.name == 0 && existing.declared &&
|
||||||
|
!existing.c_layout && !existing.opaque &&
|
||||||
|
anonymous_struct_fields_equal(store, existing, fields) {
|
||||||
|
return DYNAMIC_START+Type(index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
start := u32(len(store.fields))
|
||||||
|
append(&store.fields, ..fields)
|
||||||
|
return intern(store, Node{
|
||||||
|
kind=.Struct,
|
||||||
|
field_start=start,
|
||||||
|
field_count=u32(len(fields)),
|
||||||
|
declared=true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
variant_id :: proc(store: ^Store, name: u32, payload: Type) -> (u16, bool) {
|
variant_id :: proc(store: ^Store, name: u32, payload: Type) -> (u16, bool) {
|
||||||
for variant in store.variants {
|
for variant in store.variants {
|
||||||
if variant.name == name && variant.payload == payload {
|
if variant.name == name && variant.payload == payload {
|
||||||
|
|||||||
@@ -2568,6 +2568,90 @@ main func() i32 {
|
|||||||
testing.expect(t, strings.contains(llvm_text, "call void @llvm.memcpy.p0.p0.i64"))
|
testing.expect(t, strings.contains(llvm_text, "call void @llvm.memcpy.p0.p0.i64"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
anonymous_struct_payloads_and_keyed_payload_sugar_compile :: proc(t: ^testing.T) {
|
||||||
|
text := `PayloadError :: union(enum) {
|
||||||
|
not_found struct {
|
||||||
|
path i32
|
||||||
|
line i32
|
||||||
|
}
|
||||||
|
wrapped struct {
|
||||||
|
path i32
|
||||||
|
line i32
|
||||||
|
}
|
||||||
|
scalar i32
|
||||||
|
empty void
|
||||||
|
}
|
||||||
|
OtherError :: union(enum) {
|
||||||
|
not_found struct {
|
||||||
|
path i32
|
||||||
|
line i32
|
||||||
|
}
|
||||||
|
other void
|
||||||
|
}
|
||||||
|
BothError :: alias PayloadError | OtherError
|
||||||
|
accept func(value PayloadError) i32 {
|
||||||
|
match value {
|
||||||
|
.not_found |info|: return info.path + info.line
|
||||||
|
.wrapped |info|: return info.path * 10 + info.line
|
||||||
|
.scalar |n|: return n
|
||||||
|
.empty: return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
with_payload func(value i32) i32 ! PayloadError {
|
||||||
|
if (value == 0) return .not_found{path = 4, line = 5}
|
||||||
|
if (value == 1) return .scalar{7}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
inline_payload func(value i32) i32 ! union(enum) {
|
||||||
|
inline_bad struct {
|
||||||
|
code i32
|
||||||
|
line i32
|
||||||
|
}
|
||||||
|
} {
|
||||||
|
if (value == 0) return .inline_bad{code = 6, line = 7}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
main func() i32 {
|
||||||
|
e PayloadError = .not_found{path = 1, line = 2}
|
||||||
|
a :: accept(e)
|
||||||
|
b :: accept(.wrapped{line = 4, path = 3})
|
||||||
|
c :: with_payload(0) catch |err| {
|
||||||
|
match err {
|
||||||
|
.not_found |info|: yield info.path + info.line
|
||||||
|
.wrapped |info|: yield info.path + info.line
|
||||||
|
.scalar |n|: yield n
|
||||||
|
.empty: yield 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
d :: inline_payload(0) catch |err| {
|
||||||
|
match err {
|
||||||
|
.inline_bad |info|: yield info.code + info.line
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return a + b + c + d - 59
|
||||||
|
}
|
||||||
|
`
|
||||||
|
source_file := source.Source{path="anonymous_payloads.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(llvm_text) > 0)
|
||||||
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
inline_fallible_error_types_compile :: proc(t: ^testing.T) {
|
inline_fallible_error_types_compile :: proc(t: ^testing.T) {
|
||||||
text := `inline_enum func(value i32) i32 ! enum {
|
text := `inline_enum func(value i32) i32 ! enum {
|
||||||
@@ -2689,6 +2773,77 @@ main func() i32 {
|
|||||||
testing.expect(t, found_no_context)
|
testing.expect(t, found_no_context)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
anonymous_struct_payloads_reject_bad_forms :: proc(t: ^testing.T) {
|
||||||
|
text := `Bad :: union(enum) {
|
||||||
|
payload struct {
|
||||||
|
x i32
|
||||||
|
y i32
|
||||||
|
}
|
||||||
|
scalar i32
|
||||||
|
}
|
||||||
|
unknown func() Bad {
|
||||||
|
return .payload{x = 1, z = 2}
|
||||||
|
}
|
||||||
|
duplicate func() Bad {
|
||||||
|
return .payload{x = 1, x = 2, y = 3}
|
||||||
|
}
|
||||||
|
missing func() Bad {
|
||||||
|
return .payload{x = 1}
|
||||||
|
}
|
||||||
|
scalar_keyed func() Bad {
|
||||||
|
return .scalar{value = 1}
|
||||||
|
}
|
||||||
|
A :: union(enum) {
|
||||||
|
dup struct {
|
||||||
|
x i32
|
||||||
|
}
|
||||||
|
}
|
||||||
|
B :: union(enum) {
|
||||||
|
dup struct {
|
||||||
|
x i64
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Conflict :: alias A | B
|
||||||
|
main func() i32 {
|
||||||
|
_ = unknown()
|
||||||
|
_ = duplicate()
|
||||||
|
_ = missing()
|
||||||
|
_ = scalar_keyed()
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
`
|
||||||
|
source_file := source.Source{path="bad_anonymous_payloads.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_unknown := false
|
||||||
|
found_duplicate := false
|
||||||
|
found_missing := false
|
||||||
|
found_keyed_non_struct := false
|
||||||
|
found_conflict := false
|
||||||
|
for diagnostic in diagnostics.items {
|
||||||
|
found_unknown = found_unknown || strings.contains(diagnostic.message, "unknown struct field 'z'")
|
||||||
|
found_duplicate = found_duplicate || strings.contains(diagnostic.message, "duplicate initializer for struct field 'x'")
|
||||||
|
found_missing = found_missing || strings.contains(diagnostic.message, "missing initializer for struct field 'y'")
|
||||||
|
found_keyed_non_struct = found_keyed_non_struct || strings.contains(diagnostic.message, "keyed contextual payload requires a struct payload")
|
||||||
|
found_conflict = found_conflict || strings.contains(diagnostic.message, "same variant name")
|
||||||
|
}
|
||||||
|
testing.expect(t, found_unknown)
|
||||||
|
testing.expect(t, found_duplicate)
|
||||||
|
testing.expect(t, found_missing)
|
||||||
|
testing.expect(t, found_keyed_non_struct)
|
||||||
|
testing.expect(t, found_conflict)
|
||||||
|
}
|
||||||
|
|
||||||
@(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"
|
||||||
|
|||||||
@@ -5,6 +5,10 @@ BasicError :: enum {
|
|||||||
|
|
||||||
DetailError :: union(enum) {
|
DetailError :: union(enum) {
|
||||||
code i32
|
code i32
|
||||||
|
info struct {
|
||||||
|
code i32
|
||||||
|
extra i32
|
||||||
|
}
|
||||||
empty void
|
empty void
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,6 +48,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 .code{5}
|
if (value == 0) return .code{5}
|
||||||
if (value == 1) return .empty
|
if (value == 1) return .empty
|
||||||
|
if (value == 3) return .info{code = 8, extra = 9}
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,8 +70,9 @@ catch_basic func(value i32) i32 {
|
|||||||
catch_detail func(value i32) i32 {
|
catch_detail func(value i32) i32 {
|
||||||
return with_detail(value) catch |e| {
|
return with_detail(value) catch |e| {
|
||||||
match e {
|
match e {
|
||||||
.code |n|: yield n + 30
|
.code |n|: yield n + 30
|
||||||
.empty: yield 40
|
.info |info|: yield info.code + info.extra + 30
|
||||||
|
.empty: yield 40
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -74,10 +80,11 @@ catch_detail func(value i32) i32 {
|
|||||||
catch_widen func(value i32) i32 {
|
catch_widen func(value i32) i32 {
|
||||||
return via_widen(value) catch |e| {
|
return via_widen(value) catch |e| {
|
||||||
match e {
|
match e {
|
||||||
.bad: yield 50
|
.bad: yield 50
|
||||||
.worse: yield 51
|
.worse: yield 51
|
||||||
.code |n|: yield n
|
.code |n|: yield n
|
||||||
.empty: yield 52
|
.info |info|: yield info.code + info.extra
|
||||||
|
.empty: yield 52
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -143,8 +150,15 @@ main func() i32 {
|
|||||||
.inline_empty: yield 80
|
.inline_empty: yield 80
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
p :: with_detail(3) catch |e| {
|
||||||
|
match e {
|
||||||
|
.code |value|: yield value
|
||||||
|
.info |info|: yield info.code + info.extra
|
||||||
|
.empty: yield 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
acc = acc + a + b + c + d + e + f + g + h + i + j + k + l + m + n + o
|
acc = acc + a + b + c + d + e + f + g + h + i + j + k + l + m + n + o + p
|
||||||
acc = acc + pick(.left)
|
acc = acc + pick(.left)
|
||||||
r Right = .right
|
r Right = .right
|
||||||
acc = acc + pick(r)
|
acc = acc + pick(r)
|
||||||
@@ -154,6 +168,6 @@ main func() i32 {
|
|||||||
acc = acc + payload(empty)
|
acc = acc + payload(empty)
|
||||||
acc = acc + payload(.a{9})
|
acc = acc + payload(.a{9})
|
||||||
|
|
||||||
# 7 + 5 + 5 + 11 + 13 + 2 + 21 + 22 + 35 + 40 + 6 + 50 + 51 + 60 + 76 + 10 + 20 + 8 + 3 + 9 = 454
|
# 7 + 5 + 5 + 11 + 13 + 2 + 21 + 22 + 35 + 40 + 6 + 50 + 51 + 60 + 76 + 17 + 10 + 20 + 8 + 3 + 9 = 471
|
||||||
return acc - 454
|
return acc - 471
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user