From 7eb45b4f37d46066d9f3bb474530cbc8dfcb8e02 Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Fri, 24 Apr 2026 01:19:22 +0300 Subject: [PATCH] cgen: fix C errors with generic fn type (fixes #26156) --- vlib/v/checker/fn.v | 46 ++++++++++++++++--- vlib/v/gen/c/cgen.v | 7 +++ vlib/v/gen/c/fn.v | 26 +++++++---- .../tests/generics/generic_lambda_expr_test.v | 14 ++++++ 4 files changed, 78 insertions(+), 15 deletions(-) diff --git a/vlib/v/checker/fn.v b/vlib/v/checker/fn.v index f97e8c5a1..a317df23c 100644 --- a/vlib/v/checker/fn.v +++ b/vlib/v/checker/fn.v @@ -2767,6 +2767,10 @@ fn (mut c Checker) fn_call(mut node ast.CallExpr, mut continue_check &bool) ast. c.handle_generic_lambda_arg(node, func.generic_names, mut call_arg.expr) continue } + if mut call_arg.expr is ast.AnonFn { + c.handle_generic_anon_fn_arg(node, func.generic_names, mut + call_arg.expr) + } if c.is_optional_array_arg_compatible(utyp, unwrap_typ) { continue } @@ -2776,6 +2780,8 @@ fn (mut c Checker) fn_call(mut node ast.CallExpr, mut continue_check &bool) ast. // still set call_ctx on lambda args for generic context in cgen: if mut call_arg.expr is ast.LambdaExpr { c.handle_generic_lambda_arg(node, func.generic_names, mut call_arg.expr) + } else if mut call_arg.expr is ast.AnonFn { + c.handle_generic_anon_fn_arg(node, func.generic_names, mut call_arg.expr) } } } @@ -3811,6 +3817,8 @@ fn (mut c Checker) method_call(mut node ast.CallExpr, mut continue_check &bool) if mut arg.expr is ast.LambdaExpr { // Calling fn is generic and lambda arg also is generic c.handle_generic_lambda_arg(node, method.generic_names, mut arg.expr) + } else if mut arg.expr is ast.AnonFn { + c.handle_generic_anon_fn_arg(node, method.generic_names, mut arg.expr) } param_typ_sym := c.table.sym(exp_arg_typ) if param_typ_sym.kind == .struct && got_arg_typ !in [ast.voidptr_type, ast.nil_type] @@ -3869,8 +3877,10 @@ fn (mut c Checker) method_call(mut node ast.CallExpr, mut continue_check &bool) for i, mut arg in node.args { if mut arg.expr is ast.LambdaExpr { c.handle_generic_lambda_arg(node, method.generic_names, mut arg.expr) - node.args[i] = arg + } else if mut arg.expr is ast.AnonFn { + c.handle_generic_anon_fn_arg(node, method.generic_names, mut arg.expr) } + node.args[i] = arg } } if concrete_types.len == method_generic_names_len && concrete_types.all(!it.has_flag(.generic)) { @@ -3915,19 +3925,26 @@ fn (mut c Checker) method_call(mut node ast.CallExpr, mut continue_check &bool) } fn (c &Checker) generic_lambda_concrete_types(lambda &ast.LambdaExpr, caller_generic_names []string, caller_concrete_types []ast.Type) []ast.Type { - if lambda == unsafe { nil } || lambda.func == unsafe { nil } - || lambda.func.decl.generic_names.len == 0 { + if lambda == unsafe { nil } || lambda.func == unsafe { nil } { return []ast.Type{} } - if caller_concrete_types.len == lambda.func.decl.generic_names.len + return c.generic_callback_concrete_types(lambda.func.decl.generic_names, caller_generic_names, + caller_concrete_types) +} + +fn (c &Checker) generic_callback_concrete_types(generic_names []string, caller_generic_names []string, caller_concrete_types []ast.Type) []ast.Type { + if generic_names.len == 0 { + return []ast.Type{} + } + if caller_concrete_types.len == generic_names.len && (caller_generic_names.len == 0 || caller_generic_names.len == caller_concrete_types.len) { return caller_concrete_types.clone() } if caller_generic_names.len == 0 || caller_generic_names.len != caller_concrete_types.len { return []ast.Type{} } - mut concrete_types := []ast.Type{cap: lambda.func.decl.generic_names.len} - for generic_name in lambda.func.decl.generic_names { + mut concrete_types := []ast.Type{cap: generic_names.len} + for generic_name in generic_names { idx := caller_generic_names.index(generic_name) if idx < 0 || idx >= caller_concrete_types.len { return []ast.Type{} @@ -3959,6 +3976,23 @@ fn (mut c Checker) handle_generic_lambda_arg(node &ast.CallExpr, generic_names [ } } +fn (mut c Checker) handle_generic_anon_fn_arg(node &ast.CallExpr, generic_names []string, mut anon ast.AnonFn) { + if node.concrete_types.len == 0 || anon.decl.generic_names.len == 0 { + return + } + anon_concrete_types := c.generic_callback_concrete_types(anon.decl.generic_names, + generic_names, node.concrete_types) + if anon_concrete_types.len == 0 { + return + } + if anon.decl.fkey() !in c.table.fn_generic_types { + c.table.register_fn_generic_types(anon.decl.fkey()) + } + if c.table.register_fn_concrete_types(anon.decl.fkey(), anon_concrete_types) { + anon.decl.ninstances++ + } +} + fn (mut c Checker) resolve_short_syntax_call_arg_type(arg ast.CallArg, param_typ ast.Type, generic_names []string, concrete_types []ast.Type) ast.Type { if !param_typ.has_flag(.generic) || generic_names.len == 0 || generic_names.len != concrete_types.len { diff --git a/vlib/v/gen/c/cgen.v b/vlib/v/gen/c/cgen.v index 41a306193..53756cedc 100644 --- a/vlib/v/gen/c/cgen.v +++ b/vlib/v/gen/c/cgen.v @@ -5503,7 +5503,14 @@ fn (mut g Gen) expr(node_ ast.Expr) { g.error('g.expr(): unhandled EmptyExpr', token.Pos{}) } ast.AnonFn { + save_cur_concrete_types := g.cur_concrete_types + call_concrete := g.active_call_generic_concrete_types(node.decl.generic_names) + if call_concrete.len > 0 && !call_concrete.any(it.has_flag(.generic) + || g.type_has_unresolved_generic_parts(it)) { + g.cur_concrete_types = call_concrete + } g.gen_anon_fn(mut node) + g.cur_concrete_types = save_cur_concrete_types } ast.ArrayDecompose { g.expr(node.expr) diff --git a/vlib/v/gen/c/fn.v b/vlib/v/gen/c/fn.v index 27c09e677..216294148 100644 --- a/vlib/v/gen/c/fn.v +++ b/vlib/v/gen/c/fn.v @@ -3752,19 +3752,27 @@ fn (g &Gen) has_active_call_generic_context() bool { && g.active_call_generic_names.len == g.active_call_concrete_types.len } +fn (g &Gen) active_call_generic_concrete_types(generic_names []string) []ast.Type { + if generic_names.len == 0 || !g.has_active_call_generic_context() { + return []ast.Type{} + } + mut concrete_types := []ast.Type{cap: generic_names.len} + for generic_name in generic_names { + idx := g.active_call_generic_names.index(generic_name) + if idx < 0 || idx >= g.active_call_concrete_types.len { + return []ast.Type{} + } + concrete_types << g.active_call_concrete_types[idx] + } + return concrete_types +} + fn (g &Gen) active_call_lambda_concrete_types(node ast.LambdaExpr) []ast.Type { if node.func == unsafe { nil } || node.func.decl.generic_names.len == 0 { return []ast.Type{} } - if g.has_active_call_generic_context() { - mut concrete_types := []ast.Type{cap: node.func.decl.generic_names.len} - for generic_name in node.func.decl.generic_names { - idx := g.active_call_generic_names.index(generic_name) - if idx < 0 || idx >= g.active_call_concrete_types.len { - return []ast.Type{} - } - concrete_types << g.active_call_concrete_types[idx] - } + concrete_types := g.active_call_generic_concrete_types(node.func.decl.generic_names) + if concrete_types.len > 0 { return concrete_types } if node.call_ctx != unsafe { nil } diff --git a/vlib/v/tests/generics/generic_lambda_expr_test.v b/vlib/v/tests/generics/generic_lambda_expr_test.v index 2a3f0a7eb..f9ceaa4c8 100644 --- a/vlib/v/tests/generics/generic_lambda_expr_test.v +++ b/vlib/v/tests/generics/generic_lambda_expr_test.v @@ -6,10 +6,24 @@ pub fn mymap[T, R](input []T, f fn (T) R) []R { return results } +type StringConvertFn[T] = fn (s string) !T + +fn parse_string[T](s string, sep string, conv StringConvertFn[T]) ![]T { + mut result := []T{} + for part in s.split(sep) { + result << conv[T](part)! + } + return result +} + fn test_main() { assert dump(mymap([1, 2, 3, 4, 5], fn (i int) int { return i * i })) == [1, 4, 9, 16, 25] assert dump(mymap([1, 2, 3, 4, 5], |x| x * x)) == [1, 4, 9, 16, 25] assert dump(mymap([1, 2, 3, 4, 5], |x| u16(x * x))) == [u16(1), 4, 9, 16, 25] + assert parse_string[int]('1..5', '..', fn [T](s string) !int { + return s.int() + })! == [1, 5] + assert parse_string[int]('1..5', '..', |s| s.int())! == [1, 5] } -- 2.39.5