From 1f25e6cd1d54e1b545efa927d09b7159d82365a1 Mon Sep 17 00:00:00 2001 From: hl-valdemar Date: Thu, 16 Jul 2026 08:57:54 +0200 Subject: [PATCH] better comptime match-statement support --- compiler/checker/checker.odin | 40 +++------- compiler/checker/comptime.odin | 139 +++++++++++++++++++++------------ compiler_tests.odin | 47 +++++++++++ std/io/io.bro | 52 +++--------- 4 files changed, 159 insertions(+), 119 deletions(-) diff --git a/compiler/checker/checker.odin b/compiler/checker/checker.odin index e11105c..4d98bd8 100644 --- a/compiler/checker/checker.odin +++ b/compiler/checker/checker.odin @@ -8637,39 +8637,23 @@ specialization_match_body :: proc( if !ok || flow.kind != .Normal || subject == INVALID_CT_VALUE || int(subject) >= len(state.values) { return nil, {}, false, false } - value := state.values[subject] - if value.kind != .Struct || !types.is_tagged_union(value.type, &checker.module.types) { + selection, selected := ct_select_match_arm(&state, statement, subject, 0) + if !selected { return nil, {}, false, false } - for arm_id in statement.body { - arm := checker.ast_module.statements[arm_id] - if arm.kind != .Match_Arm || arm.pointer_capture { - continue + arm := checker.ast_module.statements[selection.arm] + if arm.pointer_capture { + return nil, {}, false, false + } + if len(arm.captures) > 0 { + if selection.payload == INVALID_CT_VALUE || types.is_void(selection.payload_type) { + return nil, {}, false, false } - if len(arm.patterns) == 0 { - return arm.body, {}, false, true - } - for pattern_id in arm.patterns { - if pattern_id == ast.INVALID_EXPR || int(pattern_id) >= len(checker.ast_module.exprs) { - continue - } - pattern := checker.ast_module.exprs[pattern_id] - if pattern.kind != .Enum_Literal { - continue - } - if index, field, found := find_struct_field(checker, value.type, pattern.name); found && index == int(value.active) { - if len(arm.captures) > 0 && arm.captures[0] != checker.sink_symbol && !types.is_void(field.type) { - children := ct_child_slice(&state, value) - if len(children) == 0 { - return nil, {}, false, false - } - return arm.body, store_static_binding(checker, &state, children[0], arm.captures[0]), true, true - } - return arm.body, {}, false, true - } + if arm.captures[0] != checker.sink_symbol { + return arm.body, store_static_binding(checker, &state, selection.payload, arm.captures[0]), true, true } } - return nil, {}, false, false + return arm.body, {}, false, true } Inline_Binding_Error :: enum u8 { diff --git a/compiler/checker/comptime.odin b/compiler/checker/comptime.odin index 118caad..4f3f8f7 100644 --- a/compiler/checker/comptime.odin +++ b/compiler/checker/comptime.odin @@ -3657,13 +3657,91 @@ ct_exec_for :: proc(state: ^Ct_State, statement: ast.Stmt, yield_returns: bool, return ct_flow(.Normal), ct_fail(state, .Not_Comptime, statement.span, "comptime for-loop iterable must be a range, array, slice, or pointer-to-array") } +Ct_Match_Selection :: struct { + arm: ast.Stmt_Id, + payload: Ct_Value_Id, + payload_field: int, + payload_type: types.Type, +} + +ct_select_match_arm :: proc( + state: ^Ct_State, + statement: ast.Stmt, + subject: Ct_Value_Id, + depth: int, +) -> (Ct_Match_Selection, bool) { + checker := state.checker + if subject == INVALID_CT_VALUE || int(subject) >= len(state.values) { + return {}, false + } + subject_value := state.values[subject] + for arm_id in statement.body { + arm := checker.ast_module.statements[arm_id] + if arm.kind != .Match_Arm { + continue + } + matched := len(arm.patterns) == 0 + selection := Ct_Match_Selection{ + arm=arm_id, + payload=INVALID_CT_VALUE, + payload_field=-1, + payload_type=types.INVALID, + } + if !matched { + for pattern_id in arm.patterns { + pattern := checker.ast_module.exprs[pattern_id] + if subject_value.kind == .Struct && types.is_tagged_union(subject_value.type, &checker.module.types) { + if pattern.kind != .Enum_Literal { + return {}, false + } + field_index, field, found := find_struct_field(checker, subject_value.type, pattern.name) + if !found { + return {}, false + } + if field_index == int(subject_value.active) { + matched = true + selection.payload_field = field_index + selection.payload_type = field.type + children := ct_child_slice(state, subject_value) + if len(children) > 0 { + selection.payload = children[0] + } + break + } + } else if pattern.kind == .Range { + probe, range_flow, range_ok := ct_eval_expr(state, pattern_id, subject_value.type, depth) + if !range_ok || range_flow.kind != .Normal { + return {}, false + } + if ct_range_contains(state, probe, subject) { + matched = true + break + } + } else { + probe, pattern_flow, pattern_ok := ct_eval_expr(state, pattern_id, subject_value.type, depth) + if !pattern_ok || pattern_flow.kind != .Normal { + return {}, false + } + if ct_values_equal(state, subject, probe) { + matched = true + break + } + } + } + } + if matched { + return selection, true + } + } + return {}, false +} + ct_exec_match :: proc(state: ^Ct_State, statement: ast.Stmt, yield_returns: bool, depth: int) -> (Ct_Flow, bool) { checker := state.checker subject, flow, ok := ct_eval_expr(state, statement.expr, types.INVALID, depth+1) if !ok || flow.kind != .Normal { return flow, ok } - subject_value := state.values[subject] wants_pointer := false for arm_id in statement.body { arm := checker.ast_module.statements[arm_id] @@ -3683,69 +3761,28 @@ ct_exec_match :: proc(state: ^Ct_State, statement: ast.Stmt, yield_returns: bool return ct_flow(.Normal), ct_fail(state, .Not_Comptime, statement.span, "comptime match pointer captures require an addressable tagged-union subject") } } - for arm_id in statement.body { - arm := checker.ast_module.statements[arm_id] - if arm.kind != .Match_Arm { - continue - } - matched := len(arm.patterns) == 0 - payload := INVALID_CT_VALUE - payload_field := -1 - payload_type := types.INVALID - if !matched { - for pattern_id in arm.patterns { - pattern := checker.ast_module.exprs[pattern_id] - if subject_value.kind == .Struct && types.is_tagged_union(subject_value.type, &checker.module.types) { - if pattern.kind != .Enum_Literal { - continue - } - if field_index, field, found := find_struct_field(checker, subject_value.type, pattern.name); found && field_index == int(subject_value.active) { - matched = true - payload_field = field_index - payload_type = field.type - children := ct_child_slice(state, subject_value) - if len(children) > 0 { - payload = children[0] - } - break - } - } else if pattern.kind == .Range { - probe, range_flow, range_ok := ct_eval_expr(state, pattern_id, subject_value.type, depth+1) - if range_ok && range_flow.kind == .Normal && ct_range_contains(state, probe, subject) { - matched = true - break - } - } else { - probe, pattern_flow, pattern_ok := ct_eval_expr(state, pattern_id, subject_value.type, depth+1) - if pattern_ok && pattern_flow.kind == .Normal && ct_values_equal(state, subject, probe) { - matched = true - break - } - } - } - } - if !matched { - continue - } + selection, selected := ct_select_match_arm(state, statement, subject, depth+1) + if selected { + arm := checker.ast_module.statements[selection.arm] scope_start := len(state.bindings) - if len(arm.captures) > 0 && payload != INVALID_CT_VALUE { + if len(arm.captures) > 0 && selection.payload != INVALID_CT_VALUE { capture := arm.captures[0] if arm.pointer_capture { - if subject_place == INVALID_CT_PLACE || payload_field < 0 || !types.is_valid(payload_type) { + if subject_place == INVALID_CT_PLACE || selection.payload_field < 0 || !types.is_valid(selection.payload_type) { ct_pop_bindings(state, scope_start) return ct_flow(.Normal), ct_fail(state, .Not_Comptime, arm.span, "comptime match pointer capture requires a tagged-union payload") } payload_place := ct_extend_place( - state, subject_place, Ct_Path_Elem{kind=.Field, index=u32(payload_field)}, - payload_type, subject_writable, + state, subject_place, Ct_Path_Elem{kind=.Field, index=u32(selection.payload_field)}, + selection.payload_type, subject_writable, ) - pointer_type := types.pointer(&checker.module.types, payload_type, subject_writable, false) + pointer_type := types.pointer(&checker.module.types, selection.payload_type, subject_writable, false) pointer := ct_add_value(state, Ct_Value{kind=.Pointer, type=pointer_type, index=u64(payload_place), active=-1}) if capture != checker.sink_symbol { ct_bind_value(state, capture, pointer_type, pointer, false) } } else if capture != checker.sink_symbol { - ct_bind_value(state, capture, state.values[payload].type, payload, false) + ct_bind_value(state, capture, state.values[selection.payload].type, selection.payload, false) } } if yield_returns && len(arm.body) == 1 && checker.ast_module.statements[arm.body[0]].kind == .Expression { diff --git a/compiler_tests.odin b/compiler_tests.odin index 8a043a7..c5aa76e 100644 --- a/compiler_tests.odin +++ b/compiler_tests.odin @@ -2818,6 +2818,53 @@ main func() void { testing.expect_value(t, len(diagnostics.items), 0) } +@(test) +milestone_37_inline_match_specialization_prunes_unselected_arms :: proc(t: ^testing.T) { + text := `Kind :: enum { integer, string, stop } +IntToken :: struct { kind Kind, value i8 } +StringToken :: struct { kind Kind, value []u8 } +StopToken :: struct { kind Kind, value bool } +take_i8 func(value i8) void { _ = value } +main func() void { + inline for { + IntToken {kind = .integer, value = 1}, + StringToken {kind = .string, value = "ok"}, + StopToken {kind = .stop, value = false}, + } |token| { + match token.kind { + .integer: take_i8(token.value) + .string: { + _ = token.value.len + continue + } + .stop: break + } + } + inline for {i8(2), "skip", "stop"} |value, index| { + match index { + 0: {} + 1..=1, 7: continue + else: break + } + take_i8(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) + 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) + + testing.expect_value(t, len(diagnostics.items), 0) +} + @(test) milestone_33_rejects_an_incompatible_runtime_write_declaration :: proc(t: ^testing.T) { text := `write c_func(_ c_int, _ c_int, _ c_ulong) c_long diff --git a/std/io/io.bro b/std/io/io.bro index 24d57ae..c231018 100644 --- a/std/io/io.bro +++ b/std/io/io.bro @@ -355,48 +355,20 @@ hide write_default func(writer Writer, $T type, value T) void ! WriteError { print func(writer Writer, $format []u8, $Args type, args Args) void ! WriteError { inline for parse_format(format.len, format, Args) |token| { - if (token.kind == .unused) { - break + match token.kind { + .unused: break + .literal: try write_all(writer, format[token.start..token.end]) + .string: try write_all(writer, field!(args, token.field)) + .default: try write_default(writer, field!(args, token.field)) + .decimal: try write_decimal(writer, field!(args, token.field)) + .binary: try write_integer(writer, field!(args, token.field), 2, false) + .octal: try write_integer(writer, field!(args, token.field), 8, false) + .hex_lower: try write_integer(writer, field!(args, token.field), 16, false) + .hex_upper: try write_integer(writer, field!(args, token.field), 16, true) + .character: try write_character(writer, field!(args, token.field)) + else: try write_float(writer, field!(args, token.field), true) } - if (token.kind == .literal) { - try write_all(writer, format[token.start..token.end]) - continue - } - if (token.kind == .string) { - try write_all(writer, field!(args, token.field)) - continue - } - if (token.kind == .default) { - try write_default(writer, field!(args, token.field)) - continue - } - if (token.kind == .decimal) { - try write_decimal(writer, field!(args, token.field)) - continue - } - if (token.kind == .binary) { - try write_integer(writer, field!(args, token.field), 2, false) - continue - } - if (token.kind == .octal) { - try write_integer(writer, field!(args, token.field), 8, false) - continue - } - if (token.kind == .hex_lower) { - try write_integer(writer, field!(args, token.field), 16, false) - continue - } - if (token.kind == .hex_upper) { - try write_integer(writer, field!(args, token.field), 16, true) - continue - } - if (token.kind == .character) { - try write_character(writer, field!(args, token.field)) - continue - } - try write_float(writer, field!(args, token.field), true) } - return } hide system_read func(_ ?*mut anyopaque, stream ReadStream, buffer []mut u8) usize ! ReadError {