From cb076e95697c7f433fa389f6f95a05937cf42470 Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Tue, 21 Apr 2026 15:40:12 +0300 Subject: [PATCH] checker: fix getting index of an array with ref arg (fixes #17800) --- vlib/v/checker/assign.v | 17 +++++++++++++- vlib/v/checker/checker.v | 9 ++++---- .../int_ptr_array_index_decl_assign_err.out | 6 +++++ .../int_ptr_array_index_decl_assign_err.vv | 4 ++++ vlib/v/gen/c/assign.v | 9 +++++--- vlib/v/gen/c/utils.v | 23 ++++++++++++++++--- 6 files changed, 57 insertions(+), 11 deletions(-) create mode 100644 vlib/v/checker/tests/int_ptr_array_index_decl_assign_err.out create mode 100644 vlib/v/checker/tests/int_ptr_array_index_decl_assign_err.vv diff --git a/vlib/v/checker/assign.v b/vlib/v/checker/assign.v index c933496bf..5a82da2e7 100644 --- a/vlib/v/checker/assign.v +++ b/vlib/v/checker/assign.v @@ -28,6 +28,20 @@ fn assign_expr_is_auto_deref(expr ast.Expr) bool { return expr.is_auto_deref_var() } +fn (c &Checker) auto_deref_source_type_is_pointer(expr ast.Expr) bool { + if expr !is ast.Ident || c.table.cur_fn == unsafe { nil } || !expr.is_auto_deref_var() { + return false + } + ident := expr as ast.Ident + for param in c.table.cur_fn.params { + if param.name == ident.name { + source_typ := if param.orig_typ != 0 { param.orig_typ } else { param.typ } + return source_typ.is_any_kind_of_pointer() + } + } + return false +} + fn (mut c Checker) smartcasted_assign_lhs_type(expr ast.Expr, fallback_type ast.Type) ast.Type { match expr { ast.Ident { @@ -394,7 +408,8 @@ fn (mut c Checker) assign_stmt(mut node ast.AssignStmt) { c.expr(mut right) } } - if assign_expr_is_auto_deref(right) && right_type.is_ptr() { + if assign_expr_is_auto_deref(right) && right_type.is_ptr() + && !c.auto_deref_source_type_is_pointer(right) { left_type = ast.mktyp(right_type.deref()) } else { left_type = ast.mktyp(right_type) diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index 005ca49ce..81efe3f3b 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -344,10 +344,11 @@ fn (mut c Checker) refresh_generic_scope_var_type_for_use(mut v ast.Var, use_pos } } } - // Keep `mut x := param` as a value copy during generic rechecks too. - // `c.expr(param)` returns the wrapped pointer type for auto-deref vars, - // but the declaration itself already inferred the dereferenced value type. - if expr_is_auto_deref_ident && refreshed_type.is_ptr() { + // Keep `mut x := param` as a value copy during generic rechecks when the + // original parameter type was lowered from a non-pointer source type. + // Pointer-typed mut params should keep their declared pointer type. + if expr_is_auto_deref_ident && refreshed_type.is_ptr() + && !c.auto_deref_source_type_is_pointer(expr) { refreshed_type = refreshed_type.deref() } $if trace_ci_fixes ? { diff --git a/vlib/v/checker/tests/int_ptr_array_index_decl_assign_err.out b/vlib/v/checker/tests/int_ptr_array_index_decl_assign_err.out new file mode 100644 index 000000000..c3270bad9 --- /dev/null +++ b/vlib/v/checker/tests/int_ptr_array_index_decl_assign_err.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/int_ptr_array_index_decl_assign_err.vv:3:14: error: non-integer index `&int` (array type `[]u8`) + 1 | pub fn read_int(bytes []u8, mut offset &int) int { + 2 | offset_d := offset + 3 | return bytes[offset_d] + | ~~~~~~~~ + 4 | } diff --git a/vlib/v/checker/tests/int_ptr_array_index_decl_assign_err.vv b/vlib/v/checker/tests/int_ptr_array_index_decl_assign_err.vv new file mode 100644 index 000000000..6ac56ab5b --- /dev/null +++ b/vlib/v/checker/tests/int_ptr_array_index_decl_assign_err.vv @@ -0,0 +1,4 @@ +pub fn read_int(bytes []u8, mut offset &int) int { + offset_d := offset + return bytes[offset_d] +} diff --git a/vlib/v/gen/c/assign.v b/vlib/v/gen/c/assign.v index 798cc8407..821c34e54 100644 --- a/vlib/v/gen/c/assign.v +++ b/vlib/v/gen/c/assign.v @@ -1124,7 +1124,8 @@ fn (mut g Gen) assign_stmt(node_ ast.AssignStmt) { // When assigning from an auto-deref variable (e.g. mut ref param), // the resolved type from scope includes the extra pointer level. // Deref it to match the V value semantics. - if val is ast.Ident && val.is_auto_deref_var() && resolved_val_type.is_ptr() { + if val is ast.Ident && val.is_auto_deref_var() && resolved_val_type.is_ptr() + && !g.auto_deref_source_type_is_pointer(val) { resolved_val_type = resolved_val_type.deref() } // Preserve shared/atomic flags from the original declaration. @@ -1352,7 +1353,8 @@ fn (mut g Gen) assign_stmt(node_ ast.AssignStmt) { // the checker's left_types[i] for blank idents can be // overwritten by a later generic instantiation. mut blank_styp := g.styp(val_type) - if val is ast.Ident && val.is_auto_deref_var() { + if val is ast.Ident && val.is_auto_deref_var() + && !g.auto_deref_source_type_is_pointer(val) { blank_styp = '${blank_styp}*' } if blank_styp.ends_with('*') { @@ -1888,7 +1890,8 @@ fn (mut g Gen) assign_stmt(node_ ast.AssignStmt) { } } } - if !is_fn_var && val.is_auto_deref_var() && !is_option_unwrapped { + if !is_fn_var && val.is_auto_deref_var() && !is_option_unwrapped + && !g.auto_deref_source_type_is_pointer(val) { g.write('*') } if (var_type.has_flag(.option) && val !in [ast.Ident, ast.SelectorExpr]) diff --git a/vlib/v/gen/c/utils.v b/vlib/v/gen/c/utils.v index 9a156e6dc..10a63ffea 100644 --- a/vlib/v/gen/c/utils.v +++ b/vlib/v/gen/c/utils.v @@ -293,6 +293,20 @@ fn (g &Gen) is_auto_deref_source_ident(expr ast.Expr) bool { return false } +fn (g &Gen) auto_deref_source_type_is_pointer(expr ast.Expr) bool { + if expr !is ast.Ident || g.cur_fn == unsafe { nil } || !expr.is_auto_deref_var() { + return false + } + ident := expr as ast.Ident + for param in g.cur_fn.params { + if param.name == ident.name { + source_typ := if param.orig_typ != 0 { param.orig_typ } else { param.typ } + return source_typ.is_any_kind_of_pointer() + } + } + return false +} + fn (mut g Gen) resolved_scope_var_type(expr ast.Ident) ast.Type { mut scope := if expr.scope != unsafe { nil } { expr.scope.innermost(expr.pos.pos) @@ -335,8 +349,10 @@ fn (mut g Gen) resolved_scope_var_type(expr ast.Ident) ast.Type { refreshed_expr_type = call_like_type } } - // Keep `mut x := param` as a value copy when re-resolving locals. - if g.is_auto_deref_source_ident(v.expr) && refreshed_expr_type.is_ptr() { + // Keep `mut x := param` as a value copy when re-resolving locals, + // unless the original mut parameter type was already a pointer. + if g.is_auto_deref_source_ident(v.expr) && refreshed_expr_type.is_ptr() + && !g.auto_deref_source_type_is_pointer(v.expr) { refreshed_expr_type = refreshed_expr_type.deref() } // If the variable was initialized with an `or {}` block that @@ -399,7 +415,8 @@ fn (mut g Gen) resolved_scope_var_type(expr ast.Ident) ast.Type { } } if g.is_auto_deref_source_ident(parent_v.expr) - && refreshed_parent_type.is_ptr() { + && refreshed_parent_type.is_ptr() + && !g.auto_deref_source_type_is_pointer(parent_v.expr) { refreshed_parent_type = refreshed_parent_type.deref() } parent_v.typ = refreshed_parent_type -- 2.39.5