From 5dac5baa050f7b070c9d6b385feb3accded22110 Mon Sep 17 00:00:00 2001 From: hl-valdemar Date: Sat, 19 Sep 2026 23:10:16 +0200 Subject: [PATCH] fix: resolve distinct type factory backings --- compiler/checker/checker.odin | 28 +++++++++++--- compiler/llvm/llvm.odin | 1 + compiler_tests.odin | 70 +++++++++++++++++++++++++++++++++++ 3 files changed, 94 insertions(+), 5 deletions(-) diff --git a/compiler/checker/checker.odin b/compiler/checker/checker.odin index 95b3562..f1d35fa 100644 --- a/compiler/checker/checker.odin +++ b/compiler/checker/checker.odin @@ -3032,6 +3032,15 @@ infer_call_comptime_values :: proc( {type_call=true, final=true}, } for pass in evidence_passes { + if pass.final { + all_bound = true + for value_bound in bound { + all_bound = all_bound && value_bound + } + if all_bound { + continue + } + } for arg_id, source_index in args { param_index := call_param_index(mapping, source_index) if param_index >= len(function.params) || param_index >= len(actual_args) { @@ -4540,6 +4549,12 @@ validate_type_nodes :: proc(checker: ^Checker) { node_count := len(checker.module.types.nodes) for index in 0.. (types.Node, bo } find_struct_field :: proc(checker: ^Checker, struct_type: types.Type, name: symbol.Id) -> (int, types.Field, bool) { - for field, index in types.fields_for(&checker.module.types, struct_type) { + record_type := types.runtime_representation(struct_type, &checker.module.types) + for field, index in types.fields_for(&checker.module.types, record_type) { if field.name == u32(name) { return index, field, true } @@ -7811,20 +7827,22 @@ find_struct_field_default :: proc( } find_tuple_field :: proc(checker: ^Checker, tuple_type: types.Type, index: u64) -> (int, types.Field, bool) { - item, ok := types.node(&checker.module.types, tuple_type) + record_type := types.runtime_representation(tuple_type, &checker.module.types) + item, ok := types.node(&checker.module.types, record_type) if !ok || item.kind != .Struct || !item.tuple || index >= u64(item.field_count) { return 0, {}, false } - fields := types.fields_for(&checker.module.types, tuple_type) + fields := types.fields_for(&checker.module.types, record_type) return int(index), fields[index], true } find_struct_field_slot :: proc(checker: ^Checker, struct_type: types.Type, name: symbol.Id) -> (int, types.Field, bool) { - item, ok := types.node(&checker.module.types, struct_type) + record_type := types.runtime_representation(struct_type, &checker.module.types) + item, ok := types.node(&checker.module.types, record_type) if !ok || (item.kind != .Struct && item.kind != .Union) { return -1, {}, false } - for field, index in types.fields_for(&checker.module.types, struct_type) { + for field, index in types.fields_for(&checker.module.types, record_type) { if field.name == u32(name) { return int(item.field_start)+index, field, true } diff --git a/compiler/llvm/llvm.odin b/compiler/llvm/llvm.odin index 29f55ea..a6628fe 100644 --- a/compiler/llvm/llvm.odin +++ b/compiler/llvm/llvm.odin @@ -1332,6 +1332,7 @@ emit_instruction_stream :: proc( if types.is_pointer(base_type, &emitter.module.types) { base_type = types.child_type(base_type, &emitter.module.types) } + base_type = types.runtime_representation(base_type, &emitter.module.types) if types.kind(base_type, &emitter.module.types) == .Fallible { fmt.sbprintf(&emitter.builder, " %%v%d = getelementptr i8, ptr %%v%d, i64 %d\n", instruction_index, instruction.a, types.fallible_payload_offset(base_type, &emitter.module.types, emitter.module.target)) continue diff --git a/compiler_tests.odin b/compiler_tests.odin index 08d7ba3..5a357b3 100644 --- a/compiler_tests.odin +++ b/compiler_tests.odin @@ -5442,6 +5442,76 @@ main func() void { testing.expect(t, unrecoverable >= 3) } +@(test) +distinct_type_factory_backings_reject_generic_calls_without_stalling :: proc(t: ^testing.T) { + text := `ArrayList func($T type) type { + return struct { items []mut T } +} +append func($T type, list @mut ArrayList(T), value T) void { + _ = list + _ = value +} +Item :: struct { value i32 } +Items :: distinct ArrayList(Item) +main func() void { + items Items := undefined + append(&items, Item { value = 1 }) +} +` + 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) + + found_mismatch := false + found_backing_error := false + found_comptime_error := false + for diagnostic in diagnostics.items { + message := diagnostic.message + found_mismatch = found_mismatch || strings.contains(message, "cannot implicitly convert") + found_backing_error = found_backing_error || strings.contains(message, "requires a concrete runtime backing type") + found_comptime_error = found_comptime_error || strings.contains(message, "invalid comptime argument") + } + testing.expect(t, found_mismatch) + testing.expect(t, !found_backing_error) + testing.expect(t, !found_comptime_error) +} + +@(test) +distinct_type_factory_backings_expose_record_fields :: proc(t: ^testing.T) { + directory := "/tmp/brolang-test-distinct-factory-fields" + main_path := "/tmp/brolang-test-distinct-factory-fields/main.bro" + output := "/tmp/brolang-test-distinct-factory-fields-output" + _ = os2.remove_all(directory) + defer _ = os2.remove_all(directory) + defer _ = os.remove(output) + testing.expect(t, os2.make_directory_all(directory) == nil) + text := `Box func($T type) type { + return struct { value T } +} +IntBox :: distinct Box(i32) +increment func(box @mut IntBox) void { + box.value += 1 +} +main func() i32 { + box IntBox := IntBox(Box(i32) { value = 41 }) + increment(&box) + return box.value - 42 +} +` + testing.expect(t, os.write_entire_file(main_path, transmute([]byte)text)) + testing.expect_value(t, compiler_core.compile_package(directory, output), 0) + state := run_executable(output) + testing.expect_value(t, state.exit_code, 0) +} + @(test) string_literals_are_contextual_comptime_type_inference_evidence :: proc(t: ^testing.T) { text := `pair func($T type, expected, actual T) T {