From fd3b3f4c53739e35cf115f1abc85e669f2cf8907 Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Wed, 15 Apr 2026 05:18:38 +0300 Subject: [PATCH] checker: fix type inference with generic structs and generic interfaces (fixes #19618) --- vlib/v/checker/check_types.v | 192 ++++++++++++++---- vlib/v/checker/checker.v | 121 ++++++----- vlib/v/checker/interface.v | 146 +++++-------- vlib/v/gen/c/fn.v | 7 +- ...neric_interface_nested_struct_infer_test.v | 31 +++ 5 files changed, 311 insertions(+), 186 deletions(-) create mode 100644 vlib/v/tests/generics/generic_interface_nested_struct_infer_test.v diff --git a/vlib/v/checker/check_types.v b/vlib/v/checker/check_types.v index 45d66559f..1f8d1e24f 100644 --- a/vlib/v/checker/check_types.v +++ b/vlib/v/checker/check_types.v @@ -972,38 +972,161 @@ fn (mut c Checker) symmetric_check(left ast.Type, right ast.Type) bool { return c.check_basic(left, right) } +fn (c &Checker) generic_type_args_and_parent_idx(typ ast.Type) ([]ast.Type, int) { + base_typ := typ.clear_flags().clear_option_and_result() + sym := c.table.sym(base_typ) + match sym.info { + ast.Struct { + mut type_args := sym.info.concrete_types.clone() + if type_args.len == 0 && sym.generic_types.len > 0 { + type_args = sym.generic_types.clone() + } + if type_args.len == 0 { + type_args = sym.info.generic_types.clone() + } + parent_idx := if sym.info.parent_type != 0 { + sym.info.parent_type.clear_flags().idx() + } else { + base_typ.idx() + } + return type_args, parent_idx + } + ast.Interface { + mut type_args := sym.info.concrete_types.clone() + if type_args.len == 0 && sym.generic_types.len > 0 { + type_args = sym.generic_types.clone() + } + if type_args.len == 0 { + type_args = sym.info.generic_types.clone() + } + parent_idx := if sym.info.parent_type != 0 { + sym.info.parent_type.clear_flags().idx() + } else { + base_typ.idx() + } + return type_args, parent_idx + } + ast.SumType { + mut type_args := sym.info.concrete_types.clone() + if type_args.len == 0 && sym.generic_types.len > 0 { + type_args = sym.generic_types.clone() + } + if type_args.len == 0 { + type_args = sym.info.generic_types.clone() + } + parent_idx := if sym.info.parent_type != 0 { + sym.info.parent_type.clear_flags().idx() + } else { + base_typ.idx() + } + return type_args, parent_idx + } + ast.GenericInst { + parent_sym := c.table.sym(ast.new_type(sym.info.parent_idx)) + if parent_sym.kind in [.struct, .interface, .sum_type] { + return sym.info.concrete_types.clone(), sym.info.parent_idx + } + } + else {} + } + return []ast.Type{}, 0 +} + fn (c &Checker) infer_composite_generic_type(gt_name string, generic_typ ast.Type, concrete_typ ast.Type) ast.Type { - param_sym := c.table.sym(generic_typ) - if param_sym.kind !in [.struct, .interface, .sum_type] { + if generic_typ == 0 || concrete_typ == 0 { return ast.void_type } + if generic_typ.has_flag(.option) && concrete_typ.has_flag(.option) { + return c.infer_composite_generic_type(gt_name, generic_typ.clear_flag(.option), + concrete_typ.clear_flag(.option)) + } + if generic_typ.has_flag(.result) && concrete_typ.has_flag(.result) { + return c.infer_composite_generic_type(gt_name, generic_typ.clear_flag(.result), + concrete_typ.clear_flag(.result)) + } + param_sym := c.table.sym(generic_typ) + if generic_typ.has_flag(.generic) && param_sym.name == gt_name { + mut inferred_type := concrete_typ + if generic_typ.nr_muls() > 0 && inferred_type.nr_muls() > 0 { + nr_muls := inferred_type.nr_muls() - generic_typ.nr_muls() + inferred_type = inferred_type.set_nr_muls(if nr_muls > 0 { nr_muls } else { 0 }) + } + return inferred_type + } arg_sym := c.table.sym(concrete_typ) - mut generic_types := []ast.Type{} - mut concrete_types := []ast.Type{} match param_sym.info { - ast.Struct, ast.Interface, ast.SumType { - if param_sym.generic_types.len > 0 { - generic_types = param_sym.generic_types.clone() - } else { - generic_types = param_sym.info.generic_types.clone() + ast.Array { + if arg_sym.info is ast.Array { + return c.infer_composite_generic_type(gt_name, param_sym.info.elem_type, + arg_sym.info.elem_type) } } - else {} - } - match arg_sym.info { - ast.Struct, ast.Interface, ast.SumType { - concrete_types = arg_sym.info.concrete_types.clone() + ast.ArrayFixed { + if arg_sym.info is ast.ArrayFixed && param_sym.info.size == arg_sym.info.size { + return c.infer_composite_generic_type(gt_name, param_sym.info.elem_type, + arg_sym.info.elem_type) + } + } + ast.Map { + if arg_sym.info is ast.Map { + inferred_key_type := c.infer_composite_generic_type(gt_name, + param_sym.info.key_type, arg_sym.info.key_type) + if inferred_key_type != ast.void_type { + return inferred_key_type + } + return c.infer_composite_generic_type(gt_name, param_sym.info.value_type, + arg_sym.info.value_type) + } + } + ast.Chan { + if arg_sym.info is ast.Chan { + return c.infer_composite_generic_type(gt_name, param_sym.info.elem_type, + arg_sym.info.elem_type) + } + } + ast.FnType { + if arg_sym.info is ast.FnType { + if param_sym.info.func.params.len != arg_sym.info.func.params.len { + return ast.void_type + } + for i, expected_param in param_sym.info.func.params { + inferred_param_type := c.infer_composite_generic_type(gt_name, + expected_param.typ, arg_sym.info.func.params[i].typ) + if inferred_param_type != ast.void_type { + return inferred_param_type + } + } + return c.infer_composite_generic_type(gt_name, param_sym.info.func.return_type, + arg_sym.info.func.return_type) + } + } + ast.MultiReturn { + if arg_sym.info is ast.MultiReturn && param_sym.info.types.len == arg_sym.info.types.len { + for i, expected_type in param_sym.info.types { + inferred_type := c.infer_composite_generic_type(gt_name, expected_type, + arg_sym.info.types[i]) + if inferred_type != ast.void_type { + return inferred_type + } + } + } } else {} } - if generic_types.len == 0 || generic_types.len != concrete_types.len { + expected_type_args, expected_parent_idx := c.generic_type_args_and_parent_idx(generic_typ) + actual_type_args, actual_parent_idx := c.generic_type_args_and_parent_idx(concrete_typ) + if expected_parent_idx == 0 || expected_parent_idx != actual_parent_idx + || expected_type_args.len == 0 || expected_type_args.len != actual_type_args.len { return ast.void_type } - generic_names := generic_types.map(c.table.sym(it).name) - if gt_name !in generic_names { - return ast.void_type + for i, expected_type_arg in expected_type_args { + inferred_type := c.infer_composite_generic_type(gt_name, expected_type_arg, + actual_type_args[i]) + if inferred_type != ast.void_type { + return inferred_type + } } - return concrete_types[generic_names.index(gt_name)] + return ast.void_type } fn (mut c Checker) infer_struct_generic_types(typ ast.Type, node ast.StructInit) []ast.Type { @@ -1312,6 +1435,10 @@ fn (mut c Checker) infer_fn_generic_types(func &ast.Fn, mut node ast.CallExpr) { param.typ } param_sym := c.table.sym(param_infer_typ) + mut param_is_interface := param_sym.kind == .interface + if !param_is_interface && param_sym.kind == .generic_inst { + param_is_interface = c.table.type_symbols[(param_sym.info as ast.GenericInst).parent_idx].kind == .interface + } if (param_infer_typ.has_flag(.option) && arg.typ.has_flag(.option)) || (param_infer_typ.has_flag(.result) && arg.typ.has_flag(.result)) { @@ -1472,25 +1599,18 @@ fn (mut c Checker) infer_fn_generic_types(func &ast.Fn, mut node ast.CallExpr) { } } } - } else if arg_sym.kind in [.struct, .interface, .sum_type] { - mut generic_types := []ast.Type{} - mut concrete_types := []ast.Type{} - match arg_sym.info { - ast.Struct, ast.Interface, ast.SumType { - if param_sym.generic_types.len > 0 { - generic_types = param_sym.generic_types.clone() - } else { - generic_types = arg_sym.info.generic_types.clone() - } - concrete_types = arg_sym.info.concrete_types.clone() - } - else {} + } else if param_is_interface && arg_sym.kind != .any { + inferred_interface_type := + c.unwrap_generic_interface(arg_typ, param_infer_typ, token.Pos{}) + if inferred_interface_type != 0 { + typ = c.infer_composite_generic_type(gt_name, param_infer_typ, + inferred_interface_type) } - generic_names := generic_types.map(c.table.sym(it).name) - if gt_name in generic_names && generic_types.len == concrete_types.len { - idx := generic_names.index(gt_name) - typ = concrete_types[idx] + if typ == ast.void_type { + typ = c.infer_composite_generic_type(gt_name, param_infer_typ, arg_typ) } + } else if arg_sym.kind in [.struct, .interface, .sum_type, .generic_inst] { + typ = c.infer_composite_generic_type(gt_name, param_infer_typ, arg_typ) } else if arg_sym.kind == .any && c.table.cur_fn.generic_names.len > 0 && c.table.cur_fn.params.len > 0 && func.generic_names.len > 0 && arg.expr is ast.Ident && arg_i !in arg_inferred { diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index b2a638258..aa6808a02 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -1870,6 +1870,74 @@ fn (mut c Checker) fail_if_immutable(mut expr ast.Expr) (string, token.Pos) { return to_lock, pos } +fn (mut c Checker) resolve_method_for_concrete_type(method ast.Fn, typ_sym &ast.TypeSymbol) ast.Fn { + mut resolved_method := method + concrete_types := c.concrete_types_for_type_symbol(typ_sym) + generic_names := c.generic_names_for_type_parent(typ_sym) + if generic_names.len == 0 || generic_names.len != concrete_types.len { + return resolved_method + } + if rt := c.table.convert_generic_type(resolved_method.return_type, generic_names, + concrete_types) + { + resolved_method.return_type = rt + } + resolved_method.params = resolved_method.params.clone() + for mut param in resolved_method.params { + if pt := c.table.convert_generic_type(param.typ, generic_names, concrete_types) { + param.typ = pt + } + } + return resolved_method +} + +fn (c &Checker) concrete_types_for_type_symbol(typ_sym &ast.TypeSymbol) []ast.Type { + match typ_sym.info { + ast.Struct, ast.Interface, ast.SumType { + mut concrete_types := typ_sym.info.concrete_types.clone() + if concrete_types.len == 0 + && typ_sym.generic_types.len == typ_sym.info.generic_types.len + && typ_sym.generic_types != typ_sym.info.generic_types { + concrete_types = typ_sym.generic_types.clone() + } + return concrete_types + } + ast.GenericInst { + return typ_sym.info.concrete_types.clone() + } + else {} + } + return []ast.Type{} +} + +fn (c &Checker) generic_names_for_type_parent(typ_sym &ast.TypeSymbol) []string { + match typ_sym.info { + ast.Struct, ast.Interface, ast.SumType { + if !typ_sym.info.parent_type.has_flag(.generic) { + return []string{} + } + parent_sym := c.table.sym(typ_sym.info.parent_type) + match parent_sym.info { + ast.Struct, ast.Interface, ast.SumType { + return parent_sym.info.generic_types.map(c.table.sym(it).name) + } + else {} + } + } + ast.GenericInst { + parent_sym := c.table.sym(ast.new_type(typ_sym.info.parent_idx)) + match parent_sym.info { + ast.Struct, ast.Interface, ast.SumType { + return parent_sym.info.generic_types.map(c.table.sym(it).name) + } + else {} + } + } + else {} + } + return []string{} +} + fn (mut c Checker) type_implements(typ ast.Type, interface_type ast.Type, pos token.Pos) bool { mut resolved_interface_type := c.unwrap_generic(interface_type) if typ == resolved_interface_type { @@ -2098,58 +2166,7 @@ fn (mut c Checker) type_implements(typ ast.Type, interface_type ast.Type, pos to continue } } - // Resolve generic parameters for concrete generic types - match typ_sym.info { - ast.Struct, ast.Interface, ast.SumType { - if typ_sym.info.concrete_types.len > 0 - && typ_sym.info.parent_type.has_flag(.generic) { - parent_sym := c.table.sym(typ_sym.info.parent_type) - match parent_sym.info { - ast.Struct, ast.Interface, ast.SumType { - generic_names := - parent_sym.info.generic_types.map(c.table.sym(it).name) - if rt := c.table.convert_generic_type(method.return_type, - generic_names, typ_sym.info.concrete_types) - { - method.return_type = rt - } - method.params = method.params.clone() - for mut param in method.params { - if pt := c.table.convert_generic_type(param.typ, generic_names, - typ_sym.info.concrete_types) - { - param.typ = pt - } - } - } - else {} - } - } - } - ast.GenericInst { - parent_sym := c.table.sym(ast.new_type(typ_sym.info.parent_idx)) - match parent_sym.info { - ast.Struct, ast.Interface, ast.SumType { - generic_names := parent_sym.info.generic_types.map(c.table.sym(it).name) - if rt := c.table.convert_generic_type(method.return_type, - generic_names, typ_sym.info.concrete_types) - { - method.return_type = rt - } - method.params = method.params.clone() - for mut param in method.params { - if pt := c.table.convert_generic_type(param.typ, generic_names, - typ_sym.info.concrete_types) - { - param.typ = pt - } - } - } - else {} - } - } - else {} - } + method = c.resolve_method_for_concrete_type(method, typ_sym) msg := c.table.is_same_method(imethod, method) if msg.len > 0 { sig := c.table.fn_signature(imethod, skip_receiver: false) diff --git a/vlib/v/checker/interface.v b/vlib/v/checker/interface.v index c0b1f9300..3d82f62bc 100644 --- a/vlib/v/checker/interface.v +++ b/vlib/v/checker/interface.v @@ -262,17 +262,30 @@ fn (mut c Checker) unwrap_generic_interface(typ ast.Type, interface_type ast.Typ for gt_name in generic_names { mut inferred_type := ast.void_type for ifield in inter_sym.info.fields { - if ifield.typ.has_flag(.generic) && c.table.get_type_name(ifield.typ) == gt_name { - if field := c.table.find_field_with_embeds(typ_sym, ifield.name) { - inferred_type = field.typ + if field := c.table.find_field_with_embeds(typ_sym, ifield.name) { + mut ifield_typ := ifield.typ + if interface_concrete_types.len == generic_names.len { + if resolved_field_type := c.table.convert_generic_type(ifield.typ, + generic_names, interface_concrete_types) + { + ifield_typ = resolved_field_type + } + } + inferred_field_type := c.infer_composite_generic_type(gt_name, ifield_typ, + field.typ) + if inferred_field_type != ast.void_type { + inferred_type = inferred_field_type } } } for imethod in inter_sym.info.methods { - method := typ_sym.find_method_with_generic_parent(imethod.name) or { - c.error('can not find method `${imethod.name}` on `${typ_sym.name}`, needed for interface: `${inter_sym.name}`', pos) + mut method := typ_sym.find_method_with_generic_parent(imethod.name) or { + if pos.file_idx != -1 { + c.error('can not find method `${imethod.name}` on `${typ_sym.name}`, needed for interface: `${inter_sym.name}`', pos) + } return 0 } + method = c.resolve_method_for_concrete_type(method, typ_sym) mut imethod_return_type := imethod.return_type if interface_concrete_types.len == generic_names.len { if resolved_return_type := c.table.convert_generic_type(imethod.return_type, @@ -281,60 +294,30 @@ fn (mut c Checker) unwrap_generic_interface(typ ast.Type, interface_type ast.Typ imethod_return_type = resolved_return_type } } - if imethod_return_type.has_flag(.generic) { + if imethod_return_type.has_flag(.generic) + || c.type_has_unresolved_generic_parts(imethod_return_type) { imret_sym := c.table.sym(imethod_return_type) mret_sym := c.table.sym(method.return_type) if method.return_type == ast.void_type && imethod_return_type != method.return_type { - c.error('interface method `${imethod.name}` returns `${imret_sym.name}`, but implementation method `${method.name}` returns no value', - pos) + if pos.file_idx != -1 { + c.error('interface method `${imethod.name}` returns `${imret_sym.name}`, but implementation method `${method.name}` returns no value', + pos) + } return 0 } if imethod_return_type == ast.void_type && imethod_return_type != method.return_type { - c.error('interface method `${imethod.name}` returns no value, but implementation method `${method.name}` returns `${mret_sym.name}`', - pos) + if pos.file_idx != -1 { + c.error('interface method `${imethod.name}` returns no value, but implementation method `${method.name}` returns `${mret_sym.name}`', + pos) + } return 0 } - if imret_sym.info is ast.MultiReturn && mret_sym.info is ast.MultiReturn { - for i, mr_typ in imret_sym.info.types { - if mr_typ.has_flag(.generic) - && c.table.get_type_name(mr_typ) == gt_name { - inferred_type = mret_sym.info.types[i] - } - } - } else if c.table.get_type_name(imethod_return_type) == gt_name { - mut ret_typ := method.return_type - if imethod_return_type.has_flag(.option) { - ret_typ = ret_typ.clear_flag(.option) - } else if imethod_return_type.has_flag(.result) { - ret_typ = ret_typ.clear_flag(.result) - } - inferred_type = ret_typ - } else if imret_sym.info is ast.SumType && mret_sym.info is ast.SumType { - im_generic_names := - imret_sym.info.generic_types.map(c.table.sym(it).name) - if gt_name in im_generic_names - && imret_sym.info.generic_types.len == mret_sym.info.concrete_types.len { - idx := im_generic_names.index(gt_name) - inferred_type = mret_sym.info.concrete_types[idx] - } - } else if imret_sym.info is ast.Interface && mret_sym.info is ast.Interface { - im_generic_names := - imret_sym.info.generic_types.map(c.table.sym(it).name) - if gt_name in im_generic_names - && imret_sym.info.generic_types.len == mret_sym.info.concrete_types.len { - idx := im_generic_names.index(gt_name) - inferred_type = mret_sym.info.concrete_types[idx] - } - } else if imret_sym.info is ast.Struct && mret_sym.info is ast.Struct { - im_generic_names := - imret_sym.info.generic_types.map(c.table.sym(it).name) - if gt_name in im_generic_names - && imret_sym.info.generic_types.len == mret_sym.info.concrete_types.len { - idx := im_generic_names.index(gt_name) - inferred_type = mret_sym.info.concrete_types[idx] - } + inferred_return_type := c.infer_composite_generic_type(gt_name, + imethod_return_type, method.return_type) + if inferred_return_type != ast.void_type { + inferred_type = inferred_return_type } } for i, iparam in imethod.params { @@ -356,59 +339,26 @@ fn (mut c Checker) unwrap_generic_interface(typ ast.Type, interface_type ast.Typ && imethod.name == method.name { need_inferred_type = true } - if iparam.typ.has_flag(.generic) || need_inferred_type { - param_sym := c.table.sym(iparam_typ) - arg_sym := c.table.sym(param.typ) - if c.table.get_type_name(iparam_typ) == gt_name - || (need_inferred_type && inferred_type == ast.void_type) { + if iparam_typ.has_flag(.generic) + || c.type_has_unresolved_generic_parts(iparam_typ) || need_inferred_type { + inferred_param_type := c.infer_composite_generic_type(gt_name, + iparam_typ, param.typ) + if inferred_param_type != ast.void_type { + inferred_type = inferred_param_type + } else if need_inferred_type && inferred_type == ast.void_type { inferred_type = param.typ - } else if arg_sym.info is ast.Array && param_sym.info is ast.Array { - mut arg_elem_typ, mut param_elem_typ := arg_sym.info.elem_type, param_sym.info.elem_type - mut arg_elem_sym, mut param_elem_sym := c.table.sym(arg_elem_typ), c.table.sym(param_elem_typ) - for { - if mut arg_elem_sym.info is ast.Array - && mut param_elem_sym.info is ast.Array { - arg_elem_typ, param_elem_typ = arg_elem_sym.info.elem_type, param_elem_sym.info.elem_type - arg_elem_sym, param_elem_sym = c.table.sym(arg_elem_typ), c.table.sym(param_elem_typ) - } else { - if param_elem_sym.name == gt_name { - inferred_type = arg_elem_typ - } - break - } - } - } else if arg_sym.info is ast.ArrayFixed - && param_sym.info is ast.ArrayFixed { - mut arg_elem_typ, mut param_elem_typ := arg_sym.info.elem_type, param_sym.info.elem_type - mut arg_elem_sym, mut param_elem_sym := c.table.sym(arg_elem_typ), c.table.sym(param_elem_typ) - for { - if mut arg_elem_sym.info is ast.ArrayFixed - && mut param_elem_sym.info is ast.ArrayFixed { - arg_elem_typ, param_elem_typ = arg_elem_sym.info.elem_type, param_elem_sym.info.elem_type - arg_elem_sym, param_elem_sym = c.table.sym(arg_elem_typ), c.table.sym(param_elem_typ) - } else { - if param_elem_sym.name == gt_name { - inferred_type = arg_elem_typ - } - break - } - } - } else if arg_sym.info is ast.Map && param_sym.info is ast.Map { - if param_sym.info.key_type.has_flag(.generic) - && c.table.sym(param_sym.info.key_type).name == gt_name { - inferred_type = arg_sym.info.key_type - } - if param_sym.info.value_type.has_flag(.generic) - && c.table.sym(param_sym.info.value_type).name == gt_name { - inferred_type = arg_sym.info.value_type - } } } } } if inferred_type == ast.void_type { - c.error('could not infer generic type `${gt_name}` in interface `${inter_sym.name}`', - pos) + if interface_concrete_types.any(c.type_has_unresolved_generic_parts(it)) { + return interface_type + } + if pos.file_idx != -1 { + c.error('could not infer generic type `${gt_name}` in interface `${inter_sym.name}`', + pos) + } return interface_type } inferred_types << inferred_type @@ -425,7 +375,9 @@ fn (mut c Checker) unwrap_generic_interface(typ ast.Type, interface_type ast.Typ c.need_recheck_generic_fns = true } if method := typ_sym.find_method_with_generic_parent(imethod.name) { - if c.table.register_fn_concrete_types(method.fkey(), inferred_types) { + method_concrete_types := c.concrete_types_for_type_symbol(typ_sym) + if method_concrete_types.len > 0 + && c.table.register_fn_concrete_types(method.fkey(), method_concrete_types) { c.need_recheck_generic_fns = true } } diff --git a/vlib/v/gen/c/fn.v b/vlib/v/gen/c/fn.v index 5379a9660..0ecfc3c74 100644 --- a/vlib/v/gen/c/fn.v +++ b/vlib/v/gen/c/fn.v @@ -3286,7 +3286,12 @@ fn (mut g Gen) generic_fn_call_concrete_types(func ast.Fn, node ast.CallExpr) [] cmp_type = cmp_sym.info.elem_type } } - if cmp_type != g.unwrap_generic(arg_type) { + resolved_arg_type := g.unwrap_generic(arg_type) + if cmp_type != resolved_arg_type { + if g.table.final_sym(cmp_type).kind == .interface + && g.table.does_type_implement_interface(resolved_arg_type, cmp_type) { + continue + } trust_node_concrete_types = false break } diff --git a/vlib/v/tests/generics/generic_interface_nested_struct_infer_test.v b/vlib/v/tests/generics/generic_interface_nested_struct_infer_test.v new file mode 100644 index 000000000..3474a92d0 --- /dev/null +++ b/vlib/v/tests/generics/generic_interface_nested_struct_infer_test.v @@ -0,0 +1,31 @@ +// Regression test for #19618. +// Generic interface inference should bind nested generic struct arguments. + +interface MyInterface[T] { + is_good(a T) bool +} + +struct MyStruct[T] {} + +struct Opt[T] { + x ?T +} + +fn (_ MyStruct[T]) is_good(a Opt[T]) bool { + if _ := a.x { + return true + } + return false +} + +fn check[T](a MyInterface[T], b T) bool { + return a.is_good(b) +} + +fn test_generic_interface_infers_nested_generic_struct_argument() { + a := MyStruct[int]{} + b := Opt[int]{ + x: 6 + } + assert check(a, b) +} -- 2.39.5