From 2a6ed1aecf993df3f59cdbd455e27c7e8dcaea36 Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Wed, 25 Mar 2026 16:42:15 +0300 Subject: [PATCH] checker: fix generic type mismatch with generic functions from multiple sources (fixes #25217) --- vlib/v/checker/checker.v | 12 ++++ vlib/v/checker/fn.v | 19 ++++++ vlib/v/gen/c/assign.v | 24 ++++++- vlib/v/gen/c/fn.v | 7 +++ .../generics_fn_field_multi_instance_test.v | 63 +++++++++++++++++++ 5 files changed, 123 insertions(+), 2 deletions(-) create mode 100644 vlib/v/tests/generics/generics_fn_field_multi_instance_test.v diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index 60ca6b771..bea7e1697 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -5439,6 +5439,18 @@ fn (mut c Checker) ident(mut node ast.Ident) ast.Type { } return ast.void_type } else if node.kind in [.constant, .global, .variable] { + if node.kind == .variable && c.table.cur_concrete_types.len > 0 { + pobj := node.scope.find_ptr(node.name) + if pobj != unsafe { nil } { + obj := *pobj + if obj is ast.Var { + mut info := node.info as ast.IdentVar + info.typ = obj.typ + node.info = info + node.obj = obj + } + } + } // second use info := node.info as ast.IdentVar mut info_typ := info.typ diff --git a/vlib/v/checker/fn.v b/vlib/v/checker/fn.v index 58cfebd1a..7d67d0f84 100644 --- a/vlib/v/checker/fn.v +++ b/vlib/v/checker/fn.v @@ -2787,6 +2787,25 @@ fn (mut c Checker) method_call(mut node ast.CallExpr, mut continue_check &bool) node.return_type = info.func.return_type return info.func.return_type } + match left_sym.info { + ast.Struct, ast.Interface, ast.SumType { + if left_sym.info.parent_type != 0 { + parent_sym := c.table.sym(left_sym.info.parent_type) + if generic_field := c.table.find_field_with_embeds(parent_sym, + method_name) + { + generic_field_sym := c.table.sym(generic_field.typ) + if generic_field_sym.info is ast.FnType { + generic_ret := generic_field_sym.info.func.return_type + if generic_ret.has_flag(.generic) { + node.return_type_generic = generic_ret + } + } + } + } + } + else {} + } node.return_type = info.func.return_type mut earg_types := []ast.Type{} diff --git a/vlib/v/gen/c/assign.v b/vlib/v/gen/c/assign.v index 56de6f45c..d734552fd 100644 --- a/vlib/v/gen/c/assign.v +++ b/vlib/v/gen/c/assign.v @@ -458,6 +458,14 @@ fn (mut g Gen) assign_stmt(node_ ast.AssignStmt) { } } val := node.right[i] + if is_decl && g.cur_concrete_types.len > 0 && val is ast.CallExpr + && val.return_type_generic != 0 { + resolved_val_type := g.unwrap_generic(val.return_type_generic).clear_option_and_result() + if resolved_val_type != ast.void_type && !resolved_val_type.has_flag(.generic) { + var_type = ast.mktyp(resolved_val_type) + val_type = resolved_val_type + } + } mut is_call := false mut gen_or := false mut blank_assign := false @@ -647,8 +655,20 @@ fn (mut g Gen) assign_stmt(node_ ast.AssignStmt) { g.type_resolver.update_ct_type(left.name, var_type) } } else if left.obj.ct_type_var == .generic_var && val is ast.CallExpr { - fn_ret_type := g.resolve_return_type(val) - if fn_ret_type != ast.void_type { + if val.return_type_generic != 0 + && val.return_type_generic.has_flag(.generic) { + mut fn_ret_type := g.unwrap_generic(val.return_type_generic).clear_option_and_result() + if fn_ret_type == ast.void_type || fn_ret_type.has_flag(.generic) { + fn_ret_type = g.resolve_return_type(val) + } + if fn_ret_type != ast.void_type { + var_type = fn_ret_type + val_type = var_type + left.obj.typ = var_type + g.assign_ct_type[val.pos.pos] = var_type + } + } else if val.is_static_method && val.left_type.has_flag(.generic) { + fn_ret_type := g.resolve_return_type(val) var_type = fn_ret_type val_type = var_type left.obj.typ = var_type diff --git a/vlib/v/gen/c/fn.v b/vlib/v/gen/c/fn.v index b0e020670..3a5d9a0de 100644 --- a/vlib/v/gen/c/fn.v +++ b/vlib/v/gen/c/fn.v @@ -1736,6 +1736,13 @@ fn (mut g Gen) call_expr(node ast.CallExpr) { } else if node.return_type_generic != 0 { unwrapped_ret_typ := g.unwrap_generic(node.return_type_generic) if !unwrapped_ret_typ.has_flag(.generic) { + ret_typ = if node.return_type.has_flag(.result) { + unwrapped_ret_typ.set_flag(.result) + } else if node.return_type.has_flag(.option) { + unwrapped_ret_typ.set_flag(.option) + } else { + unwrapped_ret_typ + } ret_sym := g.table.sym(unwrapped_ret_typ) if ret_sym.info is ast.Array && g.table.sym(node.return_type_generic).kind == .array { // Make []T returns T type when array was supplied to T diff --git a/vlib/v/tests/generics/generics_fn_field_multi_instance_test.v b/vlib/v/tests/generics/generics_fn_field_multi_instance_test.v new file mode 100644 index 000000000..757e47316 --- /dev/null +++ b/vlib/v/tests/generics/generics_fn_field_multi_instance_test.v @@ -0,0 +1,63 @@ +type ProcedureHandler = fn () !string + +struct Procedure[T, U] { + value T + function fn (T) !U = unsafe { nil } +} + +fn (p Procedure[T, U]) handle() !string { + result := p.function(p.value) or { return err } + return encode[U](result) +} + +fn encode[T](value T) string { + $if T is Dog { + return 'dog:${value.name}' + } + $if T is Cat { + return 'cat:${value.name}' + } + return 'unknown' +} + +struct Dog { + name string +} + +struct NewDog { + name string +} + +fn make_dog(value NewDog) !Dog { + return Dog{ + name: value.name + } +} + +struct Cat { + name string +} + +struct NewCat { + name string +} + +fn make_cat(value NewCat) !Cat { + return Cat{ + name: value.name + } +} + +fn test_generic_fn_field_multi_instance_specialization() { + dog := Procedure[NewDog, Dog]{ + value: NewDog{'leo'} + function: make_dog + } + cat := Procedure[NewCat, Cat]{ + value: NewCat{'milo'} + function: make_cat + } + handlers := [ProcedureHandler(dog.handle), ProcedureHandler(cat.handle)] + assert (handlers[0]() or { panic(err) }) == 'dog:leo' + assert (handlers[1]() or { panic(err) }) == 'cat:milo' +} -- 2.39.5