From ac7ca1271687ee6e0a69799523a779043f39eeb0 Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Tue, 14 Apr 2026 12:45:25 +0300 Subject: [PATCH] checker: support smart casting receiver var (fixes #24670) --- doc/docs.md | 2 ++ vlib/v/ast/ast.v | 7 +++---- vlib/v/checker/assign.v | 20 ++++++++++++-------- vlib/v/checker/checker.v | 5 +++++ vlib/v/checker/for.v | 6 +++++- vlib/v/checker/if.v | 2 +- vlib/v/tests/loops/for_smartcast_test.v | 22 ++++++++++++++++++++++ 7 files changed, 50 insertions(+), 14 deletions(-) diff --git a/doc/docs.md b/doc/docs.md index 7bbd403d3..dd82f2e54 100644 --- a/doc/docs.md +++ b/doc/docs.md @@ -4247,6 +4247,8 @@ if mut w is Mars { Otherwise `w` would keep its original type. > This works for both simple variables and complex expressions like `user.name` > and `values[i]`. +> The same rule applies to mutable method receivers, for example +> `fn (mut w World) fn_name() { for mut w is Mars { ... } }`. Smart casts also work on indexed expressions in `match` branches: diff --git a/vlib/v/ast/ast.v b/vlib/v/ast/ast.v index af531b6d8..c98b64193 100644 --- a/vlib/v/ast/ast.v +++ b/vlib/v/ast/ast.v @@ -3190,11 +3190,10 @@ pub fn (expr Expr) is_reference() bool { // remove_par removes all parenthesis and gets the innermost Expr pub fn (mut expr Expr) remove_par() Expr { - mut e := expr - for mut e is ParExpr { - e = e.expr + for mut expr is ParExpr { + expr = expr.expr } - return e + return expr } // is `expr` a literal, i.e. it does not depend on any other declarations (C compile time constant) diff --git a/vlib/v/checker/assign.v b/vlib/v/checker/assign.v index 3d4ac89ad..9b9253ff0 100644 --- a/vlib/v/checker/assign.v +++ b/vlib/v/checker/assign.v @@ -4,6 +4,10 @@ module checker import v.ast +fn assign_expr_is_auto_deref(expr ast.Expr) bool { + return expr.is_auto_deref_var() +} + // TODO: 980 line function fn (mut c Checker) assign_stmt(mut node ast.AssignStmt) { prev_inside_assign := c.inside_assign @@ -317,7 +321,7 @@ fn (mut c Checker) assign_stmt(mut node ast.AssignStmt) { c.expr(mut right) } } - if right.is_auto_deref_var() && right_type.is_ptr() { + if assign_expr_is_auto_deref(right) && right_type.is_ptr() { left_type = ast.mktyp(right_type.deref()) } else { left_type = ast.mktyp(right_type) @@ -680,7 +684,7 @@ or use an explicit `unsafe{ a[..] }`, if you do not want a copy of the slice.', } if left_sym.kind == .map && is_assign && right_sym.kind == .map && !c.inside_unsafe && !left.is_blank_ident() && right.is_lvalue() && right !is ast.ComptimeSelector - && (!right_type.is_ptr() || (right is ast.Ident && right.is_auto_deref_var())) { + && (!right_type.is_ptr() || (right is ast.Ident && assign_expr_is_auto_deref(right))) { // Do not allow `a = b` c.error('cannot copy map: call `move` or `clone` method (or use a reference)', right.pos()) @@ -715,7 +719,7 @@ or use an explicit `unsafe{ a[..] }`, if you do not want a copy of the slice.', } } } - if left_type.is_any_kind_of_pointer() && !left.is_auto_deref_var() { + if left_type.is_any_kind_of_pointer() && !assign_expr_is_auto_deref(left) { if !c.inside_unsafe && node.op !in [.assign, .decl_assign] { // ptr op= if !c.pref.translated && !c.file.is_translated { @@ -755,7 +759,7 @@ or use an explicit `unsafe{ a[..] }`, if you do not want a copy of the slice.', .assign {} // No need to do single side check for =. But here put it first for speed. .plus_assign, .minus_assign { // allow literal values to auto deref var (e.g.`for mut v in values { v += 1.0 }`) - left_deref := if left.is_auto_deref_var() && left_type.is_ptr() { + left_deref := if assign_expr_is_auto_deref(left) && left_type.is_ptr() { left_type.deref() } else { left_type @@ -938,7 +942,7 @@ or use an explicit `unsafe{ a[..] }`, if you do not want a copy of the slice.', left_deref := left_type.deref() right_deref := if right.is_pure_literal() { right.get_pure_type() - } else if right.is_auto_deref_var() && right_type.is_ptr() { + } else if assign_expr_is_auto_deref(right) && right_type.is_ptr() { right_type.deref() } else { right_type @@ -948,15 +952,15 @@ or use an explicit `unsafe{ a[..] }`, if you do not want a copy of the slice.', } } // allow literal values to auto deref var (e.g.`for mut v in values { v = 1.0 }`) - if left.is_auto_deref_var() || right.is_auto_deref_var() { - left_deref := if left.is_auto_deref_var() && left_type.is_ptr() { + if assign_expr_is_auto_deref(left) || assign_expr_is_auto_deref(right) { + left_deref := if assign_expr_is_auto_deref(left) && left_type.is_ptr() { left_type.deref() } else { left_type } right_deref := if right.is_pure_literal() { right.get_pure_type() - } else if right.is_auto_deref_var() && right_type.is_ptr() { + } else if assign_expr_is_auto_deref(right) && right_type.is_ptr() { right_type.deref() } else { right_type diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index c053f3227..4e4a301b1 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -6593,12 +6593,15 @@ fn (mut c Checker) smartcast(mut expr ast.Expr, cur_type ast.Type, to_type_ ast. mut orig_type := 0 mut is_inherited := false mut is_auto_heap := false + mut is_auto_deref := false mut ct_type_var := ast.ComptimeVarKind.no_comptime mut is_ct_type_unwrapped := false if mut expr.obj is ast.Var { is_ct_type_unwrapped = expr.obj.ct_type_var != ast.ComptimeVarKind.no_comptime is_mut = expr.obj.is_mut is_auto_heap = expr.obj.is_auto_heap + is_auto_deref = expr.obj.is_auto_deref && c.table.cur_fn != unsafe { nil } + && c.table.cur_fn.is_method && c.table.cur_fn.receiver.name == expr.name smartcasts << expr.obj.smartcasts is_already_casted = expr.obj.pos.pos == expr.pos.pos if orig_type == 0 { @@ -6631,6 +6634,7 @@ fn (mut c Checker) smartcast(mut expr ast.Expr, cur_type ast.Type, to_type_ ast. pos: expr.pos is_used: true is_mut: expr.is_mut + is_auto_deref: is_auto_deref is_inherited: is_inherited is_auto_heap: is_auto_heap smartcasts: [to_type] @@ -6654,6 +6658,7 @@ fn (mut c Checker) smartcast(mut expr ast.Expr, cur_type ast.Type, to_type_ ast. pos: expr.pos is_used: true is_mut: expr.is_mut || is_mut + is_auto_deref: is_auto_deref is_inherited: is_inherited is_auto_heap: is_auto_heap is_unwrapped: is_option_unwrap diff --git a/vlib/v/checker/for.v b/vlib/v/checker/for.v index f46289b22..2103d02af 100644 --- a/vlib/v/checker/for.v +++ b/vlib/v/checker/for.v @@ -344,7 +344,11 @@ fn (mut c Checker) for_stmt(mut node ast.ForStmt) { } if mut node.cond is ast.InfixExpr && node.cond.op == .key_is { if node.cond.right is ast.TypeNode && node.cond.left in [ast.Ident, ast.SelectorExpr] { - if c.table.type_kind(node.cond.left_type) in [.sum_type, .interface] { + left_kind := c.table.type_kind(node.cond.left_type) + is_receiver_smartcast := left_kind == .placeholder && node.cond.left.is_auto_deref_var() + && node.cond.left_type.is_ptr() + && c.table.final_sym(node.cond.left_type).kind in [.sum_type, .interface] + if left_kind in [.sum_type, .interface] || is_receiver_smartcast { c.smartcast(mut node.cond.left, node.cond.left_type, node.cond.right_type, mut node.scope, false, false, false) } diff --git a/vlib/v/checker/if.v b/vlib/v/checker/if.v index 7367f46ec..c5df39734 100644 --- a/vlib/v/checker/if.v +++ b/vlib/v/checker/if.v @@ -447,7 +447,7 @@ fn (mut c Checker) if_expr(mut node ast.IfExpr) ast.Type { if node.typ == ast.void_type { // first branch of if expression node.is_expr = true - if stmt.expr.is_auto_deref_var() { + if stmt.expr.is_auto_deref_var() && stmt.typ.is_ptr() { node.typ = stmt.typ.deref() } else { node.typ = stmt.typ diff --git a/vlib/v/tests/loops/for_smartcast_test.v b/vlib/v/tests/loops/for_smartcast_test.v index ea327cb92..7dcec2154 100644 --- a/vlib/v/tests/loops/for_smartcast_test.v +++ b/vlib/v/tests/loops/for_smartcast_test.v @@ -54,3 +54,25 @@ fn test_conditional_break() { } assert true } + +type ReceiverExpr = ReceiverPar | int + +struct ReceiverPar { + expr ReceiverExpr +} + +fn (mut expr ReceiverExpr) strip_par() ReceiverExpr { + for mut expr is ReceiverPar { + expr = expr.expr + } + return expr +} + +fn test_receiver_var_smartcast() { + mut expr := ReceiverExpr(ReceiverPar{ + expr: ReceiverExpr(ReceiverPar{ + expr: ReceiverExpr(1) + }) + }) + assert expr.strip_par() == ReceiverExpr(1) +} -- 2.39.5