From 05aa7d084af609274055ab0383f8fa7eee7a03fa Mon Sep 17 00:00:00 2001 From: hl-valdemar Date: Mon, 20 Jul 2026 15:49:52 +0200 Subject: [PATCH] make record fields resolve type-factory calls before runtime validation --- compiler/checker/checker.odin | 26 +++++++++++++++++++++- compiler/parser/parser.odin | 6 +++++- compiler_tests.odin | 32 ++++++++++++++++++++++++++++ examples/programs/arraylist/main.bro | 8 +++++++ std/testing/testing.bro | 16 ++++++-------- 5 files changed, 76 insertions(+), 12 deletions(-) diff --git a/compiler/checker/checker.odin b/compiler/checker/checker.odin index aff8e16..f4ea693 100644 --- a/compiler/checker/checker.odin +++ b/compiler/checker/checker.odin @@ -1189,7 +1189,12 @@ type_from_syntax :: proc( } return composed case .Type_Call: - return resolve_type_factory_call(checker, ast.Expr_Id(item.count_expr), pkg, file) + expr_id := ast.Expr_Id(item.count_expr) + call_file := file + if expr_id != ast.INVALID_EXPR && int(expr_id) < len(checker.ast_module.exprs) { + call_file = ast.File_Id(checker.ast_module.exprs[expr_id].span.file) + } + return resolve_type_factory_call(checker, expr_id, pkg, call_file) } if changed { return types.intern(store, item) @@ -4072,6 +4077,25 @@ validate_meta_schema :: proc(checker: ^Checker) { } validate_type_nodes :: proc(checker: ^Checker) { + node_count := len(checker.module.types.nodes) + for index in 0.. len(checker.module.types.fields) { + continue + } + for slot in start.. ast.Stmt_Id { else_body: []ast.Stmt_Id = nil saved_cursor := parser.cursor skip_newlines(parser) - if current(parser).kind == .Keyword_Else { + // `else:` (and the reserved `else |...|:` shape) starts the next match arm; + // it is not the else-branch of a brace-less if used as the previous arm body. + match_arm_else := current(parser).kind == .Keyword_Else && + (peek(parser).kind == .Colon || peek(parser).kind == .Pipe) + if current(parser).kind == .Keyword_Else && !match_arm_else { advance(parser) skip_newlines(parser) if current(parser).kind == .Keyword_If { diff --git a/compiler_tests.odin b/compiler_tests.odin index 9158f32..7e9c0a8 100644 --- a/compiler_tests.odin +++ b/compiler_tests.odin @@ -9648,6 +9648,38 @@ main func() void { testing.expect_value(t, len(unwrap_if.body), 1) } +@(test) +match_else_arm_is_not_captured_by_braceless_if :: proc(t: ^testing.T) { + text := `ready func() bool { return true } +main func() void { + value i32 = 0 + match value { + 0: if ready() _ = value + else: _ = value + } +} +` + source_file := source.Source{path="test.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) + module := parser.parse(&stream, &source_file, &diagnostics) + defer ast.destroy_module(&module) + + testing.expect_value(t, len(diagnostics.items), 0) + match_statement := module.statements[module.functions[1].body[1]] + testing.expect_value(t, len(match_statement.body), 2) + first_arm := module.statements[match_statement.body[0]] + first_if := module.statements[first_arm.body[0]] + testing.expect_value(t, first_if.kind, ast.Stmt_Kind.If) + testing.expect_value(t, len(first_if.else_body), 0) + else_arm := module.statements[match_statement.body[1]] + testing.expect_value(t, len(else_arm.patterns), 0) +} + @(test) parser_diagnoses_braceless_if_without_parens_or_call :: proc(t: ^testing.T) { text := `main func() void { diff --git a/examples/programs/arraylist/main.bro b/examples/programs/arraylist/main.bro index c5818c8..79bde74 100644 --- a/examples/programs/arraylist/main.bro +++ b/examples/programs/arraylist/main.bro @@ -2,6 +2,14 @@ arraylist :: import "@std/arraylist" mem :: import "@std/mem" std :: import "@std" +Token :: struct { value i32 } +ScanDiagnostic :: struct { value i32 } + +State :: struct { + tokens std.ArrayList(Token) + diagnostics std.ArrayList(ScanDiagnostic) +} + hide fail_alloc func(_ ?@mut anyopaque, _ usize, _ usize) ?*mut u8 { return none } diff --git a/std/testing/testing.bro b/std/testing/testing.bro index dc8fa44..5a8a2e5 100644 --- a/std/testing/testing.bro +++ b/std/testing/testing.bro @@ -34,17 +34,13 @@ expect_equal func($T type, expected, actual T, location SourceLocation) void ! E return .expectation_failed } } - .slice: { - if !mem.eql(expected, actual) { - debug.print("{s}:{d}:{d}: expected and actual slices differ\n", {location.file, location.line, location.column}) - return .expectation_failed - } + .slice: if !mem.eql(expected, actual) { + debug.print("{s}:{d}:{d}: expected and actual slices differ\n", {location.file, location.line, location.column}) + return .expectation_failed } - else: { - if expected != actual { - debug.print("{s}:{d}:{d}: expected {}, found {}\n", {location.file, location.line, location.column, expected, actual}) - return .expectation_failed - } + else: if expected != actual { + debug.print("{s}:{d}:{d}: expected {}, found {}\n", {location.file, location.line, location.column, expected, actual}) + return .expectation_failed } } }