fix: resolve distinct type factory backings

This commit is contained in:
2026-09-19 23:10:16 +02:00
parent 378eb637c6
commit 5dac5baa05
3 changed files with 94 additions and 5 deletions
+23 -5
View File
@@ -3032,6 +3032,15 @@ infer_call_comptime_values :: proc(
{type_call=true, final=true}, {type_call=true, final=true},
} }
for pass in evidence_passes { 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 { for arg_id, source_index in args {
param_index := call_param_index(mapping, source_index) param_index := call_param_index(mapping, source_index)
if param_index >= len(function.params) || param_index >= len(actual_args) { 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) node_count := len(checker.module.types.nodes)
for index in 0..<node_count { for index in 0..<node_count {
item := checker.module.types.nodes[index] item := checker.module.types.nodes[index]
if item.kind == .Distinct {
resolved := type_from_syntax(
checker, item.child, ast.Package_Id(item.pkg), ast.File_Id(item.file),
)
checker.module.types.nodes[index].child = resolved
}
if item.kind != .Struct && item.kind != .Union { if item.kind != .Struct && item.kind != .Union {
continue continue
} }
@@ -7747,7 +7762,8 @@ sequence_item :: proc(value: types.Type, store: ^types.Store) -> (types.Node, bo
} }
find_struct_field :: proc(checker: ^Checker, struct_type: types.Type, name: symbol.Id) -> (int, types.Field, bool) { 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) { if field.name == u32(name) {
return index, field, true 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) { 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) { if !ok || item.kind != .Struct || !item.tuple || index >= u64(item.field_count) {
return 0, {}, false 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 return int(index), fields[index], true
} }
find_struct_field_slot :: proc(checker: ^Checker, struct_type: types.Type, name: symbol.Id) -> (int, types.Field, bool) { 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) { if !ok || (item.kind != .Struct && item.kind != .Union) {
return -1, {}, false 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) { if field.name == u32(name) {
return int(item.field_start)+index, field, true return int(item.field_start)+index, field, true
} }
+1
View File
@@ -1332,6 +1332,7 @@ emit_instruction_stream :: proc(
if types.is_pointer(base_type, &emitter.module.types) { if types.is_pointer(base_type, &emitter.module.types) {
base_type = types.child_type(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 { 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)) 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 continue
+70
View File
@@ -5442,6 +5442,76 @@ main func() void {
testing.expect(t, unrecoverable >= 3) 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) @(test)
string_literals_are_contextual_comptime_type_inference_evidence :: proc(t: ^testing.T) { string_literals_are_contextual_comptime_type_inference_evidence :: proc(t: ^testing.T) {
text := `pair func($T type, expected, actual T) T { text := `pair func($T type, expected, actual T) T {