From 5d3ad7c0a3127167f0f18478f253c6507a7d012b Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Wed, 25 Mar 2026 16:42:18 +0300 Subject: [PATCH] ast: fix initializing fn field with matching fn function with ref params (fixes #16291) --- vlib/v/ast/str.v | 22 +++++++- vlib/v/ast/table.v | 54 +++++++++++-------- .../fn_field_ref_param_struct_init_test.v | 22 ++++++++ 3 files changed, 74 insertions(+), 24 deletions(-) create mode 100644 vlib/v/tests/fn_field_ref_param_struct_init_test.v diff --git a/vlib/v/ast/str.v b/vlib/v/ast/str.v index f1aec62a3..8702fc485 100644 --- a/vlib/v/ast/str.v +++ b/vlib/v/ast/str.v @@ -229,7 +229,7 @@ fn (t &Table) stringify_fn_after_name(node &FnDecl, mut f strings.Builder, cur_m s = t.type_to_str(param_typ.clear_flag(.shared_f).deref()) } } - s = util.no_cur_mod(s, cur_mod) + s = shorten_full_name_based_on_cur_mod(s, cur_mod) s = shorten_full_name_based_on_aliases(s, m2a) if !is_type_only { f.write_string(' ') @@ -254,7 +254,8 @@ fn (t &Table) stringify_fn_after_name(node &FnDecl, mut f strings.Builder, cur_m } else { node.return_type } - sreturn_type := util.no_cur_mod(t.type_to_str(return_type), cur_mod) + sreturn_type := shorten_full_name_based_on_cur_mod(t.type_to_str(return_type), + cur_mod) short_sreturn_type := shorten_full_name_based_on_aliases(sreturn_type, m2a) f.write_string(' ${short_sreturn_type}') } @@ -379,6 +380,23 @@ fn shorten_full_name_based_on_aliases(input string, m2a map[string]string) strin return res } +fn shorten_full_name_based_on_cur_mod(input string, cur_mod string) string { + if cur_mod == '' || !input.contains('${cur_mod}.') { + return input + } + pattern := '${cur_mod}.' + mut out := strings.new_builder(input.len) + for i := 0; i < input.len; i++ { + if input[i..].starts_with(pattern) + && (i == 0 || input[i - 1] in [` `, `(`, `[`, `,`, `|`, `&`, `?`, `!`, `:`]) { + i += pattern.len - 1 + continue + } + out.write_u8(input[i]) + } + return out.str() +} + // This method creates the format specifier (including the colon) or an empty // string if none is needed. For example, '${z:8.3f} ${a:-20} ${a>b+2}' pub fn (lit &StringInterLiteral) get_fspec(i int) string { diff --git a/vlib/v/ast/table.v b/vlib/v/ast/table.v index 9a26685a4..cffd524d1 100644 --- a/vlib/v/ast/table.v +++ b/vlib/v/ast/table.v @@ -141,8 +141,10 @@ pub fn (mut t Table) free() { } } -pub const fn_type_escape_seq = [' ', '', '(', '_', ')', ''] -pub const map_cname_escape_seq = ['[', '_T_', ', ', '_T_', ',', '_T_', ']', ''] +pub const fn_type_escape_seq = ['...', 'variadic_', '&', 'ref_', '[', '_T_', ']', '', ', ', '_', + ',', '_', '(', '_', ')', '', ' ', '_', '?', 'option_', '!', 'result_', '|', '_or_', '<', '_', + '>', ''] +pub const map_cname_escape_seq = ['[', '_T_', ', ', '_', ']', ''] pub type FnPanicHandler = fn (&Table, string) @@ -175,38 +177,53 @@ __global global_table = &Table(unsafe { nil }) pub fn (t &Table) fn_type_signature(f &Fn) string { mut sig := '' for i, arg in f.params { - typ := arg.typ.set_nr_muls(0) - if arg.is_mut { - sig += 'mut_' - } - sig += t.sym(typ).cname.to_lower_ascii() + sig += t.fn_type_signature_part(f, i, arg) if i < f.params.len - 1 { sig += '_' } } if f.return_type != 0 && f.return_type != void_type { - sym := t.sym(f.return_type) - opt := if f.return_type.has_flag(.option) { 'option_' } else { '' } - res := if f.return_type.has_flag(.result) { 'result_' } else { '' } - - sig += '__${opt}${res}${sym.cname}' + sig += '__${util.no_dots(t.type_to_str(f.return_type)).replace_each(fn_type_escape_seq)}' } return sig } +fn (t &Table) fn_type_signature_part(f &Fn, i int, arg Param) string { + mut typ := arg.typ + mut sig := '' + if arg.is_mut { + if typ.is_ptr() { + typ = typ.deref() + } + sig += 'mut ' + } + if i == f.params.len - 1 && f.is_variadic { + sig += '...' + } + sig += t.type_to_str(typ) + return util.no_dots(sig).replace_each(fn_type_escape_seq) +} + // fn_type_source_signature generates the signature of a function which looks like in the V source pub fn (t &Table) fn_type_source_signature(f &Fn) string { + import_aliases := map[string]string{} mut sig := '(' for i, arg in f.params { + mut typ := arg.typ if arg.is_mut { + if typ.is_ptr() { + typ = typ.deref() + } sig += 'mut ' } // Note: arg name is only added for fmt, else it would causes errors with generics if t.is_fmt && arg.name != '' { sig += '${arg.name} ' } - arg_type_sym := t.sym(arg.typ) - sig += arg_type_sym.name + if i == f.params.len - 1 && f.is_variadic { + sig += '...' + } + sig += t.type_to_str_using_aliases(typ, import_aliases) if i < f.params.len - 1 { sig += ', ' } @@ -217,14 +234,7 @@ pub fn (t &Table) fn_type_source_signature(f &Fn) string { } else if f.return_type == rvoid_type { sig += ' !' } else if f.return_type != void_type && f.return_type != 0 { - return_type_sym := t.sym(f.return_type) - if f.return_type.has_flag(.option) { - sig += ' ?${return_type_sym.name}' - } else if f.return_type.has_flag(.result) { - sig += ' !${return_type_sym.name}' - } else { - sig += ' ${return_type_sym.name}' - } + sig += ' ${t.type_to_str_using_aliases(f.return_type, import_aliases)}' } return sig } diff --git a/vlib/v/tests/fn_field_ref_param_struct_init_test.v b/vlib/v/tests/fn_field_ref_param_struct_init_test.v new file mode 100644 index 000000000..e6cd37c64 --- /dev/null +++ b/vlib/v/tests/fn_field_ref_param_struct_init_test.v @@ -0,0 +1,22 @@ +struct FnFields { + by_val fn (int) int = unsafe { nil } + by_ref fn (&int) int = unsafe { nil } +} + +fn by_val(n int) int { + return n + 1 +} + +fn by_ref(n &int) int { + return *n + 1 +} + +fn test_inline_fn_field_with_ref_param_type_does_not_collide() { + fields := FnFields{ + by_val: by_val + by_ref: by_ref + } + value := 41 + assert fields.by_val(41) == 42 + assert fields.by_ref(&value) == 42 +} -- 2.39.5