From 8d0709ce1640b2179145dc578c670639bc4c34d2 Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Wed, 15 Apr 2026 04:58:21 +0300 Subject: [PATCH] checker: do not force the declaration of unused callback params (fixes #24460) --- vlib/v/checker/fn.v | 86 +++++++++++++++++++++++++++++ vlib/v/checker/lambda_expr.v | 10 +++- vlib/v/tests/fns/anon_fn_test.v | 22 ++++++++ vlib/v/tests/fns/lambda_expr_test.v | 5 ++ 4 files changed, 122 insertions(+), 1 deletion(-) diff --git a/vlib/v/checker/fn.v b/vlib/v/checker/fn.v index 50d1f9c98..e70aceb1c 100644 --- a/vlib/v/checker/fn.v +++ b/vlib/v/checker/fn.v @@ -968,6 +968,91 @@ fn (c &Checker) check_same_type_ignoring_pointers(type_a ast.Type, type_b ast.Ty return true } +fn (mut c Checker) expected_callback_fn() ?ast.Fn { + if c.expected_type in [0, ast.void_type] { + return none + } + expected_type := c.unwrap_generic(c.recheck_concrete_type(c.expected_type)) + expected_sym := c.table.final_sym(expected_type) + if expected_sym.kind != .function || expected_sym.info !is ast.FnType { + return none + } + return (expected_sym.info as ast.FnType).func +} + +fn (mut c Checker) omitted_callback_param(expected_param ast.Param, idx int, pos token.Pos, prefix string) ast.Param { + param_type := c.recheck_concrete_type(expected_param.typ) + return ast.Param{ + pos: pos + name: '${prefix}${idx}' + is_mut: expected_param.is_mut + is_shared: expected_param.is_shared + is_atomic: expected_param.is_atomic + typ: param_type + orig_typ: if expected_param.orig_typ == 0 { param_type } else { expected_param.orig_typ } + type_pos: pos + on_newline: expected_param.on_newline + } +} + +fn (mut c Checker) append_omitted_callback_params(mut params []ast.Param, expected_params []ast.Param, pos token.Pos, prefix string, scope &ast.Scope) { + if params.len >= expected_params.len { + return + } + for idx in params.len .. expected_params.len { + param := c.omitted_callback_param(expected_params[idx], idx, pos, prefix) + params << param + if scope != unsafe { nil } { + unsafe { + mut scope_ := &ast.Scope(scope) + scope_.register(ast.Var{ + name: param.name + typ: param.typ + generic_typ: if param.typ.has_flag(.generic) { param.typ } else { ast.Type(0) } + is_mut: c.implicit_mutability_enabled() || param.is_mut + is_auto_deref: param.is_mut + pos: param.pos + is_used: true + is_arg: true + is_stack_obj: !param.typ.has_flag(.shared_f) + && (param.is_mut || param.typ.is_ptr()) + }) + } + } + } +} + +fn (mut c Checker) expand_anon_fn_callback_signature(mut node ast.AnonFn) { + expected_fn := c.expected_callback_fn() or { return } + if node.decl.params.len >= expected_fn.params.len || node.decl.is_variadic + || expected_fn.is_variadic { + return + } + mut params := node.decl.params.clone() + c.append_omitted_callback_params(mut params, expected_fn.params, node.decl.pos, + '__v_anon_unused_param_', node.decl.scope) + node.decl.params = params + mut func := ast.Fn{ + params: params + is_variadic: node.decl.is_variadic + return_type: node.decl.return_type + is_method: false + } + name := c.table.get_anon_fn_name(c.file.unique_prefix, func, node.decl.pos) + func.name = name + node.decl = ast.FnDecl{ + ...node.decl + name: name + params: params + } + idx := c.table.find_or_register_fn_type(func, true, false) + node.typ = if node.decl.generic_names.len > 0 { + ast.new_type(idx).set_flag(.generic) + } else { + ast.new_type(idx) + } +} + 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 @@ -985,6 +1070,7 @@ fn (mut c Checker) anon_fn(mut node ast.AnonFn) ast.Type { if node.decl.no_body { c.error('anonymous function must declare a body', node.decl.pos) } + c.expand_anon_fn_callback_signature(mut node) for param in node.decl.params { if param.name == '' { c.error('use `_` to name an unused parameter', param.pos) diff --git a/vlib/v/checker/lambda_expr.v b/vlib/v/checker/lambda_expr.v index 83d42fc0a..12d3ae4e5 100644 --- a/vlib/v/checker/lambda_expr.v +++ b/vlib/v/checker/lambda_expr.v @@ -33,7 +33,7 @@ pub fn (mut c Checker) lambda_expr(mut node ast.LambdaExpr, exp_typ ast.Type) as return ast.void_type } if exp_sym.info is ast.FnType { - if node.params.len != exp_sym.info.func.params.len { + if node.params.len > exp_sym.info.func.params.len { c.error('lambda expression has ${node.params.len} params, but the expected fn callback needs ${exp_sym.info.func.params.len} params', node.pos) return ast.void_type @@ -54,6 +54,14 @@ pub fn (mut c Checker) lambda_expr(mut node ast.LambdaExpr, exp_typ ast.Type) as type_pos: x.pos } } + for idx in node.params.len .. exp_sym.info.func.params.len { + eparam_type := exp_sym.info.func.params[idx].typ + if eparam_type.has_flag(.generic) { + generic_types[eparam_type] = true + } + } + c.append_omitted_callback_params(mut params, exp_sym.info.func.params, node.pos, + '__v_lambda_unused_param_', unsafe { nil }) is_variadic := false return_type := exp_sym.info.func.return_type diff --git a/vlib/v/tests/fns/anon_fn_test.v b/vlib/v/tests/fns/anon_fn_test.v index b3637af4c..75b00e3ed 100644 --- a/vlib/v/tests/fns/anon_fn_test.v +++ b/vlib/v/tests/fns/anon_fn_test.v @@ -54,3 +54,25 @@ fn test_spawn_anon_fn_with_closure_parameters_and_mut_ref_parameters() { }(mut s) assert t.wait() == 1 } + +struct CallbackEvent {} + +fn call_event_handler(cb fn (CallbackEvent) bool) bool { + return cb(CallbackEvent{}) +} + +fn call_two_arg_handler(cb fn (CallbackEvent, int) int) int { + return cb(CallbackEvent{}, 7) +} + +fn test_anon_fn_can_omit_all_unused_callback_params() { + assert call_event_handler(fn () bool { + return true + }) +} + +fn test_anon_fn_can_omit_trailing_callback_params() { + assert call_two_arg_handler(fn (_ CallbackEvent) int { + return 42 + }) == 42 +} diff --git a/vlib/v/tests/fns/lambda_expr_test.v b/vlib/v/tests/fns/lambda_expr_test.v index d57952e69..02dbcd729 100644 --- a/vlib/v/tests/fns/lambda_expr_test.v +++ b/vlib/v/tests/fns/lambda_expr_test.v @@ -54,3 +54,8 @@ fn f4(g fn (int) string) { fn test_params_has_blank_ident() { f4(|_| 'hello') } + +fn test_lambda_expr_can_omit_unused_callback_params() { + assert f1(|| 4) == 4 + assert f2(|x| x + 4) == 14 +} -- 2.39.5