From 5be0735d49125afdc9478b1b785a31e058d2a7d6 Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Wed, 25 Mar 2026 23:18:01 +0300 Subject: [PATCH] checker: fix cannot use &int as &&int in argument (fixes #23019) --- vlib/v/ast/ast.v | 3 +- vlib/v/ast/table.v | 43 +++++++++++++++--- vlib/v/checker/check_types.v | 44 ++++++++++++++----- vlib/v/checker/fn.v | 32 ++++++++++++-- vlib/v/gen/c/fn.v | 11 +++++ vlib/v/parser/fn.v | 4 ++ .../generics/generic_mut_pointer_param_test.v | 27 ++++++++++++ 7 files changed, 141 insertions(+), 23 deletions(-) create mode 100644 vlib/v/tests/generics/generic_mut_pointer_param_test.v diff --git a/vlib/v/ast/ast.v b/vlib/v/ast/ast.v index 9891c2d7a..fbcdb226c 100644 --- a/vlib/v/ast/ast.v +++ b/vlib/v/ast/ast.v @@ -751,7 +751,8 @@ pub: is_hidden bool // interface first arg on_newline bool // whether the argument starts on a new line pub mut: - typ Type + typ Type + orig_typ Type // source type before mut lowering } pub fn (p &Param) specifier() string { diff --git a/vlib/v/ast/table.v b/vlib/v/ast/table.v index 415ef3a0e..2f9543181 100644 --- a/vlib/v/ast/table.v +++ b/vlib/v/ast/table.v @@ -1919,7 +1919,7 @@ pub fn (mut t Table) convert_generic_type(generic_type Type, generic_names []str func.params = func.params.clone() for mut param in func.params { if param.typ.has_flag(.generic) { - if typ := t.convert_generic_type(param.typ, generic_names, to_types) { + if typ := t.convert_generic_param_type(param, generic_names, to_types) { param.typ = typ if typ.has_flag(.generic) { has_generic = true @@ -2107,6 +2107,37 @@ pub fn (mut t Table) convert_generic_type(generic_type Type, generic_names []str return none } +fn (mut t Table) lower_mut_param_type(typ Type) Type { + mut lowered := if typ.is_ptr() && t.sym(typ).kind == .struct { + typ.ref() + } else { + typ.set_nr_muls(1) + } + if lowered.has_flag(.option) { + lowered = lowered.set_flag(.option_mut_param_t) + } + return lowered +} + +pub fn (mut t Table) convert_generic_param_type(param Param, generic_names []string, to_types []Type) ?Type { + if param.is_mut && param.orig_typ != 0 && param.orig_typ.has_flag(.generic) + && to_types.all(!it.has_flag(.generic)) { + if typ := t.convert_generic_type(param.orig_typ, generic_names, to_types) { + return t.lower_mut_param_type(typ) + } + } + return t.convert_generic_type(param.typ, generic_names, to_types) +} + +pub fn (mut t Table) unwrap_generic_param_type(param Param, generic_names []string, concrete_types []Type) Type { + if param.is_mut && param.orig_typ != 0 && param.orig_typ.has_flag(.generic) + && concrete_types.all(!it.has_flag(.generic)) { + return t.lower_mut_param_type(t.unwrap_generic_type(param.orig_typ, generic_names, + concrete_types)) + } + return t.unwrap_generic_type(param.typ, generic_names, concrete_types) +} + fn generic_names_push_with_filter(mut to_names []string, from_names []string) { for name in from_names { if name !in to_names { @@ -2215,8 +2246,8 @@ pub fn (mut t Table) unwrap_generic_type_ex(typ Type, generic_names []string, co mut has_generic := false for i, param in unwrapped_fn.params { if param.typ.has_flag(.generic) { - unwrapped_fn.params[i].typ = t.unwrap_generic_type_ex(param.typ, generic_names, - concrete_types, recheck_concrete_types) + unwrapped_fn.params[i].typ = t.unwrap_generic_param_type(param, generic_names, + concrete_types) has_generic = true } } @@ -2447,7 +2478,7 @@ pub fn (mut t Table) unwrap_generic_type_ex(typ Type, generic_names []string, co method.return_type = unwrap_typ } for mut param in method.params { - if unwrap_typ := t.convert_generic_type(param.typ, gn_names, concrete_types) { + if unwrap_typ := t.convert_generic_param_type(param, gn_names, concrete_types) { param.typ = unwrap_typ } } @@ -2611,7 +2642,7 @@ pub fn (mut t Table) generic_insts_to_concrete() { } method.params = method.params.clone() for mut param in method.params { - if pt := t.convert_generic_type(param.typ, generic_names, + if pt := t.convert_generic_param_type(param, generic_names, info.concrete_types) { param.typ = pt @@ -2694,7 +2725,7 @@ pub fn (mut t Table) generic_insts_to_concrete() { function.params = function.params.clone() for mut param in function.params { if param.typ.has_flag(.generic) { - if t_typ := t.convert_generic_type(param.typ, function.generic_names, + if t_typ := t.convert_generic_param_type(param, function.generic_names, info.concrete_types) { param.typ = t_typ diff --git a/vlib/v/checker/check_types.v b/vlib/v/checker/check_types.v index ae202839f..40f1c2bc0 100644 --- a/vlib/v/checker/check_types.v +++ b/vlib/v/checker/check_types.v @@ -1101,6 +1101,10 @@ fn (g Checker) get_generic_array_element_type(array ast.Array) ast.Type { fn (mut c Checker) infer_fn_generic_types(func &ast.Fn, mut node ast.CallExpr) { mut inferred_types := []ast.Type{} mut arg_inferred := []int{} + has_concrete_caller_types := c.table.cur_fn == unsafe { nil } + || c.table.cur_fn.generic_names.len == 0 + || (c.table.cur_fn.generic_names.len == c.table.cur_concrete_types.len + && c.table.cur_concrete_types.all(!it.has_flag(.generic))) for gi, gt_name in func.generic_names { // skip known types if gi < node.concrete_types.len { @@ -1151,18 +1155,25 @@ fn (mut c Checker) infer_fn_generic_types(func &ast.Fn, mut node ast.CallExpr) { break } arg := node.args[arg_i] - param_sym := c.table.sym(param.typ) + param_infer_typ := if has_concrete_caller_types && param.is_mut + && !param.typ.has_flag(.variadic) && param.orig_typ != 0 + && param.orig_typ.has_flag(.generic) { + param.orig_typ + } else { + param.typ + } + param_sym := c.table.sym(param_infer_typ) - if (param.typ.has_flag(.option) && arg.typ.has_flag(.option)) - || (param.typ.has_flag(.result) && arg.typ.has_flag(.result)) { - param_inner := param.typ.clear_option_and_result() + if (param_infer_typ.has_flag(.option) && arg.typ.has_flag(.option)) + || (param_infer_typ.has_flag(.result) && arg.typ.has_flag(.result)) { + param_inner := param_infer_typ.clear_option_and_result() if param_inner.has_flag(.generic) && c.table.sym(param_inner).name == gt_name { typ = arg.typ.clear_option_and_result() if param_inner.nr_muls() > 0 && typ.nr_muls() > 0 { typ = typ.set_nr_muls(0) } } - } else if param.typ.has_flag(.generic) && param_sym.name == gt_name { + } else if param_infer_typ.has_flag(.generic) && param_sym.name == gt_name { typ = ast.mktyp(arg.typ) if typ == ast.nil_type { typ = ast.voidptr_type @@ -1185,12 +1196,16 @@ fn (mut c Checker) infer_fn_generic_types(func &ast.Fn, mut node ast.CallExpr) { } } } - if arg.expr.is_auto_deref_var() { + if arg.expr.is_auto_deref_var() && param_infer_typ.nr_muls() > 0 { + typ = typ.deref() + } + if has_concrete_caller_types && param.is_mut && param_infer_typ.nr_muls() == 0 + && typ.is_ptr() && c.table.final_sym(typ).kind == .struct { typ = typ.deref() } // resolve &T &&T ... - if param.typ.nr_muls() > 0 && typ.nr_muls() > 0 { - param_muls := param.typ.nr_muls() + if param_infer_typ.nr_muls() > 0 && typ.nr_muls() > 0 { + param_muls := param_infer_typ.nr_muls() arg_muls := typ.nr_muls() typ = if arg_muls >= param_muls { typ.set_nr_muls(arg_muls - param_muls) @@ -1198,14 +1213,14 @@ fn (mut c Checker) infer_fn_generic_types(func &ast.Fn, mut node ast.CallExpr) { typ.set_nr_muls(0) } } - } else if param.typ.has_flag(.generic) { + } else if param_infer_typ.has_flag(.generic) { arg_typ := if c.table.sym(arg.typ).kind == .any { c.unwrap_generic(arg.typ) } else { arg.typ } arg_sym := c.table.final_sym(arg_typ) - if param.typ.has_flag(.variadic) { + if param_infer_typ.has_flag(.variadic) { typ = ast.mktyp(arg_typ) } else if arg_sym.info is ast.Array && param_sym.info is ast.Array { mut arg_elem_typ, mut param_elem_typ := arg_sym.info.elem_type, param_sym.info.elem_type @@ -1319,10 +1334,15 @@ fn (mut c Checker) infer_fn_generic_types(func &ast.Fn, mut node ast.CallExpr) { && arg.expr is ast.Ident && arg_i !in arg_inferred { var_name := arg.expr.name for k, cur_param in c.table.cur_fn.params { - if !cur_param.typ.has_flag(.generic) || k < gi || cur_param.name != var_name { + if k < gi || cur_param.name != var_name { continue } - typ = cur_param.typ + typ = if has_concrete_caller_types && cur_param.typ.has_flag(.generic) { + c.table.unwrap_generic_param_type(cur_param, c.table.cur_fn.generic_names, + c.table.cur_concrete_types) + } else { + cur_param.typ + } mut cparam_type_sym := c.table.sym(c.unwrap_generic(typ)) if cparam_type_sym.kind == .array { typ = c.type_resolver.get_generic_array_element_type(cparam_type_sym.info as ast.Array) diff --git a/vlib/v/checker/fn.v b/vlib/v/checker/fn.v index 1b359c3f2..0e2b1c843 100644 --- a/vlib/v/checker/fn.v +++ b/vlib/v/checker/fn.v @@ -108,6 +108,22 @@ fn (mut c Checker) fn_decl(mut node ast.FnDecl) { return } node.ninstances++ + mut old_params := []ast.Param{} + if node.generic_names.len > 0 && c.table.cur_concrete_types.len == node.generic_names.len + && c.table.cur_concrete_types.all(!it.has_flag(.generic)) { + old_params = node.params.clone() + for i, param in old_params { + if !param.typ.has_flag(.generic) { + continue + } + if typ := c.table.convert_generic_param_type(param, node.generic_names, c.table.cur_concrete_types) { + node.params[i].typ = typ + if mut v := node.scope.find_var(param.name) { + v.typ = typ + } + } + } + } // save all the state that fn_decl or inner statements/expressions // could potentially modify, since functions can be nested, due to // anonymous function support, and ensure that it is restored, when @@ -125,6 +141,14 @@ fn (mut c Checker) fn_decl(mut node ast.FnDecl) { c.inside_unsafe = node.is_unsafe c.returns = false defer { + if old_params.len > 0 { + node.params = old_params + for param in old_params { + if mut v := node.scope.find_var(param.name) { + v.typ = param.typ + } + } + } c.stmt_level = prev_stmt_level c.fn_level-- c.returns = prev_returns @@ -1613,7 +1637,7 @@ fn (mut c Checker) fn_call(mut node ast.CallExpr, mut continue_check &bool) ast. && c.table.cur_fn.generic_names.len > 0 && c.table.cur_fn.generic_names.len == c.table.cur_concrete_types.len { mut unwrapped := param - unwrapped.typ = c.table.unwrap_generic_type(param.typ, c.table.cur_fn.generic_names, + unwrapped.typ = c.table.unwrap_generic_param_type(param, c.table.cur_fn.generic_names, c.table.cur_concrete_types) param = unwrapped } @@ -2017,7 +2041,7 @@ fn (mut c Checker) fn_call(mut node ast.CallExpr, mut continue_check &bool) ast. func.params[i] } if param.typ.has_flag(.generic) { - if unwrap_typ := c.table.convert_generic_type(param.typ, func.generic_names, + if unwrap_typ := c.table.convert_generic_param_type(param, func.generic_names, concrete_types) { c.expected_type = unwrap_typ @@ -2034,7 +2058,7 @@ fn (mut c Checker) fn_call(mut node ast.CallExpr, mut continue_check &bool) ast. }) if param.typ.has_flag(.generic) && func.generic_names.len == node.concrete_types.len { - if unwrap_typ := c.table.convert_generic_type(param.typ, func.generic_names, + if unwrap_typ := c.table.convert_generic_param_type(param, func.generic_names, concrete_types) { utyp := c.unwrap_generic(typ) @@ -2763,7 +2787,7 @@ fn (mut c Checker) method_call(mut node ast.CallExpr, mut continue_check &bool) } else { concrete_types } - if exp_utyp := c.table.convert_generic_type(exp_arg_typ, method.generic_names, + if exp_utyp := c.table.convert_generic_param_type(param, method.generic_names, method_concrete_types) { exp_arg_typ = exp_utyp diff --git a/vlib/v/gen/c/fn.v b/vlib/v/gen/c/fn.v index 26fdf9335..5a4d3e481 100644 --- a/vlib/v/gen/c/fn.v +++ b/vlib/v/gen/c/fn.v @@ -880,6 +880,17 @@ fn (mut g Gen) fn_decl_params(params []ast.Param, scope &ast.Scope, is_variadic typ = g.table.sym(typ).array_info().elem_type.set_flag(.variadic) } param_type_sym := g.table.sym(typ) + if param.is_mut && param.orig_typ != 0 && param.orig_typ.has_flag(.generic) { + mut surface_typ := g.unwrap_generic(param.orig_typ) + typ = if surface_typ.is_ptr() && g.table.sym(surface_typ).kind == .struct { + surface_typ.ref() + } else { + surface_typ.set_nr_muls(1) + } + if typ.has_flag(.option) { + typ = typ.set_flag(.option_mut_param_t) + } + } if param.is_mut && param.typ.has_flag(.generic) && typ.has_flag(.option) { typ = typ.set_flag(.option_mut_param_t).set_nr_muls(param.typ.nr_muls() - 1) } diff --git a/vlib/v/parser/fn.v b/vlib/v/parser/fn.v index f759a059d..e2abaaddf 100644 --- a/vlib/v/parser/fn.v +++ b/vlib/v/parser/fn.v @@ -1351,6 +1351,7 @@ fn (mut p Parser) fn_params() ([]ast.Param, bool, bool, bool) { p.inside_fn_param = true mut param_type := p.parse_type() p.inside_fn_param = prev_inside_fn_param + orig_param_type := param_type type_pos := pos.extend(p.prev_tok.pos()) if param_type == 0 { // error is added in parse_type @@ -1416,6 +1417,7 @@ fn (mut p Parser) fn_params() ([]ast.Param, bool, bool, bool) { pos: pos name: name is_mut: is_mut + orig_typ: orig_param_type typ: param_type type_pos: type_pos on_newline: prev_param_newline != pos.line_nr @@ -1488,6 +1490,7 @@ fn (mut p Parser) fn_params() ([]ast.Param, bool, bool, bool) { p.inside_fn_param = true mut typ := p.parse_type() p.inside_fn_param = prev_inside_fn_param + orig_typ := typ type_pos[0] = pos.extend(p.prev_tok.pos()) if typ == 0 { // error is added in parse_type @@ -1545,6 +1548,7 @@ fn (mut p Parser) fn_params() ([]ast.Param, bool, bool, bool) { is_mut: is_mut is_atomic: is_atomic is_shared: is_shared + orig_typ: orig_typ typ: typ type_pos: type_pos[i] on_newline: prev_param_newline != param_pos[i].line_nr diff --git a/vlib/v/tests/generics/generic_mut_pointer_param_test.v b/vlib/v/tests/generics/generic_mut_pointer_param_test.v new file mode 100644 index 000000000..5474d59c0 --- /dev/null +++ b/vlib/v/tests/generics/generic_mut_pointer_param_test.v @@ -0,0 +1,27 @@ +module main + +struct Decoder {} + +pub fn decode[T](mut result T) ! { + mut decoder := Decoder{} + decoder.decode_value(mut result)! +} + +fn (mut decoder Decoder) decode_value[T](mut val T) ! { + $if T.indirections != 0 { + unsafe { + *val = 2 + } + } $else { + unsafe { + *val = 1 + } + } +} + +fn test_generic_mut_pointer_param() { + mut value := 0 + mut result := &value + decode[&int](mut result)! + assert value == 2 +} -- 2.39.5