From 00ff5eabc0cef089901bcda105b034c66f3f72e8 Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Wed, 11 Mar 2026 16:46:50 +0300 Subject: [PATCH] checker: match []u8 gives &[]u8 (fixes #24054) --- vlib/v/ast/ast.v | 1 + vlib/v/checker/checker.v | 36 +++++++++++++++---- vlib/v/checker/comptime.v | 3 +- vlib/v/checker/fn.v | 9 +++-- vlib/v/checker/infix.v | 3 +- vlib/v/checker/return.v | 3 +- .../tests/match_return_mismatch_type_err.out | 21 ++++------- vlib/v/gen/c/cgen.v | 4 +++ .../interface_match_smartcast_array_test.v | 36 +++++++++++++++++++ 9 files changed, 90 insertions(+), 26 deletions(-) create mode 100644 vlib/v/tests/interfaces/interface_match_smartcast_array_test.v diff --git a/vlib/v/ast/ast.v b/vlib/v/ast/ast.v index d40381fa3..aacef71f7 100644 --- a/vlib/v/ast/ast.v +++ b/vlib/v/ast/ast.v @@ -1024,6 +1024,7 @@ pub struct ScopeStructField { pub: struct_type Type // type of struct name string + is_mut bool pos token.Pos typ Type orig_type Type // original sumtype type; 0 if it's not a sumtype diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index aeb89eee7..8a8b45935 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -2185,7 +2185,8 @@ fn (mut c Checker) selector_expr(mut node ast.SelectorExpr) ast.Type { if !prevent_sum_type_unwrapping_once { scope_field := node.scope.find_struct_field(node.expr.str(), typ, field_name) if scope_field != unsafe { nil } { - sf_smartcast_type := scope_field.smartcasts.last() + sf_smartcast_type := c.exposed_smartcast_type(scope_field.orig_type, + scope_field.smartcasts.last(), scope_field.is_mut) if c.inside_sql { node.typ = sf_smartcast_type } @@ -3589,7 +3590,8 @@ pub fn (mut c Checker) expr(mut node ast.Expr) ast.Type { if mut node.expr is ast.Ident { if mut node.expr.obj is ast.Var { ident_typ := if node.expr.obj.smartcasts.len > 0 { - node.expr.obj.smartcasts.last() + c.exposed_smartcast_type(node.expr.obj.orig_type, node.expr.obj.smartcasts.last(), + node.expr.obj.is_mut) } else { node.expr.obj.typ } @@ -3693,14 +3695,16 @@ pub fn (mut c Checker) expr(mut node ast.Expr) ast.Type { } else if node.expr.obj is ast.Var { var_obj := node.expr.obj as ast.Var if var_obj.smartcasts.len > 0 { - node.expr_type = c.unwrap_generic(var_obj.smartcasts.last()) + node.expr_type = c.unwrap_generic(c.exposed_smartcast_type(var_obj.orig_type, + var_obj.smartcasts.last(), var_obj.is_mut)) } } } else if mut node.expr is ast.Ident { if node.expr.obj is ast.Var { var_obj := node.expr.obj as ast.Var if var_obj.smartcasts.len > 0 { - node.expr_type = c.unwrap_generic(var_obj.smartcasts.last()) + node.expr_type = c.unwrap_generic(c.exposed_smartcast_type(var_obj.orig_type, + var_obj.smartcasts.last(), var_obj.is_mut)) } } } @@ -4959,7 +4963,12 @@ fn (mut c Checker) ident(mut node ast.Ident) ast.Type { is_sum_type_cast := obj.smartcasts.len != 0 && !c.prevent_sum_type_unwrapping_once c.prevent_sum_type_unwrapping_once = false - mut typ := if is_sum_type_cast { obj.smartcasts.last() } else { obj.typ } + mut typ := if is_sum_type_cast { + c.exposed_smartcast_type(obj.orig_type, obj.smartcasts.last(), + obj.is_mut) + } else { + obj.typ + } if typ == 0 { if mut obj.expr is ast.Ident { if obj.expr.kind == .unresolved { @@ -4990,7 +4999,8 @@ fn (mut c Checker) ident(mut node ast.Ident) ast.Type { typ = c.expr(mut obj.expr) } } - if c.inside_interface_deref && c.table.is_interface_var(obj) { + if c.inside_interface_deref && c.table.is_interface_var(obj) + && !is_sum_type_cast { typ = typ.deref() } is_option := typ.has_option_or_result() || node.or_expr.kind != .absent @@ -5302,6 +5312,7 @@ fn (mut c Checker) smartcast(mut expr ast.Expr, cur_type ast.Type, to_type_ ast. scope.register_struct_field(expr_str, ast.ScopeStructField{ struct_type: expr.expr_type name: expr.field_name + is_mut: expr.is_mut || is_mut typ: cur_type smartcasts: smartcasts pos: expr.pos @@ -5404,6 +5415,19 @@ fn (mut c Checker) smartcast(mut expr ast.Expr, cur_type ast.Type, to_type_ ast. } } +@[inline] +fn (c &Checker) exposed_smartcast_type(orig_type ast.Type, smartcast_type ast.Type, is_mut bool) ast.Type { + if is_mut || orig_type == 0 || smartcast_type == 0 || !smartcast_type.is_ptr() + || orig_type.is_ptr() { + return smartcast_type + } + if c.table.final_sym(orig_type).kind == .interface + && c.table.final_sym(smartcast_type).kind != .interface { + return smartcast_type.deref() + } + return smartcast_type +} + fn (mut c Checker) select_expr(mut node ast.SelectExpr) ast.Type { node.is_expr = c.expected_type != ast.void_type node.expected_type = c.expected_type diff --git a/vlib/v/checker/comptime.v b/vlib/v/checker/comptime.v index 0874c9f03..7c796c1c0 100644 --- a/vlib/v/checker/comptime.v +++ b/vlib/v/checker/comptime.v @@ -1079,7 +1079,8 @@ fn (mut c Checker) get_expr_type(cond ast.Expr) ast.Type { // var checked_type = c.unwrap_generic(var.typ) if var.smartcasts.len > 0 { - checked_type = c.unwrap_generic(var.smartcasts.last()) + checked_type = c.unwrap_generic(c.exposed_smartcast_type(var.orig_type, + var.smartcasts.last(), var.is_mut)) } } return checked_type diff --git a/vlib/v/checker/fn.v b/vlib/v/checker/fn.v index 3af2cb871..50d56e7a0 100644 --- a/vlib/v/checker/fn.v +++ b/vlib/v/checker/fn.v @@ -788,7 +788,8 @@ fn (mut c Checker) anon_fn(mut node ast.AnonFn) ast.Type { var.pos) } ptyp := if parent_var.smartcasts.len > 0 { - parent_var.smartcasts.last() + c.exposed_smartcast_type(parent_var.orig_type, parent_var.smartcasts.last(), + parent_var.is_mut) } else { parent_var.typ } @@ -1392,7 +1393,8 @@ fn (mut c Checker) fn_call(mut node ast.CallExpr, mut continue_check &bool) ast. } ast.Var { if obj.smartcasts.len != 0 { - typ = obj.smartcasts.last() + typ = c.exposed_smartcast_type(obj.orig_type, obj.smartcasts.last(), + obj.is_mut) } else { if obj.typ == 0 { if mut obj.expr is ast.IfGuardExpr { @@ -2475,7 +2477,8 @@ fn (mut c Checker) method_call(mut node ast.CallExpr, mut continue_check &bool) scope_field := node.scope.find_struct_field(node.left.str(), node.left_type, method_name) if scope_field != unsafe { nil } { - field_typ = scope_field.smartcasts.last() + field_typ = c.exposed_smartcast_type(scope_field.orig_type, scope_field.smartcasts.last(), + scope_field.is_mut) node.is_unwrapped_fn_selector = true } else { c.error('Option function field must be unwrapped first', node.pos) diff --git a/vlib/v/checker/infix.v b/vlib/v/checker/infix.v index 6654e3bac..2936ff728 100644 --- a/vlib/v/checker/infix.v +++ b/vlib/v/checker/infix.v @@ -1035,7 +1035,8 @@ fn (mut c Checker) maybe_wrap_index_expr_smartcast(mut expr ast.Expr, expr_type expr_key := smartcast_index_expr_scope_key(index_expr) if var := scope.find_var(expr_key) { if var.smartcasts.len > 0 { - cast_type := var.smartcasts.last() + cast_type := c.exposed_smartcast_type(var.orig_type, var.smartcasts.last(), + var.is_mut) if cast_type != expr_type { expr = ast.AsCast{ expr: ast.Expr(index_expr) diff --git a/vlib/v/checker/return.v b/vlib/v/checker/return.v index e8a890d20..511645bf6 100644 --- a/vlib/v/checker/return.v +++ b/vlib/v/checker/return.v @@ -112,7 +112,8 @@ fn (mut c Checker) return_stmt(mut node ast.Return) { } else { if mut expr is ast.Ident && expr.obj is ast.Var { if expr.obj.smartcasts.len > 0 { - typ = c.unwrap_generic(expr.obj.smartcasts.last()) + typ = c.unwrap_generic(c.exposed_smartcast_type(expr.obj.orig_type, + expr.obj.smartcasts.last(), expr.obj.is_mut)) } if expr.obj.ct_type_var != .no_comptime { typ = c.type_resolver.get_type_or_default(expr, typ) diff --git a/vlib/v/checker/tests/match_return_mismatch_type_err.out b/vlib/v/checker/tests/match_return_mismatch_type_err.out index a81d56a23..9af111722 100644 --- a/vlib/v/checker/tests/match_return_mismatch_type_err.out +++ b/vlib/v/checker/tests/match_return_mismatch_type_err.out @@ -5,13 +5,6 @@ vlib/v/checker/tests/match_return_mismatch_type_err.vv:3:3: notice: match is alw | ^ 4 | else { 22 } 5 | } -vlib/v/checker/tests/match_return_mismatch_type_err.vv:27:6: warning: cannot assign a reference to a value (this will be an error soon) left=string false right=string true ptr=true - 25 | - 26 | mut res := '' - 27 | res = match any { - | ^ - 28 | string { &any } - 29 | else { &variable } vlib/v/checker/tests/match_return_mismatch_type_err.vv:4:10: error: return type mismatch, it should be `string`, but it is instead `int literal` 2 | a := match 1 { 3 | 1 { 'aa' } @@ -19,13 +12,6 @@ vlib/v/checker/tests/match_return_mismatch_type_err.vv:4:10: error: return type | ~~ 5 | } 6 | println(a) -vlib/v/checker/tests/match_return_mismatch_type_err.vv:18:10: error: return type mismatch, it should be `&string`, but it is instead `string` - 16 | _ = match any { - 17 | string { &any } - 18 | else { variable } - | ~~~~~~~~ - 19 | } - 20 | vlib/v/checker/tests/match_return_mismatch_type_err.vv:23:10: error: return type mismatch, it should be `string`, but it is instead `&string` 21 | _ = match any { 22 | string { any } @@ -33,6 +19,13 @@ vlib/v/checker/tests/match_return_mismatch_type_err.vv:23:10: error: return type | ^ 24 | } 25 | +vlib/v/checker/tests/match_return_mismatch_type_err.vv:29:10: error: return type mismatch, it should be `string`, but it is instead `&string` + 27 | res = match any { + 28 | string { &any } + 29 | else { &variable } + | ^ + 30 | } + 31 | println(res) vlib/v/checker/tests/match_return_mismatch_type_err.vv:43:10: error: return type mismatch, it should be `&string`, but it is instead `string` 41 | _ = match any { 42 | string { &any } diff --git a/vlib/v/gen/c/cgen.v b/vlib/v/gen/c/cgen.v index c61763fce..0d9f0ec0d 100644 --- a/vlib/v/gen/c/cgen.v +++ b/vlib/v/gen/c/cgen.v @@ -5944,6 +5944,9 @@ fn (mut g Gen) ident(node ast.Ident) { // Skip smartcasts for comptime_for variables to avoid using stale types skip_smartcasts := g.is_comptime_for_var(node) if node.obj.smartcasts.len > 0 && !skip_smartcasts { + needs_interface_smartcast_deref := g.table.is_interface_smartcast(node.obj) + && node.info.typ != 0 + && node.obj.smartcasts.last().nr_muls() == node.info.typ.nr_muls() + 1 obj_sym := g.table.final_sym(g.unwrap_generic(node.obj.typ)) if !prevent_sum_type_unwrapping_once { nested_unwrap := node.obj.smartcasts.len > 1 @@ -5972,6 +5975,7 @@ fn (mut g Gen) ident(node ast.Ident) { g.write('*') } } else if (g.inside_interface_deref && g.table.is_interface_var(node.obj)) + || needs_interface_smartcast_deref || node.obj.ct_type_var == .smartcast || (obj_sym.kind == .interface && g.table.type_kind(node.obj.typ) == .any) { diff --git a/vlib/v/tests/interfaces/interface_match_smartcast_array_test.v b/vlib/v/tests/interfaces/interface_match_smartcast_array_test.v new file mode 100644 index 000000000..db487ad9f --- /dev/null +++ b/vlib/v/tests/interfaces/interface_match_smartcast_array_test.v @@ -0,0 +1,36 @@ +interface Value {} + +fn bytes_to_blr(param []u8) ([]u8, []u8) { + return param.clone(), param +} + +fn interface_match_call(v Value) []u8 { + match v { + []u8 { + blr, value := bytes_to_blr(v) + assert value == [u8(1), 2, 3] + return blr + } + else { + return []u8{} + } + } +} + +fn interface_match_return(v Value) []u8 { + match v { + []u8 { + matched := v + return matched + } + else { + return []u8{} + } + } +} + +fn test_interface_match_array_smartcast_for_call_and_return() { + input := Value([u8(1), 2, 3]) + assert interface_match_call(input) == [u8(1), 2, 3] + assert interface_match_return(input) == [u8(1), 2, 3] +} -- 2.39.5