From b65fa43d8e7a3ce7da8cab16c99c02d7fd399c76 Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Wed, 11 Mar 2026 11:48:48 +0300 Subject: [PATCH] checker: match on mutable sumtype doesn't compile (fixes #16506) --- vlib/v/checker/checker.v | 8 ++- vlib/v/checker/for.v | 2 +- vlib/v/checker/if.v | 10 ++-- vlib/v/checker/match.v | 3 +- ..._mutable_selector_sumtype_smartcast_test.v | 50 +++++++++++++++++++ 5 files changed, 64 insertions(+), 9 deletions(-) create mode 100644 vlib/v/tests/conditions/matches/match_mutable_selector_sumtype_smartcast_test.v diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index 00a5b57bb..af3b635fe 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -5209,7 +5209,7 @@ fn smartcast_index_expr_scope_key(expr ast.IndexExpr) string { // smartcast takes the expression with the current type which should be smartcasted to the target type in the given scope fn (mut c Checker) smartcast(mut expr ast.Expr, cur_type ast.Type, to_type_ ast.Type, mut scope ast.Scope, - is_comptime bool, is_option_unwrap bool) { + is_comptime bool, is_option_unwrap bool, allow_mut_selector_smartcast bool) { sym := c.table.sym(cur_type) to_type := if sym.kind == .interface && c.table.sym(to_type_).kind != .interface { to_type_.ref() @@ -5219,6 +5219,7 @@ fn (mut c Checker) smartcast(mut expr ast.Expr, cur_type ast.Type, to_type_ ast. match mut expr { ast.SelectorExpr { mut is_mut := false + mut root_is_arg := false mut smartcasts := []ast.Type{} expr_sym := c.table.sym(expr.expr_type) mut orig_type := ast.no_type @@ -5227,6 +5228,7 @@ fn (mut c Checker) smartcast(mut expr ast.Expr, cur_type ast.Type, to_type_ ast. if root_ident := expr.root_ident() { if v := scope.find_var(root_ident.name) { is_mut = v.is_mut + root_is_arg = v.is_arg } } } @@ -5243,7 +5245,9 @@ fn (mut c Checker) smartcast(mut expr ast.Expr, cur_type ast.Type, to_type_ ast. smartcasts << field.smartcasts } // smartcast either if the value is immutable or if the mut argument is explicitly given - if !is_mut || expr.is_mut || is_option_unwrap || orig_type.has_flag(.option) { + if !is_mut || expr.is_mut || is_option_unwrap + || orig_type.has_flag(.option) + || (allow_mut_selector_smartcast && root_is_arg) { smartcasts << to_type scope.register_struct_field(expr_str, ast.ScopeStructField{ struct_type: expr.expr_type diff --git a/vlib/v/checker/for.v b/vlib/v/checker/for.v index f9b1e9b04..2fbe1d547 100644 --- a/vlib/v/checker/for.v +++ b/vlib/v/checker/for.v @@ -327,7 +327,7 @@ fn (mut c Checker) for_stmt(mut node ast.ForStmt) { 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] { c.smartcast(mut node.cond.left, node.cond.left_type, node.cond.right_type, mut - node.scope, false, false) + node.scope, false, false, false) } } } diff --git a/vlib/v/checker/if.v b/vlib/v/checker/if.v index 15982b297..5150b268c 100644 --- a/vlib/v/checker/if.v +++ b/vlib/v/checker/if.v @@ -603,10 +603,10 @@ fn (mut c Checker) smartcast_if_conds(mut node ast.Expr, mut scope ast.Scope, co if node.left is ast.Ident && c.comptime.get_ct_type_var(node.left) == .smartcast { node.left_type = c.type_resolver.get_type(node.left) c.smartcast(mut node.left, node.left_type, node.left_type.clear_flag(.option), mut - scope, true, true) + scope, true, true, false) } else { c.smartcast(mut node.left, node.left_type, node.left_type.clear_flag(.option), mut - scope, false, true) + scope, false, true, false) } } } else if node.op == .key_is { @@ -685,7 +685,7 @@ fn (mut c Checker) smartcast_if_conds(mut node ast.Expr, mut scope ast.Scope, co if !skip_smartcast && (left_final_sym.kind in [.interface, .sum_type] || is_option_unwrap) { c.smartcast(mut node.left, node.left_type, right_type, mut - scope, is_comptime, is_option_unwrap) + scope, is_comptime, is_option_unwrap, false) } } } @@ -705,10 +705,10 @@ fn (mut c Checker) smartcast_if_conds(mut node ast.Expr, mut scope ast.Scope, co if c.comptime.get_ct_type_var(first_cond.left) == .smartcast { first_cond.left_type = c.type_resolver.get_type(first_cond.left) c.smartcast(mut first_cond.left, first_cond.left_type, first_cond.left_type.clear_flag(.option), mut - scope, true, true) + scope, true, true, false) } else { c.smartcast(mut first_cond.left, first_cond.left_type, first_cond.left_type.clear_flag(.option), mut - scope, false, true) + scope, false, true, false) } } } diff --git a/vlib/v/checker/match.v b/vlib/v/checker/match.v index ac10c58fe..b79fb152a 100644 --- a/vlib/v/checker/match.v +++ b/vlib/v/checker/match.v @@ -849,8 +849,9 @@ fn (mut c Checker) match_exprs(mut node ast.MatchExpr, cond_type_sym ast.TypeSym expr_type = expr_types[0].typ } + allow_mut_selector_smartcast := node.cond is ast.SelectorExpr c.smartcast(mut node.cond, node.cond_type, expr_type, mut branch.scope, - false, false) + false, false, allow_mut_selector_smartcast) } } } diff --git a/vlib/v/tests/conditions/matches/match_mutable_selector_sumtype_smartcast_test.v b/vlib/v/tests/conditions/matches/match_mutable_selector_sumtype_smartcast_test.v new file mode 100644 index 000000000..bd364fef5 --- /dev/null +++ b/vlib/v/tests/conditions/matches/match_mutable_selector_sumtype_smartcast_test.v @@ -0,0 +1,50 @@ +struct MatchMutableSelectorStandard {} + +struct MatchMutableSelectorCustom { +mut: + name string +} + +type MatchMutableSelectorIdentifier = MatchMutableSelectorStandard | MatchMutableSelectorCustom + +struct MatchMutableSelectorOwner { +mut: + identifier MatchMutableSelectorIdentifier +} + +fn (mut c MatchMutableSelectorCustom) rename_to(value string) string { + c.name = value + return c.name +} + +fn (mut o MatchMutableSelectorOwner) update_identifier(value string) !string { + match o.identifier { + MatchMutableSelectorStandard { + return error('not allowed') + } + MatchMutableSelectorCustom { + return o.identifier.rename_to(value) + } + } +} + +fn (o MatchMutableSelectorOwner) identifier_name() !string { + match o.identifier { + MatchMutableSelectorStandard { + return error('not allowed') + } + MatchMutableSelectorCustom { + return o.identifier.name + } + } +} + +fn test_match_smartcast_on_mutable_selector_without_match_mut() { + mut owner := MatchMutableSelectorOwner{ + identifier: MatchMutableSelectorCustom{ + name: 'before' + } + } + assert owner.update_identifier('after')! == 'after' + assert owner.identifier_name()! == 'after' +} -- 2.39.5