From bfed5ab11ac9244527860a31cc1c51cd89974c57 Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Wed, 15 Apr 2026 00:42:16 +0300 Subject: [PATCH] cgen: fix generic methods passing lambda needing type hint (fixes #26259) --- vlib/v/checker/fn.v | 46 +++++++++++++++++-- vlib/v/gen/c/cgen.v | 2 +- vlib/v/gen/c/fn.v | 22 +++++++++ .../generic_fn_call_by_generic_fn_test.v | 6 +++ vlib/v/type_resolver/generic_resolver.v | 4 ++ 5 files changed, 74 insertions(+), 6 deletions(-) diff --git a/vlib/v/checker/fn.v b/vlib/v/checker/fn.v index 9a501427f..8217e9334 100644 --- a/vlib/v/checker/fn.v +++ b/vlib/v/checker/fn.v @@ -2613,7 +2613,7 @@ fn (mut c Checker) fn_call(mut node ast.CallExpr, mut continue_check &bool) ast. } if mut call_arg.expr is ast.LambdaExpr { // Calling fn is generic and lambda arg also is generic - c.handle_generic_lambda_arg(node, mut call_arg.expr) + c.handle_generic_lambda_arg(node, func.generic_names, mut call_arg.expr) continue } if c.is_optional_array_arg_compatible(utyp, unwrap_typ) { @@ -2624,7 +2624,7 @@ fn (mut c Checker) fn_call(mut node ast.CallExpr, mut continue_check &bool) ast. // When check succeeds (e.g. after lambda re-check with concrete types), // 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, mut call_arg.expr) + c.handle_generic_lambda_arg(node, func.generic_names, mut call_arg.expr) } } } @@ -3613,7 +3613,7 @@ 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, mut arg.expr) + c.handle_generic_lambda_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] @@ -3668,6 +3668,14 @@ fn (mut c Checker) method_call(mut node ast.CallExpr, mut continue_check &bool) c.infer_fn_generic_types(method, mut node) concrete_types = node.concrete_types.map(c.unwrap_generic(it)) } + if method_generic_names_len > 0 && node.concrete_types.len > 0 { + 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 + } + } + } if concrete_types.len == method_generic_names_len && concrete_types.all(!it.has_flag(.generic)) { c.table.register_fn_concrete_types(method.fkey(), concrete_types) need_recheck, _ := c.type_resolver.resolve_fn_generic_args(c.table.cur_fn, method, mut node) @@ -3709,12 +3717,40 @@ fn (mut c Checker) method_call(mut node ast.CallExpr, mut continue_check &bool) return node.return_type } -fn (mut c Checker) handle_generic_lambda_arg(node &ast.CallExpr, mut lambda ast.LambdaExpr) { +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 { + return []ast.Type{} + } + if caller_concrete_types.len == lambda.func.decl.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 { + idx := caller_generic_names.index(generic_name) + if idx < 0 || idx >= caller_concrete_types.len { + return []ast.Type{} + } + concrete_types << caller_concrete_types[idx] + } + return concrete_types +} + +fn (mut c Checker) handle_generic_lambda_arg(node &ast.CallExpr, caller_generic_names []string, mut lambda ast.LambdaExpr) { // Calling fn is generic and lambda arg also is generic if node.concrete_types.len > 0 && lambda.func != unsafe { nil } && lambda.func.decl.generic_names.len > 0 { lambda.call_ctx = unsafe { node } - if c.table.register_fn_concrete_types(lambda.func.decl.fkey(), node.concrete_types) { + lambda_concrete_types := c.generic_lambda_concrete_types(lambda, caller_generic_names, + node.concrete_types) + if lambda_concrete_types.len == 0 { + return + } + if c.table.register_fn_concrete_types(lambda.func.decl.fkey(), lambda_concrete_types) { lambda.func.decl.ninstances++ } } diff --git a/vlib/v/gen/c/cgen.v b/vlib/v/gen/c/cgen.v index e65ba8544..643cc18fd 100644 --- a/vlib/v/gen/c/cgen.v +++ b/vlib/v/gen/c/cgen.v @@ -5251,7 +5251,7 @@ fn (mut g Gen) expr(node_ ast.Expr) { ast.LambdaExpr { if node.call_ctx != unsafe { nil } { save_cur_concrete_types := g.cur_concrete_types - call_concrete := node.call_ctx.concrete_types + call_concrete := g.active_call_lambda_concrete_types(node) // Only override cur_concrete_types if the call context has // fully resolved (non-generic) types. When inside a generic // function instantiation, g.cur_concrete_types already has diff --git a/vlib/v/gen/c/fn.v b/vlib/v/gen/c/fn.v index 99fa15423..3250e0a25 100644 --- a/vlib/v/gen/c/fn.v +++ b/vlib/v/gen/c/fn.v @@ -3511,6 +3511,28 @@ 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_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] + } + return concrete_types + } + if node.call_ctx != unsafe { nil } + && node.call_ctx.concrete_types.len == node.func.decl.generic_names.len { + return node.call_ctx.concrete_types.clone() + } + return []ast.Type{} +} + fn (mut g Gen) resolve_active_call_concrete_type(typ ast.Type) ast.Type { if typ == 0 { return 0 diff --git a/vlib/v/tests/generics/generic_fn_call_by_generic_fn_test.v b/vlib/v/tests/generics/generic_fn_call_by_generic_fn_test.v index a14edeeaf..4bd30875b 100644 --- a/vlib/v/tests/generics/generic_fn_call_by_generic_fn_test.v +++ b/vlib/v/tests/generics/generic_fn_call_by_generic_fn_test.v @@ -25,3 +25,9 @@ fn test_main() { }) assert result.some && result.val == 5 } + +fn test_generic_method_map_infers_lambda_return_from_selector_expr() { + f := some('hello') + result := f.map(|s| s.len) + assert result.some && result.val == 5 +} diff --git a/vlib/v/type_resolver/generic_resolver.v b/vlib/v/type_resolver/generic_resolver.v index 7dcc71179..58dd4e10b 100644 --- a/vlib/v/type_resolver/generic_resolver.v +++ b/vlib/v/type_resolver/generic_resolver.v @@ -13,6 +13,10 @@ pub fn (mut ct TypeResolver) unwrap_generic_expr(expr ast.Expr, default_typ ast. ast.ParExpr { return ct.unwrap_generic_expr(expr.expr, default_typ) } + ast.SelectorExpr { + typ := ct.get_type(expr) + return if typ != ast.void_type { ct.resolver.unwrap_generic(typ) } else { default_typ } + } ast.CastExpr { return expr.typ } -- 2.39.5