From 10abba54a5db0cbabc9f1a2aec49ee7224392fbf Mon Sep 17 00:00:00 2001 From: hl-valdemar Date: Mon, 20 Jul 2026 08:33:59 +0200 Subject: [PATCH] comptime-state propagation through generated struct field resolution --- compiler/checker/checker.odin | 28 +++++++++++++++------------- compiler/checker/comptime.odin | 16 +++++++++++++--- compiler_tests.odin | 29 +++++++++++++++++++++-------- 3 files changed, 49 insertions(+), 24 deletions(-) diff --git a/compiler/checker/checker.odin b/compiler/checker/checker.odin index 5006d67..3195b15 100644 --- a/compiler/checker/checker.odin +++ b/compiler/checker/checker.odin @@ -1099,6 +1099,7 @@ type_from_syntax :: proc( pkg := ast.Package_Id(0), file := ast.File_Id(0), depth := 0, + active_state: ^Ct_State = nil, ) -> types.Type { if depth > 64 { return types.INVALID @@ -1116,9 +1117,9 @@ type_from_syntax :: proc( changed := false #partial switch item.kind { case .Alias: - return type_from_syntax(checker, item.child, pkg, file, depth+1) + return type_from_syntax(checker, item.child, pkg, file, depth+1, active_state) case .Array: - child := type_from_syntax(checker, item.child, pkg, file, depth+1) + child := type_from_syntax(checker, item.child, pkg, file, depth+1, active_state) changed = changed || child != item.child item.child = child if item.unresolved_count { @@ -1127,7 +1128,8 @@ type_from_syntax :: proc( if expr_id != ast.INVALID_EXPR && int(expr_id) < len(checker.ast_module.exprs) { span = checker.ast_module.exprs[expr_id].span } - constant := eval_integer_constant_in_context(checker, expr_id, pkg, file) + constant := eval_integer_constant_in_state(active_state, expr_id) if active_state != nil else + eval_integer_constant_in_context(checker, expr_id, pkg, file) if constant.kind == .Value { switch { case constant.value < 0: @@ -1153,14 +1155,14 @@ type_from_syntax :: proc( } return types.intern(store, item) case .Pointer, .Slice, .Optional, .Range, .Fallible: - child := type_from_syntax(checker, item.child, pkg, file, depth+1) - extra := type_from_syntax(checker, item.extra, pkg, file, depth+1) + child := type_from_syntax(checker, item.child, pkg, file, depth+1, active_state) + extra := type_from_syntax(checker, item.extra, pkg, file, depth+1, active_state) item.child = child item.extra = extra return types.intern(store, item) case .Distinct, .Enum: - child := type_from_syntax(checker, item.child, pkg, file, depth+1) - extra := type_from_syntax(checker, item.extra, pkg, file, depth+1) + child := type_from_syntax(checker, item.child, pkg, file, depth+1, active_state) + extra := type_from_syntax(checker, item.extra, pkg, file, depth+1, active_state) changed = child != item.child || extra != item.extra item.child = child item.extra = extra @@ -1169,13 +1171,13 @@ type_from_syntax :: proc( resolved_params := make([]types.Type, len(params), checker.allocator) defer delete(resolved_params, checker.allocator) for param, index in params { - resolved_params[index] = type_from_syntax(checker, param.type, pkg, file, depth+1) + resolved_params[index] = type_from_syntax(checker, param.type, pkg, file, depth+1, active_state) } - result := type_from_syntax(checker, item.child, pkg, file, depth+1) + result := type_from_syntax(checker, item.child, pkg, file, depth+1, active_state) return types.function(store, resolved_params, result, item.c_abi, item.variadic) case .Sum: - left := type_from_syntax(checker, item.child, pkg, file, depth+1) - right := type_from_syntax(checker, item.extra, pkg, file, depth+1) + left := type_from_syntax(checker, item.child, pkg, file, depth+1, active_state) + right := type_from_syntax(checker, item.extra, pkg, file, depth+1, active_state) composed, compose_error := types.compose_sum(store, left, right) if compose_error == .Unsupported { source.add(checker.diagnostics, source.Span{}, "only native unbacked enums and tagged unions can be composed with '|'") @@ -2947,7 +2949,7 @@ resolved_call_arg_expected :: proc( return result } -resolve_generated_struct_type :: proc(checker: ^Checker, expr_id: ast.Expr_Id, pkg: ast.Package_Id, file: ast.File_Id) -> types.Type { +resolve_generated_struct_type :: proc(checker: ^Checker, expr_id: ast.Expr_Id, pkg: ast.Package_Id, file: ast.File_Id, active_state: ^Ct_State) -> types.Type { for entry in checker.generated_types { if entry.expr == expr_id && comptime_values_equal(entry.values, checker.current_comptime_values) { return entry.result @@ -2966,7 +2968,7 @@ resolve_generated_struct_type :: proc(checker: ^Checker, expr_id: ast.Expr_Id, p fields := make([]types.Field, len(template_fields), checker.allocator) defer delete(fields, checker.allocator) for field, index in template_fields { - resolved := type_from_syntax(checker, field.type, pkg, file) + resolved := type_from_syntax(checker, field.type, pkg, file, active_state=active_state) if (!is_runtime_type(checker, resolved) && !is_comptime_value_type(checker, resolved)) || types.is_void(resolved) { if expr.tuple { source.addf(checker.diagnostics, expr.span, "tuple element %d requires a concrete runtime type, got %s", index, type_label(checker, resolved)) diff --git a/compiler/checker/comptime.odin b/compiler/checker/comptime.odin index baa3e33..51faec4 100644 --- a/compiler/checker/comptime.odin +++ b/compiler/checker/comptime.odin @@ -1150,7 +1150,7 @@ ct_eval_expr :: proc( resolved := type_from_syntax(checker, expr.type, state.pkg, state.file) return ct_add_value(state, Ct_Value{kind=.Type, type=types.INVALID, index=u64(resolved)}), ct_flow(.Normal), types.is_valid(resolved) case .Anonymous_Struct_Type: - resolved := resolve_generated_struct_type(checker, expr_id, state.pkg, state.file) + resolved := resolve_generated_struct_type(checker, expr_id, state.pkg, state.file, state) return ct_add_value(state, Ct_Value{kind=.Type, type=types.INVALID, index=u64(resolved)}), ct_flow(.Normal), types.is_valid(resolved) case .Enum_Literal: return ct_eval_enum_literal(state, expr, expected, depth+1) @@ -4451,7 +4451,17 @@ eval_integer_constant_in_context :: proc( } state := ct_state_make(checker, pkg, file, types.INVALID, values, diagnose=false) defer ct_state_destroy(&state) - value, flow, ok := ct_eval_expr(&state, expr_id, types.INVALID, depth) + return eval_integer_constant_in_state(&state, expr_id, depth) +} + +eval_integer_constant_in_state :: proc(state: ^Ct_State, expr_id: ast.Expr_Id, depth := 0) -> Constant { + if depth > 64 || expr_id == ast.INVALID_EXPR || int(expr_id) >= len(state.checker.ast_module.exprs) { + return Constant{kind=.Not_Constant} + } + previous_silent := state.silent + state.silent = true + defer state.silent = previous_silent + value, flow, ok := ct_eval_expr(state, expr_id, types.INVALID, depth) if !ok || flow.kind != .Normal { #partial switch state.error { case .Overflow: @@ -4465,7 +4475,7 @@ eval_integer_constant_in_context :: proc( } return Constant{kind=.Not_Constant} } - integer, integer_ok := ct_integer_value(&state, value) + integer, integer_ok := ct_integer_value(state, value) if !integer_ok { return Constant{kind=.Not_Constant} } diff --git a/compiler_tests.odin b/compiler_tests.odin index 26f8f15..d3e5368 100644 --- a/compiler_tests.odin +++ b/compiler_tests.odin @@ -13819,17 +13819,29 @@ Generated func($T type) type { GeneratedInt :: alias Generated(i32) ordered Names = {} -Map func($E, $V type) type { return struct { count usize } } - -init func($E, $V type, values meta.EnumFieldStruct(E, ?V, some!(none))) Map(E, V) { - count usize = 0 +Map func($E, $V type) type { match typeinfo!(E) { - .enum |info|: expand for info.fields |field| { - if field!(values, field.name) |_| { count += 1 } + .enum |info|: return struct { + present [info.fields.len]mut bool + values [info.fields.len]mut V } else: compile_error!("EnumMap key must be an enum") } - return Map(E, V) {count = count} +} + +init func($E, $V type, values meta.EnumFieldStruct(E, ?V, some!(none))) Map(E, V) { + map Map(E, V) = undefined + match typeinfo!(E) { + .enum |info|: expand for info.fields |field, index| { + map.present[index] = false + if field!(values, field.name) |value| { + map.present[index] = true + map.values[index] = value + } + } + else: compile_error!("EnumMap key must be an enum") + } + return map } main func() i32 { @@ -13875,7 +13887,8 @@ main func() i32 { ident = "identifier", int = "integer", }) - if map.count != 2 { return 9 } + if !map.present[0] or !map.present[1] or map.present[2] { return 9 } + if map.values[0].len != 10 or map.values[1].len != 7 { return 18 } return 0 } `