fix: allow multiline grouped sum types

This commit is contained in:
2026-10-09 23:25:09 +02:00
parent 7f7de7a8d2
commit 7cb4ace0fa
3 changed files with 73 additions and 4 deletions
+1 -1
View File
@@ -44,7 +44,7 @@ roadmap and milestone history.
- compiler-reordered native structs with fields laid out by decreasing alignment (declaration order breaks ties and remains the reflection/diagnostic order), opaque nominal records with `Name :: opaque`, complete source-order `c_struct { ... }`, keyed record literals, native untagged unions, and native tagged unions `union(Enum)` / `union(enum)` - compiler-reordered native structs with fields laid out by decreasing alignment (declaration order breaks ties and remains the reflection/diagnostic order), opaque nominal records with `Name :: opaque`, complete source-order `c_struct { ... }`, keyed record literals, native untagged unions, and native tagged unions `union(Enum)` / `union(enum)`
- named native struct fields may declare defaults with `field T = expression`; keyed literals use defaults for omitted fields and explicit initializers override them - named native struct fields may declare defaults with `field T = expression`; keyed literals use defaults for omitted fields and explicit initializers override them
- void-payload tagged-union variants, anonymous struct payloads, contextual `.variant`, `.variant{payload}`, and `.variant{field = value}` construction - void-payload tagged-union variants, anonymous struct payloads, contextual `.variant`, `.variant{payload}`, and `.variant{field = value}` construction
- native sum composition with `A | B` for unbacked enums and tagged unions, optionally grouped as `(A | B)`, using program-global `u16` variant ids - native sum composition with `A | B` for unbacked enums and tagged unions, optionally grouped as `(A | B)`, using program-global `u16` variant ids; parenthesized types allow newlines after `(`, before `)`, and on either side of `|`, including in aliases and fallible signatures
- fallible channel types `T ! E`, where `E` is a native enum, native struct, tagged union, or supported sum composition; `void ! E` functions complete successfully on fallthrough, and void-success `catch` handlers may fall through without `yield` - fallible channel types `T ! E`, where `E` is a native enum, native struct, tagged union, or supported sum composition; `void ! E` functions complete successfully on fallthrough, and void-success `catch` handlers may fall through without `yield`
- bodyful local functions and root `main` may write `T!` to infer a specialization-local error channel from propagated `try` expressions and concretely typed error returns; inference composes only existing named error types, never synthesizes variants, and requires at least one inferred error - bodyful local functions and root `main` may write `T!` to infer a specialization-local error channel from propagated `try` expressions and concretely typed error returns; inference composes only existing named error types, never synthesizes variants, and requires at least one inferred error
+13 -3
View File
@@ -429,7 +429,8 @@ parse_type_atom :: proc(parser: ^Parser) -> ast.Type_Syntax {
return types.BOOL return types.BOOL
case .Left_Paren: case .Left_Paren:
advance(parser) advance(parser)
grouped := parse_type(parser) skip_newlines(parser)
grouped := parse_type_pipe_tail(parser, parse_type_atom(parser), true)
if _, ok := allow(parser, .Right_Paren); !ok { if _, ok := allow(parser, .Right_Paren); !ok {
source.add(parser.diagnostics, current(parser).span, "expected ')' after grouped type") source.add(parser.diagnostics, current(parser).span, "expected ')' after grouped type")
} }
@@ -533,10 +534,19 @@ sum_syntax_requires_resolution :: proc(parser: ^Parser, value: types.Type, depth
return false return false
} }
parse_type_pipe_tail :: proc(parser: ^Parser, left: ast.Type_Syntax) -> ast.Type_Syntax { parse_type_pipe_tail :: proc(parser: ^Parser, left: ast.Type_Syntax, multiline := false) -> ast.Type_Syntax {
result := left result := left
for current(parser).kind == .Pipe { for {
if multiline {
skip_newlines(parser)
}
if current(parser).kind != .Pipe {
break
}
operator := advance(parser) operator := advance(parser)
if multiline {
skip_newlines(parser)
}
right := parse_type_atom(parser) right := parse_type_atom(parser)
composed, compose_error := types.compose_sum(&parser.module.type_store, result, right) composed, compose_error := types.compose_sum(&parser.module.type_store, result, right)
if compose_error == .Unsupported { if compose_error == .Unsupported {
+59
View File
@@ -7680,6 +7680,65 @@ 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)
multiline_grouped_sum_types_compile_and_run :: proc(t: ^testing.T) {
directory :: "/tmp/brolang-test-multiline-sums"
output := "/tmp/brolang-test-multiline-sums-output"
_ = os2.remove_all(directory)
defer _ = os2.remove_all(directory)
defer _ = os.remove(output)
testing.expect(t, os.make_directory(directory) == nil)
testing.expect(t, os.make_directory(directory + "/errors") == nil)
errors_text := `AllocError :: enum { alloc }
CapacityError :: enum { capacity }
SourceError :: union(enum) { source i32 }
`
testing.expect(t, os.write_entire_file(directory + "/errors/errors.hon", transmute([]byte)errors_text))
text := `errors :: import "errors"
@hide:file
DriverError :: alias (
errors.AllocError
| (
errors.CapacityError |
errors.SourceError
)
)
code func(err DriverError) i32 {
return match err {
.alloc: 1
.capacity: 2
.source |value|: value
}
}
fail func() void ! (
errors.AllocError
|
errors.CapacityError
) {
return .capacity
}
main func() i32 {
if (code(.alloc) != 1) return 1
if (code(.capacity) != 2) return 2
if (code(.source{42}) != 42) return 3
result := 0
fail() catch |err| {
result = code(err)
}
if (result != 2) return 4
return 0
}
`
testing.expect(t, os.write_entire_file(directory + "/main.hon", transmute([]byte)text))
status := compiler_core.compile_package(directory, output)
testing.expect_value(t, status, 0)
if status != 0 {
return
}
state := run_executable(output)
testing.expect_value(t, state.exit_code, 0)
}
@(test) @(test)
anonymous_struct_payloads_and_keyed_payload_sugar_compile :: proc(t: ^testing.T) { anonymous_struct_payloads_and_keyed_payload_sugar_compile :: proc(t: ^testing.T) {
text := `PayloadError :: union(enum) { text := `PayloadError :: union(enum) {