From cb523a64ddbc2c6a988045d4d1f65be3dd9f1b5c Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Wed, 25 Mar 2026 16:42:25 +0300 Subject: [PATCH] checker: fix implementing generic function for generic function type (fixes #18037) --- vlib/v/ast/table.v | 154 ++++++++++++++++++ vlib/v/checker/fn.v | 24 +++ .../generic_fn_type_generic_method_test.v | 32 ++++ 3 files changed, 210 insertions(+) create mode 100644 vlib/v/tests/generics/generic_fn_type_generic_method_test.v diff --git a/vlib/v/ast/table.v b/vlib/v/ast/table.v index 54de2382e..113f4bab9 100644 --- a/vlib/v/ast/table.v +++ b/vlib/v/ast/table.v @@ -1742,6 +1742,22 @@ pub fn (mut t Table) find_or_register_generic_inst(parent_typ Type, concrete_typ ) } +fn (t &Table) generic_fn_inst_name(sym &TypeSymbol, concrete_types []Type) string { + mut name := sym.name + '[' + for i, concrete_type in concrete_types { + concrete_sym := t.sym(concrete_type) + if concrete_type.is_ptr() { + name += '&'.repeat(concrete_type.nr_muls()) + } + name += concrete_sym.name + if i < concrete_types.len - 1 { + name += ', ' + } + } + name += ']' + return name +} + pub fn (mut t Table) add_placeholder_type(name string, cname string, language Language) int { mut modname := '' if name.contains('.') { @@ -2301,6 +2317,13 @@ pub fn (mut t Table) convert_generic_type(generic_type Type, generic_names []str } } } + if !sym.info.is_anon && !has_generic { + inst_name := t.generic_fn_inst_name(sym, to_types) + idx := t.find_type_idx(inst_name) + if idx > 0 { + return new_type(idx).derive_add_muls(generic_type).clear_flag(.generic) + } + } func.name = '' func.generic_names = [] idx := t.find_or_register_fn_type(func, true, false) @@ -3217,6 +3240,13 @@ fn (mut t Table) unwrap_generic_type_ex_with_depth(typ Type, generic_names []str has_generic = true } if has_generic { + if !ts.info.is_anon { + inst_name := t.generic_fn_inst_name(ts, concrete_types) + idx := t.find_type_idx(inst_name) + if idx > 0 { + return new_type(idx).derive_add_muls(typ).clear_flag(.generic) + } + } // Clear the name so find_or_register_fn_type registers a new anonymous fn // type with the resolved concrete param/return types, instead of returning // the existing generic fn type entry (matched by name). @@ -3529,6 +3559,121 @@ fn (mut t Table) unwrap_method_types(ts &TypeSymbol, generic_names []string, con } } +fn (mut t Table) specialize_generic_fn_method_type(typ Type, parent_type Type, concrete_type Type, generic_names []string, concrete_types []Type) Type { + if typ.clear_flag(.generic).idx() == parent_type.clear_flag(.generic).idx() { + return concrete_type.derive(typ).clear_flag(.generic) + } + sym := t.sym(typ) + match sym.info { + Array { + dims, elem_type := t.get_array_dims(sym.info) + elem_typ := t.specialize_generic_fn_method_type(elem_type, parent_type, concrete_type, + generic_names, concrete_types) + if elem_typ != elem_type { + idx := t.find_or_register_array_with_dims(elem_typ, dims) + if elem_typ.has_flag(.generic) { + return new_type(idx).derive_add_muls(typ).set_flag(.generic) + } + return new_type(idx).derive_add_muls(typ).clear_flag(.generic) + } + } + ArrayFixed { + elem_typ := t.specialize_generic_fn_method_type(sym.info.elem_type, parent_type, + concrete_type, generic_names, concrete_types) + if elem_typ != sym.info.elem_type { + idx := t.find_or_register_array_fixed(elem_typ, sym.info.size, None{}, + false) + if elem_typ.has_flag(.generic) { + return new_type(idx).derive_add_muls(typ).set_flag(.generic) + } + return new_type(idx).derive_add_muls(typ).clear_flag(.generic) + } + } + Chan { + elem_typ := t.specialize_generic_fn_method_type(sym.info.elem_type, parent_type, + concrete_type, generic_names, concrete_types) + if elem_typ != sym.info.elem_type { + idx := t.find_or_register_chan(elem_typ, elem_typ.nr_muls() > 0) + if elem_typ.has_flag(.generic) { + return new_type(idx).derive_add_muls(typ).set_flag(.generic) + } + return new_type(idx).derive_add_muls(typ).clear_flag(.generic) + } + } + Thread { + ret_typ := t.specialize_generic_fn_method_type(sym.info.return_type, parent_type, + concrete_type, generic_names, concrete_types) + if ret_typ != sym.info.return_type { + idx := t.find_or_register_thread(ret_typ) + if ret_typ.has_flag(.generic) { + return new_type(idx).derive_add_muls(typ).set_flag(.generic) + } + return new_type(idx).derive_add_muls(typ).clear_flag(.generic) + } + } + MultiReturn { + mut resolved_types := []Type{cap: sym.info.types.len} + mut type_changed := false + for ret_typ in sym.info.types { + resolved_typ := t.specialize_generic_fn_method_type(ret_typ, parent_type, + concrete_type, generic_names, concrete_types) + if resolved_typ != ret_typ { + type_changed = true + } + resolved_types << resolved_typ + } + if type_changed { + idx := t.find_or_register_multi_return(resolved_types) + if resolved_types.any(it.has_flag(.generic)) { + return new_type(idx).derive_add_muls(typ).set_flag(.generic) + } + return new_type(idx).derive_add_muls(typ).clear_flag(.generic) + } + } + Map { + key_typ := t.specialize_generic_fn_method_type(sym.info.key_type, parent_type, + concrete_type, generic_names, concrete_types) + value_typ := t.specialize_generic_fn_method_type(sym.info.value_type, parent_type, + concrete_type, generic_names, concrete_types) + if key_typ != sym.info.key_type || value_typ != sym.info.value_type { + idx := t.find_or_register_map(key_typ, value_typ) + if key_typ.has_flag(.generic) || value_typ.has_flag(.generic) { + return new_type(idx).derive_add_muls(typ).set_flag(.generic) + } + return new_type(idx).derive_add_muls(typ).clear_flag(.generic) + } + } + else {} + } + if typ.has_flag(.generic) { + if resolved_typ := t.convert_generic_type(typ, generic_names, concrete_types) { + return resolved_typ + } + } + return typ +} + +fn (mut t Table) specialize_generic_fn_type_methods(parent_type Type, mut concrete_sym TypeSymbol, generic_names []string, concrete_types []Type) { + parent_sym := t.sym(parent_type) + if parent_sym.info !is FnType || parent_sym.methods.len == 0 { + return + } + concrete_type := idx_to_type(concrete_sym.idx) + concrete_sym.methods = []Fn{} + for method in parent_sym.methods { + mut concrete_method := method.new_method_with_receiver_type(concrete_type) + concrete_method.generic_names = method.generic_names.clone() + concrete_method.return_type = t.specialize_generic_fn_method_type(method.return_type, + parent_type, concrete_type, generic_names, concrete_types) + for i in 1 .. concrete_method.params.len { + concrete_method.params[i].typ = t.specialize_generic_fn_method_type(method.params[i].typ, + parent_type, concrete_type, generic_names, concrete_types) + } + concrete_method.receiver_type = concrete_method.params[0].typ + concrete_sym.register_method(concrete_method) + } +} + // generic struct instantiations to concrete types pub fn (mut t Table) generic_insts_to_concrete() { for mut sym in t.type_symbols { @@ -3750,8 +3895,17 @@ pub fn (mut t Table) generic_insts_to_concrete() { ...parent_info func: function } + sym.parent_idx = info.parent_idx sym.is_pub = true sym.kind = parent.kind + sym.generic_types = info.concrete_types.clone() + for method in parent.methods { + if method.generic_names.len == info.concrete_types.len { + t.register_fn_concrete_types(method.fkey(), info.concrete_types) + } + } + t.specialize_generic_fn_type_methods(new_type(info.parent_idx).set_flag(.generic), mut + sym, parent_info.func.generic_names, info.concrete_types) } else {} } diff --git a/vlib/v/checker/fn.v b/vlib/v/checker/fn.v index b652fa322..fd1ac6d55 100644 --- a/vlib/v/checker/fn.v +++ b/vlib/v/checker/fn.v @@ -1669,6 +1669,18 @@ fn (mut c Checker) fn_call(mut node ast.CallExpr, mut continue_check &bool) ast. // XTODO document if typ != 0 { + if node.concrete_types.len == 0 && typ.has_flag(.generic) + && c.table.cur_fn != unsafe { nil } && c.table.cur_fn.is_method + && node.name == c.table.cur_fn.receiver.name + && c.table.cur_fn.receiver.typ.has_flag(.generic) + && c.table.cur_fn.generic_names.len == c.table.cur_concrete_types.len { + if fn_typ := c.table.convert_generic_type(typ, c.table.cur_fn.generic_names, + c.table.cur_concrete_types) + { + typ = fn_typ + node.fn_var_type = fn_typ + } + } generic_vts := c.table.final_sym(typ) if generic_vts.info is ast.FnType { func = generic_vts.info.func @@ -3047,6 +3059,18 @@ fn (mut c Checker) method_call(mut node ast.CallExpr, mut continue_check &bool) else {} } } + ast.FnType { + if rec_sym.parent_idx > 0 { + parent_sym := c.table.sym(ast.idx_to_type(rec_sym.parent_idx)) + if parent_sym.info is ast.FnType { + rec_concrete_types = rec_sym.generic_types.clone() + if rec_concrete_types.len > 0 + && method_generic_names_len == rec_concrete_types.len { + node.concrete_types = rec_concrete_types + } + } + } + } else {} } mut concrete_types := node.concrete_types.map(c.unwrap_generic(it)) diff --git a/vlib/v/tests/generics/generic_fn_type_generic_method_test.v b/vlib/v/tests/generics/generic_fn_type_generic_method_test.v new file mode 100644 index 000000000..83c6e4fbc --- /dev/null +++ b/vlib/v/tests/generics/generic_fn_type_generic_method_test.v @@ -0,0 +1,32 @@ +struct ParseResult[T] { + result T + rest string +} + +type ParseFunction[T] = fn (string) !ParseResult[T] + +fn (f ParseFunction[T]) parse[T](input string) !ParseResult[T] { + return f(input) +} + +fn literal(l string) ParseFunction[string] { + return fn [l] (input string) !ParseResult[string] { + if !input.starts_with(l) { + return error(input) + } + return ParseResult[string]{ + result: l + rest: input.all_after_first(l) + } + } +} + +fn test_generic_fn_type_generic_method() { + l_func := literal('start') + val1 := l_func.parse('start test') or { ParseResult[string]{} } + val2 := l_func.parse('test') or { ParseResult[string]{} } + assert val1.result == 'start' + assert val1.rest == ' test' + assert val2.result == '' + assert val2.rest == '' +} -- 2.39.5