diff --git a/LANGUAGE.md b/LANGUAGE.md index 8201b30..f7798d9 100644 --- a/LANGUAGE.md +++ b/LANGUAGE.md @@ -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)` - 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 -- 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` - 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 diff --git a/compiler/parser/parser.odin b/compiler/parser/parser.odin index d9279e7..89de3c2 100644 --- a/compiler/parser/parser.odin +++ b/compiler/parser/parser.odin @@ -429,7 +429,8 @@ parse_type_atom :: proc(parser: ^Parser) -> ast.Type_Syntax { return types.BOOL case .Left_Paren: 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 { 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 } -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 - for current(parser).kind == .Pipe { + for { + if multiline { + skip_newlines(parser) + } + if current(parser).kind != .Pipe { + break + } operator := advance(parser) + if multiline { + skip_newlines(parser) + } right := parse_type_atom(parser) composed, compose_error := types.compose_sum(&parser.module.type_store, result, right) if compose_error == .Unsupported { diff --git a/compiler_tests.odin b/compiler_tests.odin index a233489..a74cbd3 100644 --- a/compiler_tests.odin +++ b/compiler_tests.odin @@ -7680,6 +7680,65 @@ main func() i32 { 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) anonymous_struct_payloads_and_keyed_payload_sugar_compile :: proc(t: ^testing.T) { text := `PayloadError :: union(enum) {