From 84637a32ff9141423eef2e3192908abf92b05eb5 Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Wed, 25 Mar 2026 16:42:24 +0300 Subject: [PATCH] cgen: fix c compilation error using type parameters in function fields (fixes #26629) --- vlib/v/checker/checker.v | 8 + vlib/v/checker/fn.v | 47 ++++- vlib/v/gen/c/fn.v | 183 ++++++++++-------- .../generic_struct_fn_field_closure_test.v | 24 +++ 4 files changed, 178 insertions(+), 84 deletions(-) create mode 100644 vlib/v/tests/generics/generic_struct_fn_field_closure_test.v diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index b6c238083..d87a0fb85 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -97,6 +97,8 @@ pub mut: inside_integer_literal_cast bool // true inside `int(123)` cur_struct_generic_types []ast.Type cur_struct_concrete_types []ast.Type + anon_fn_generic_names []string + anon_fn_concrete_types []ast.Type skip_flags bool // should `#flag` and `#include` be skipped fn_level int // 0 for the top level, 1 for `fn abc() {}`, 2 for a nested fn, etc smartcast_mut_pos token.Pos // match mut foo, if mut foo is Foo @@ -3947,6 +3949,12 @@ fn (mut c Checker) unwrap_generic(typ ast.Type) ast.Type { return t_typ } } + if c.inside_anon_fn && c.anon_fn_generic_names.len > 0 + && c.anon_fn_generic_names.len == c.anon_fn_concrete_types.len { + if t_typ := c.table.convert_generic_type(typ, c.anon_fn_generic_names, c.anon_fn_concrete_types) { + return t_typ + } + } if c.table.cur_fn != unsafe { nil } { if t_typ := c.table.convert_generic_type(typ, c.table.cur_fn.generic_names, c.table.cur_concrete_types) diff --git a/vlib/v/checker/fn.v b/vlib/v/checker/fn.v index 134e91de5..4b78222c6 100644 --- a/vlib/v/checker/fn.v +++ b/vlib/v/checker/fn.v @@ -940,11 +940,15 @@ fn (mut c Checker) anon_fn(mut node ast.AnonFn) ast.Type { keep_fn := c.table.cur_fn keep_inside_anon := c.inside_anon_fn keep_anon_fn := c.cur_anon_fn + keep_anon_fn_generic_names := c.anon_fn_generic_names.clone() + keep_anon_fn_concrete_types := c.anon_fn_concrete_types.clone() c.table.used_features.anon_fn = true defer { c.table.cur_fn = keep_fn c.inside_anon_fn = keep_inside_anon c.cur_anon_fn = keep_anon_fn + c.anon_fn_generic_names = keep_anon_fn_generic_names + c.anon_fn_concrete_types = keep_anon_fn_concrete_types } if node.decl.no_body { c.error('anonymous function must declare a body', node.decl.pos) @@ -954,6 +958,7 @@ fn (mut c Checker) anon_fn(mut node ast.AnonFn) ast.Type { c.error('use `_` to name an unused parameter', param.pos) } } + mut can_use_outer_generic_context := node.decl.generic_names.len > 0 c.table.cur_fn = unsafe { &node.decl } c.inside_anon_fn = true c.cur_anon_fn = unsafe { &node } @@ -993,9 +998,10 @@ fn (mut c Checker) anon_fn(mut node ast.AnonFn) ast.Type { ret_typ := parent_var_sym.info.func.return_type if c.type_has_unresolved_generic_parts(ret_typ) { generic_names := c.table.generic_type_names(ret_typ) - curr_list := c.table.cur_fn.generic_names.join(', ') + curr_list := node.decl.generic_names.join(', ') for name in generic_names { - if name !in c.table.cur_fn.generic_names { + if name !in node.decl.generic_names { + can_use_outer_generic_context = false c.error('Add the generic type `${name}` to the anon fn generic list type, that is currently `[${curr_list}]`', var.pos) } @@ -1023,6 +1029,24 @@ fn (mut c Checker) anon_fn(mut node ast.AnonFn) ast.Type { } node.decl.scope.update_var_type(var.name, var.typ) } + c.anon_fn_generic_names = []string{} + c.anon_fn_concrete_types = []ast.Type{} + if can_use_outer_generic_context && keep_fn != unsafe { nil } + && keep_fn.generic_names.len == c.table.cur_concrete_types.len { + for generic_name in node.decl.generic_names { + generic_idx := keep_fn.generic_names.index(generic_name) + if generic_idx >= 0 { + c.anon_fn_generic_names << generic_name + c.anon_fn_concrete_types << c.table.cur_concrete_types[generic_idx] + } + } + for i, generic_name in keep_fn.generic_names { + if generic_name !in c.anon_fn_generic_names { + c.anon_fn_generic_names << generic_name + c.anon_fn_concrete_types << c.table.cur_concrete_types[i] + } + } + } if has_generic && node.decl.generic_names.len == 0 { c.error('generic closure fn must specify type parameter, e.g. fn [foo] [T]()', node.decl.pos) @@ -2510,7 +2534,6 @@ fn (mut c Checker) check_type_and_visibility(name string, type_idx int, expected return true } -<<<<<<< HEAD fn (c &Checker) is_valid_os_file_struct_io_type(typ ast.Type) bool { if typ.nr_muls() > 0 || typ.has_option_or_result() { return false @@ -2792,7 +2815,16 @@ fn (mut c Checker) method_call(mut node ast.CallExpr, mut continue_check &bool) // TODO: can we use SelectorExpr for all? this dosent really belong here if field := c.table.find_field_with_embeds(left_sym, method_name) { mut field_typ := field.typ - if field.typ.has_flag(.option) { + if left_sym.info is ast.Struct && left_sym.info.generic_types.len > 0 + && left_sym.info.generic_types.len == left_sym.info.concrete_types.len { + generic_names := c.table.get_generic_names(left_sym.info.generic_types) + if resolved_typ := c.table.convert_generic_type(field_typ, generic_names, + left_sym.info.concrete_types) + { + field_typ = resolved_typ + } + } + if field_typ.has_flag(.option) { // unwrapped callback (if f.func != none {}) scope_field := node.scope.find_struct_field(node.left.str(), node.left_type, method_name) @@ -2809,9 +2841,11 @@ fn (mut c Checker) method_call(mut node ast.CallExpr, mut continue_check &bool) node.is_method = false node.is_field = true info := field_sym.info as ast.FnType - c.check_expected_arg_count(mut node, info.func) or { node.return_type = info.func.return_type + if info.func.return_type.has_flag(.generic) { + node.return_type_generic = info.func.return_type + } return info.func.return_type } match left_sym.info { @@ -2834,6 +2868,9 @@ fn (mut c Checker) method_call(mut node ast.CallExpr, mut continue_check &bool) else {} } node.return_type = info.func.return_type + if info.func.return_type.has_flag(.generic) { + node.return_type_generic = info.func.return_type + } mut earg_types := []ast.Type{} for i, mut arg in node.args { diff --git a/vlib/v/gen/c/fn.v b/vlib/v/gen/c/fn.v index 5ee0e8923..2acc6286c 100644 --- a/vlib/v/gen/c/fn.v +++ b/vlib/v/gen/c/fn.v @@ -1167,9 +1167,23 @@ fn (mut g Gen) c_fn_name(node &ast.FnDecl) string { const closure_ctx = '_V_closure_ctx' +fn (g &Gen) anon_fn_generic_names(generic_names []string) []string { + if g.cur_fn == unsafe { nil } || g.cur_fn.generic_names.len == 0 + || g.cur_concrete_types.len == 0 { + return generic_names.clone() + } + mut names := g.cur_fn.generic_names.clone() + for generic_name in generic_names { + if generic_name !in names { + names << generic_name + } + } + return names +} + fn (mut g Gen) gen_closure_fn_name(node ast.AnonFn) string { mut fn_name := node.decl.name - if node.decl.generic_names.len > 0 { + if g.anon_fn_generic_names(node.decl.generic_names).len > 0 { fn_name = g.generic_fn_name(g.cur_concrete_types, fn_name) } if node.has_ct_var { @@ -1225,7 +1239,12 @@ fn (mut g Gen) c_call_name(node ast.CallExpr, cname string) string { fn (mut g Gen) closure_ctx(node ast.FnDecl) string { mut fn_name := node.name - if node.generic_names.len > 0 { + generic_names := if node.is_anon { + g.anon_fn_generic_names(node.generic_names) + } else { + node.generic_names + } + if generic_names.len > 0 { fn_name = g.generic_fn_name(g.cur_concrete_types, fn_name) } return 'struct _V_${fn_name}_Ctx' @@ -1417,10 +1436,14 @@ fn (mut g Gen) gen_anon_fn_decl(mut node ast.AnonFn) { was_anon_fn := g.anon_fn prev_stmt_path_pos := g.stmt_path_pos.clone() prev_skip_stmt_pos := g.skip_stmt_pos + decl := ast.FnDecl{ + ...node.decl + generic_names: g.anon_fn_generic_names(node.decl.generic_names) + } g.stmt_path_pos = [] g.skip_stmt_pos = false g.anon_fn = node - g.fn_decl(node.decl) + g.fn_decl(decl) g.anon_fn = was_anon_fn g.skip_stmt_pos = prev_skip_stmt_pos g.stmt_path_pos = prev_stmt_path_pos @@ -4741,94 +4764,96 @@ fn (mut g Gen) fn_call(node ast.CallExpr) { // g.write(cur_line + ' /* <== af cur line*/') // } mut is_fn_var := false - if obj := node.scope.find_var(node.name) { - // Temp fix generate call fn error when the struct type of sumtype - // has the fn field and is same to the struct name. - mut is_cast_needed := true - mut smartcast_types := obj.smartcasts.clone() - if obj.orig_type.has_flag(.option) { - mut unwrapped_fn_var_type := ast.no_type - mut resolved_parent_value_type := ast.no_type - if node.scope.parent != unsafe { nil } { - if parent_var := node.scope.parent.find_var(node.name) { - resolved_parent_value_type = g.unwrap_generic(g.recheck_concrete_type(parent_var.typ)).clear_option_and_result() - if resolved_parent_value_type != 0 - && g.table.final_sym(resolved_parent_value_type).kind == .function { - unwrapped_fn_var_type = resolved_parent_value_type + if !is_selector_call { + if obj := node.scope.find_var(node.name) { + // Temp fix generate call fn error when the struct type of sumtype + // has the fn field and is same to the struct name. + mut is_cast_needed := true + mut smartcast_types := obj.smartcasts.clone() + if obj.orig_type.has_flag(.option) { + mut unwrapped_fn_var_type := ast.no_type + mut resolved_parent_value_type := ast.no_type + if node.scope.parent != unsafe { nil } { + if parent_var := node.scope.parent.find_var(node.name) { + resolved_parent_value_type = g.unwrap_generic(g.recheck_concrete_type(parent_var.typ)).clear_option_and_result() + if resolved_parent_value_type != 0 + && g.table.final_sym(resolved_parent_value_type).kind == .function { + unwrapped_fn_var_type = resolved_parent_value_type + } } } - } - mut resolved_expr_value_type := ast.no_type - if unwrapped_fn_var_type == 0 && obj.expr !is ast.EmptyExpr { - resolved_expr_value_type = g.unwrap_generic(g.recheck_concrete_type(g.resolved_expr_type(obj.expr, - obj.typ))).clear_option_and_result() - if resolved_expr_value_type != 0 - && g.table.final_sym(resolved_expr_value_type).kind == .function { - unwrapped_fn_var_type = resolved_expr_value_type + mut resolved_expr_value_type := ast.no_type + if unwrapped_fn_var_type == 0 && obj.expr !is ast.EmptyExpr { + resolved_expr_value_type = g.unwrap_generic(g.recheck_concrete_type(g.resolved_expr_type(obj.expr, + obj.typ))).clear_option_and_result() + if resolved_expr_value_type != 0 + && g.table.final_sym(resolved_expr_value_type).kind == .function { + unwrapped_fn_var_type = resolved_expr_value_type + } } - } - resolved_scope_type := g.resolved_scope_var_type(ast.Ident{ - name: node.name - scope: node.scope - }) - if unwrapped_fn_var_type == 0 && resolved_scope_type != 0 { - resolved_scope_value_type := g.unwrap_generic(g.recheck_concrete_type(resolved_scope_type)).clear_option_and_result() - if resolved_scope_value_type != 0 - && g.table.final_sym(resolved_scope_value_type).kind == .function { - unwrapped_fn_var_type = resolved_scope_value_type + resolved_scope_type := g.resolved_scope_var_type(ast.Ident{ + name: node.name + scope: node.scope + }) + if unwrapped_fn_var_type == 0 && resolved_scope_type != 0 { + resolved_scope_value_type := g.unwrap_generic(g.recheck_concrete_type(resolved_scope_type)).clear_option_and_result() + if resolved_scope_value_type != 0 + && g.table.final_sym(resolved_scope_value_type).kind == .function { + unwrapped_fn_var_type = resolved_scope_value_type + } + } + if unwrapped_fn_var_type == 0 { + unwrapped_fn_var_type = g.unwrap_generic(g.recheck_concrete_type(obj.typ.clear_option_and_result())) + } + if unwrapped_fn_var_type != 0 + && g.table.final_sym(unwrapped_fn_var_type).kind == .function { + smartcast_types = [unwrapped_fn_var_type] } } - if unwrapped_fn_var_type == 0 { - unwrapped_fn_var_type = g.unwrap_generic(g.recheck_concrete_type(obj.typ.clear_option_and_result())) - } - if unwrapped_fn_var_type != 0 - && g.table.final_sym(unwrapped_fn_var_type).kind == .function { - smartcast_types = [unwrapped_fn_var_type] - } - } - if node.is_method && node.left_type != 0 { - left_sym := g.table.sym(node.left_type) - if left_sym.kind == .struct && node.name == obj.name { - is_cast_needed = false - } - } - if smartcast_types.len > 0 && is_cast_needed { - for typ in smartcast_types { - sym := g.table.sym(g.unwrap_generic(typ)) - if obj.orig_type.has_flag(.option) && sym.kind == .function { - g.write('(*(${sym.cname}*)(') - } else { - g.write('(*(${sym.cname})(') + if node.is_method && node.left_type != 0 { + left_sym := g.table.sym(node.left_type) + if left_sym.kind == .struct && node.name == obj.name { + is_cast_needed = false } } - for i, typ in smartcast_types { - cast_sym := g.table.sym(g.unwrap_generic(typ)) - mut is_ptr := false - if i == 0 { - if obj.is_inherited { - g.write(closure_ctx + '->' + c_name(node.name)) + if smartcast_types.len > 0 && is_cast_needed { + for typ in smartcast_types { + sym := g.table.sym(g.unwrap_generic(typ)) + if obj.orig_type.has_flag(.option) && sym.kind == .function { + g.write('(*(${sym.cname}*)(') } else { - g.write(node.name) - } - if obj.orig_type.is_ptr() { - is_ptr = true + g.write('(*(${sym.cname})(') } } - dot := if is_ptr { '->' } else { '.' } - if cast_sym.info is ast.Aggregate { - sym := g.table.sym(cast_sym.info.types[g.aggregate_type_idx]) - g.write('${dot}_${sym.cname}') - } else if cast_sym.kind == .function && obj.orig_type.has_flag(.option) { - g.write('.data') - } else { - g.write('${dot}_${cast_sym.cname}') + for i, typ in smartcast_types { + cast_sym := g.table.sym(g.unwrap_generic(typ)) + mut is_ptr := false + if i == 0 { + if obj.is_inherited { + g.write(closure_ctx + '->' + c_name(node.name)) + } else { + g.write(node.name) + } + if obj.orig_type.is_ptr() { + is_ptr = true + } + } + dot := if is_ptr { '->' } else { '.' } + if cast_sym.info is ast.Aggregate { + sym := g.table.sym(cast_sym.info.types[g.aggregate_type_idx]) + g.write('${dot}_${sym.cname}') + } else if cast_sym.kind == .function && obj.orig_type.has_flag(.option) { + g.write('.data') + } else { + g.write('${dot}_${cast_sym.cname}') + } + g.write('))') } - g.write('))') + is_fn_var = true + } else if obj.is_inherited { + g.write(closure_ctx + '->' + c_name(node.name)) + is_fn_var = true } - is_fn_var = true - } else if obj.is_inherited { - g.write(closure_ctx + '->' + c_name(node.name)) - is_fn_var = true } } if !is_fn_var { diff --git a/vlib/v/tests/generics/generic_struct_fn_field_closure_test.v b/vlib/v/tests/generics/generic_struct_fn_field_closure_test.v new file mode 100644 index 000000000..f41c3bb8c --- /dev/null +++ b/vlib/v/tests/generics/generic_struct_fn_field_closure_test.v @@ -0,0 +1,24 @@ +struct Foo[U] { + f fn (string) U @[required] +} + +fn map_foo[T, U](f Foo[T], m fn (T) U) Foo[U] { + return Foo[U]{ + f: fn [f, m] [U](s string) U { + v := f.f(s) + return m(v) + } + } +} + +fn test_generic_struct_fn_field_closure() { + f := Foo[string]{ + f: fn (s string) string { + return s + } + } + v := map_foo(f, fn (_ string) int { + return -1 + }) + assert v.f('foo') == -1 +} -- 2.39.5