From 81c0300e1c0fbbd0ea203e7d2f85d9fae6051192 Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Wed, 15 Apr 2026 06:25:33 +0300 Subject: [PATCH] checker: fix mut smartcasts not working correctly (fixes #17440) --- vlib/toml/parser/parser.v | 5 +- vlib/v/ast/ast.v | 26 ++- vlib/v/ast/table.v | 11 +- vlib/v/checker/assign.v | 76 +++++--- vlib/v/checker/checker.v | 168 +++++++++++------- vlib/v/checker/containers.v | 10 +- vlib/v/checker/fn.v | 37 ++-- vlib/v/checker/for.v | 14 +- vlib/v/checker/if.v | 51 +++--- vlib/v/checker/infix.v | 16 +- vlib/v/checker/match.v | 31 ++-- vlib/v/checker/return.v | 2 +- vlib/v/checker/struct.v | 33 ++-- .../tests/if_smartcast_mutation_err.out | 14 ++ .../tests/if_smartcast_mutation_err.vv | 12 ++ .../tests/incorrect_smartcast3_err.out | 35 ---- .../tests/infix_sumtype_in_array_err.out | 7 - .../match_branch_call_expr_arg_mismatch.out | 28 --- .../tests/sum_type_mutable_cast_err.out | 28 --- vlib/v/gen/c/assert.v | 6 +- vlib/v/gen/c/assign.v | 48 +++-- vlib/v/gen/c/cgen.v | 8 +- vlib/v/parser/asm.v | 4 +- vlib/v/parser/expr.v | 6 +- vlib/v/parser/parser.v | 13 +- .../tests/conditions/ifs/if_smartcast_test.v | 20 ++- vlib/v/transformer/transformer.v | 6 +- 27 files changed, 415 insertions(+), 300 deletions(-) create mode 100644 vlib/v/checker/tests/if_smartcast_mutation_err.out create mode 100644 vlib/v/checker/tests/if_smartcast_mutation_err.vv diff --git a/vlib/toml/parser/parser.v b/vlib/toml/parser/parser.v index 01efc20fa..f4bff8e4d 100644 --- a/vlib/toml/parser/parser.v +++ b/vlib/toml/parser/parser.v @@ -1401,6 +1401,7 @@ pub fn (mut p Parser) key() !ast.Key { // A few small exceptions that can't easily be done via `checker` or `decoder` *after* the // main table has been build since information like `is_multiline` is lost when using the key.text as a // V `map` key directly. + mut decoded_key := key if key is ast.Quoted { if p.config.run_checks { quoted := key as ast.Quoted @@ -1416,11 +1417,11 @@ pub fn (mut p Parser) key() !ast.Key { if p.config.decode_values { mut quoted := key as ast.Quoted decoder.decode_quoted_escapes(mut quoted)! - key = ast.Key(quoted) + decoded_key = ast.Key(quoted) } } - return key + return decoded_key } // key_value parse and returns a pair `ast.Key` and `ast.Value` type. diff --git a/vlib/v/ast/ast.v b/vlib/v/ast/ast.v index 16e688c7b..e8e49f255 100644 --- a/vlib/v/ast/ast.v +++ b/vlib/v/ast/ast.v @@ -329,8 +329,14 @@ pub mut: // root_ident returns the origin ident where the selector started. pub fn (e &SelectorExpr) root_ident() ?Ident { mut root := e.expr - for mut root is SelectorExpr { - root = root.expr + for { + mut next_root := Expr(EmptyExpr{}) + if mut root is SelectorExpr { + next_root = root.expr + } else { + break + } + root = next_root } if mut root is Ident { return root @@ -3051,8 +3057,10 @@ pub fn (mut lx IndexExpr) recursive_arraymap_set_is_setter() { lx.is_setter = true if mut lx.left is IndexExpr { lx.left.recursive_arraymap_set_is_setter() - } else if mut lx.left is SelectorExpr && lx.left.expr is IndexExpr { - lx.left.expr.recursive_arraymap_set_is_setter() + } else if mut lx.left is SelectorExpr { + if mut lx.left.expr is IndexExpr { + lx.left.expr.recursive_arraymap_set_is_setter() + } } } @@ -3193,8 +3201,14 @@ 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 next_expr := Expr(EmptyExpr{}) + if mut e is ParExpr { + next_expr = e.expr + } else { + break + } + e = next_expr } return e } diff --git a/vlib/v/ast/table.v b/vlib/v/ast/table.v index 14a9cfd70..59de55eee 100644 --- a/vlib/v/ast/table.v +++ b/vlib/v/ast/table.v @@ -1710,11 +1710,14 @@ pub fn (mut t Table) find_or_register_fn_type(f Fn, is_anon bool, has_decl bool) } anon := f.name == '' || is_anon existing_idx := t.type_idxs[name] - if existing_idx > 0 && t.type_symbols[existing_idx].kind != .placeholder { - if t.type_symbols[existing_idx].info is FnType && !has_decl { - t.type_symbols[existing_idx].info.has_decl = has_decl + if existing_idx > 0 { + mut existing_sym := &t.type_symbols[existing_idx] + if existing_sym.kind != .placeholder { + if mut existing_sym.info is FnType && !has_decl { + existing_sym.info.has_decl = has_decl + } + return existing_idx } - return existing_idx } return t.register_sym( kind: .function diff --git a/vlib/v/checker/assign.v b/vlib/v/checker/assign.v index be250bbb4..b5a5e9781 100644 --- a/vlib/v/checker/assign.v +++ b/vlib/v/checker/assign.v @@ -8,6 +8,29 @@ fn assign_expr_is_auto_deref(expr ast.Expr) bool { return expr.is_auto_deref_var() } +fn (mut c Checker) smartcasted_assign_lhs_type(expr ast.Expr, fallback_type ast.Type) ast.Type { + match expr { + ast.Ident { + if expr.obj is ast.Var && expr.obj.smartcasts.len > 0 { + return c.exposed_smartcast_type(expr.obj.orig_type, expr.obj.smartcasts.last(), + expr.obj.is_mut) + } + } + ast.SelectorExpr { + if expr.expr_type != 0 { + scope_field := expr.scope.find_struct_field(smartcast_selector_expr_str(expr), + expr.expr_type, expr.field_name) + if scope_field != unsafe { nil } && scope_field.smartcasts.len > 0 { + return c.exposed_smartcast_type(scope_field.orig_type, + scope_field.smartcasts.last(), scope_field.is_mut) + } + } + } + else {} + } + return fallback_type +} + // TODO: 980 line function fn (mut c Checker) assign_stmt(mut node ast.AssignStmt) { prev_inside_assign := c.inside_assign @@ -141,10 +164,8 @@ fn (mut c Checker) assign_stmt(mut node ast.AssignStmt) { for i, mut left in node.left { node.left_types << ast.void_type if mut left is ast.Ident { - if left.info is ast.IdentVar { - mut ident_var_info := left.info as ast.IdentVar - ident_var_info.typ = ast.void_type - left.info = ident_var_info + if mut left.info is ast.IdentVar { + left.info.typ = ast.void_type if mut left.obj is ast.Var { left.obj.typ = ast.void_type } @@ -234,6 +255,7 @@ fn (mut c Checker) assign_stmt(mut node ast.AssignStmt) { c.is_index_assign = true } left_type = c.expr(mut left) + left_type = c.smartcasted_assign_lhs_type(left, left_type) c.is_index_assign = false c.expected_type = c.unwrap_generic(left_type) is_shared_re_assign = left is ast.Ident && left.info is ast.IdentVar @@ -281,6 +303,8 @@ fn (mut c Checker) assign_stmt(mut node ast.AssignStmt) { } mut right := if i < node.right.len { node.right[i] } else { node.right[0] } mut right_type := node.right_types[i] + right_pos := right.pos() + left_pos := left.pos() if mut right is ast.Ident { // resolve shared right variable if right_type.has_flag(.shared_f) { @@ -359,7 +383,7 @@ fn (mut c Checker) assign_stmt(mut node ast.AssignStmt) { && right.name in c.global_names { ident_var_info := left.info as ast.IdentVar if ident_var_info.share == .shared_t { - c.error('cannot assign global variable to shared variable', right.pos()) + c.error('cannot assign global variable to shared variable', right_pos) } } if right_type.is_ptr() && left_type.is_ptr() { @@ -382,11 +406,11 @@ fn (mut c Checker) assign_stmt(mut node ast.AssignStmt) { if right is ast.StructInit { right_sym := c.table.sym(right_type) - c.check_any_type(right_type, right_sym, right.pos()) + c.check_any_type(right_type, right_sym, right_pos) } if left is ast.ParExpr && is_decl { - c.error('parentheses are not supported on the left side of `:=`', left.pos()) + c.error('parentheses are not supported on the left side of `:=`', left_pos) } left = left.remove_par() is_assign := node.op in [.assign, .decl_assign] @@ -429,7 +453,7 @@ fn (mut c Checker) assign_stmt(mut node ast.AssignStmt) { // println(x) // }` c.error('use of untyped nil in assignment (use `unsafe` | ${c.inside_unsafe})', - right.pos()) + right_pos) } mut ident_var_info := left.info as ast.IdentVar if ident_var_info.share == .shared_t || is_shared_re_assign { @@ -523,7 +547,7 @@ fn (mut c Checker) assign_stmt(mut node ast.AssignStmt) { } if node.op == .assign && left_type.has_flag(.option) && right is ast.UnsafeExpr && right.expr.is_nil() { - c.error('cannot assign `nil` to option value', right.pos()) + c.error('cannot assign `nil` to option value', right_pos) } } } @@ -563,7 +587,7 @@ fn (mut c Checker) assign_stmt(mut node ast.AssignStmt) { } } if left_type.has_flag(.option) && right is ast.UnsafeExpr && right.expr.is_nil() { - c.error('cannot assign `nil` to option value', right.pos()) + c.error('cannot assign `nil` to option value', right_pos) } } else { @@ -647,17 +671,22 @@ fn (mut c Checker) assign_stmt(mut node ast.AssignStmt) { } } } - } else if mut left is ast.Ident && left.kind != .blank_ident - && right is ast.IndexExpr { - right_index_expr := right as ast.IndexExpr - if right_index_expr.left is ast.Ident && right_index_expr.index is ast.RangeExpr - && (right_index_expr.left.is_mut() || left.is_mut()) && !c.inside_unsafe { + } else if mut left is ast.Ident && left.kind != .blank_ident { + mut should_clone_slice := false + right_notice_pos := right.pos() + right_expr := right + if right_expr is ast.IndexExpr { + should_clone_slice = right_expr.left is ast.Ident + && right_expr.index is ast.RangeExpr + && (right_expr.left.is_mut() || left.is_mut()) && !c.inside_unsafe + } + if should_clone_slice { // `mut a := arr[..]` auto add clone() -> `mut a := arr[..].clone()` c.add_error_detail_with_pos('To silence this notice, use either an explicit `a[..].clone()`, or use an explicit `unsafe{ a[..] }`, if you do not want a copy of the slice.', - right.pos()) - c.note('an implicit clone of the slice was done here', right.pos()) - right = ast.CallExpr{ + right_notice_pos) + c.note('an implicit clone of the slice was done here', right_notice_pos) + mut cloned_right := ast.CallExpr{ name: 'clone' kind: .clone left: right @@ -668,8 +697,9 @@ or use an explicit `unsafe{ a[..] }`, if you do not want a copy of the slice.', scope: c.fn_scope is_return_used: true } - right_type = c.expr(mut right) - node.right[i] = right + right_type = c.expr(mut cloned_right) + right = cloned_right + node.right[i] = cloned_right } } } @@ -1002,9 +1032,9 @@ or use an explicit `unsafe{ a[..] }`, if you do not want a copy of the slice.', if left_sym.kind == .array_fixed && right_sym.kind == .array && right is ast.ArrayInit { c.add_error_detail('try `${left} = ${right}!` instead (with `!` after the array literal)') - c.error('cannot assign to `${left}`: ${err.msg()}', right.pos()) + c.error('cannot assign to `${left}`: ${err.msg()}', right_pos) } else { - c.error('cannot assign to `${left}`: ${err.msg()}', right.pos()) + c.error('cannot assign to `${left}`: ${err.msg()}', right_pos) } } } @@ -1022,7 +1052,7 @@ or use an explicit `unsafe{ a[..] }`, if you do not want a copy of the slice.', } if left_sym.info is ast.Struct && !left_sym.info.is_anon && right is ast.StructInit && right.is_anon { - c.error('cannot assign anonymous `struct` to a typed `struct`', right.pos()) + c.error('cannot assign anonymous `struct` to a typed `struct`', right_pos) } if right_sym.kind == .alias && right_sym.name == 'byte' { c.error('byte is deprecated, use u8 instead', right.pos()) diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index 66c0e3772..7398cefcb 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -518,7 +518,8 @@ pub fn (mut c Checker) check(mut ast_file ast.File) { c.stmt_level = 0 for mut stmt in ast_file.stmts { - if stmt is ast.GlobalDecl { + is_global_decl := stmt is ast.GlobalDecl + if is_global_decl { c.expr_level = 0 c.stmt(mut stmt) } @@ -1632,7 +1633,10 @@ fn (mut c Checker) fail_if_immutable(mut expr ast.Expr) (string, token.Pos) { if mut expr.obj is ast.Var { if !expr.obj.is_mut && !c.pref.translated && !c.file.is_translated && !c.inside_unsafe { - if c.inside_anon_fn { + if expr.obj.smartcasts.len > 0 { + c.error('cannot mutate `${expr.name}` in a non-mut smartcast, use `if mut ${expr.name} ...`', + expr.pos) + } else if c.inside_anon_fn { c.error('the closure copy of `${expr.name}` is immutable, declare it with `mut` to make it mutable', expr.pos) } else { @@ -1727,6 +1731,15 @@ fn (mut c Checker) fail_if_immutable(mut expr ast.Expr) (string, token.Pos) { if expr.expr_type == 0 { return '', expr.pos } + scope_field := expr.scope.find_struct_field(smartcast_selector_expr_str(expr), + expr.expr_type, expr.field_name) + if scope_field != unsafe { nil } && scope_field.smartcasts.len > 0 + && !scope_field.is_mut && !c.pref.translated && !c.file.is_translated + && !c.inside_unsafe { + expr_str := expr.str() + c.error('cannot mutate `${expr_str}` in a non-mut smartcast, use `if mut ${expr_str} ...`', + expr.pos) + } // retrieve ast.Field if !c.ensure_type_exists(expr.expr_type, expr.pos) { return '', expr.pos @@ -3099,13 +3112,16 @@ fn (mut c Checker) const_decl(mut node ast.ConstDecl) { c.table.export_names[field.name] = check_name } } - if field.expr is ast.CallExpr { - sym := c.table.sym(c.check_expr_option_or_result_call(field.expr, - c.expr(mut field.expr))) + is_call_expr := field.expr is ast.CallExpr + if is_call_expr { + mut field_expr := field.expr + sym := c.table.sym(c.check_expr_option_or_result_call(field_expr, + c.expr(mut field_expr))) if sym.kind == .multi_return { c.error('const declarations do not support multiple return values yet', - field.expr.pos()) + field_expr.pos()) } + field.expr = field_expr } const_name := field.name.all_after_last('.') if const_name == c.mod && const_name != 'main' { @@ -3248,6 +3264,8 @@ fn (mut c Checker) enum_decl(mut node ast.EnumDecl) { } seen_enum_field_names[field.name] = i if field.has_expr { + mut replacement_expr := ast.empty_expr + mut has_replacement := false match mut field.expr { ast.IntegerLiteral { c.check_enum_field_integer_literal(field.expr, signed, node.is_multi_allowed, @@ -3309,7 +3327,8 @@ fn (mut c Checker) enum_decl(mut node ast.EnumDecl) { } iseen << ref_val // Transform to IntegerLiteral for code generation - field.expr = ast.IntegerLiteral{ + has_replacement = true + replacement_expr = ast.IntegerLiteral{ val: ref_val.str() pos: field.expr.pos } @@ -3332,7 +3351,8 @@ fn (mut c Checker) enum_decl(mut node ast.EnumDecl) { } useen << ref_val // Transform to IntegerLiteral for code generation - field.expr = ast.IntegerLiteral{ + has_replacement = true + replacement_expr = ast.IntegerLiteral{ val: ref_val.str() pos: field.expr.pos } @@ -3394,55 +3414,65 @@ fn (mut c Checker) enum_decl(mut node ast.EnumDecl) { c.check_enum_field_integer_literal(comptime_lit, signed, node.is_multi_allowed, senum_type, field.expr.pos, mut useen, enum_umin, enum_umax, mut iseen, enum_imin, enum_imax) - field.expr = comptime_lit + has_replacement = true + replacement_expr = comptime_lit } else { c.error('the default value for an enum has to be an integer', field.expr.pos) } } else { + mut handled_expr := false if mut field.expr is ast.Ident { if field.expr.language == .c { - continue - } - if field.expr.kind == .unresolved { - c.ident(mut field.expr) - } - if field.expr.kind == .constant && field.expr.obj.typ.is_int() { - // accepts int constants as enum value - if mut field.expr.obj is ast.ConstField { - if comptime_value := c.eval_comptime_const_expr(field.expr, 0) { - if comptime_lit := c.comptime_value_to_integer_literal(comptime_value, - field.expr.pos) - { - c.check_enum_field_integer_literal(comptime_lit, signed, + handled_expr = true + } else { + if field.expr.kind == .unresolved { + c.ident(mut field.expr) + } + if field.expr.kind == .constant && field.expr.obj.typ.is_int() { + handled_expr = true + // accepts int constants as enum value + if mut field.expr.obj is ast.ConstField { + if comptime_value := c.eval_comptime_const_expr(field.expr, 0) { + if comptime_lit := c.comptime_value_to_integer_literal(comptime_value, + field.expr.pos) + { + c.check_enum_field_integer_literal(comptime_lit, + signed, node.is_multi_allowed, senum_type, + field.expr.pos, mut useen, enum_umin, enum_umax, mut + iseen, enum_imin, enum_imax) + has_replacement = true + replacement_expr = comptime_lit + } + } + folded_expr := + c.checker_transformer.expr(mut field.expr.obj.expr) + + if folded_expr is ast.IntegerLiteral { + c.check_enum_field_integer_literal(folded_expr, signed, node.is_multi_allowed, senum_type, field.expr.pos, mut useen, enum_umin, enum_umax, mut iseen, enum_imin, enum_imax) - field.expr = comptime_lit - continue + has_replacement = true + replacement_expr = folded_expr } } - folded_expr := c.checker_transformer.expr(mut field.expr.obj.expr) - - if folded_expr is ast.IntegerLiteral { - c.check_enum_field_integer_literal(folded_expr, signed, - node.is_multi_allowed, senum_type, field.expr.pos, mut - useen, enum_umin, enum_umax, mut iseen, enum_imin, - enum_imax) - field.expr = folded_expr - } } - continue } } - mut pos := field.expr.pos() - if pos.pos == 0 { - pos = field.pos + if !handled_expr { + mut pos := field.expr.pos() + if pos.pos == 0 { + pos = field.pos + } + c.error('the default value for an enum has to be an integer', pos) } - c.error('the default value for an enum has to be an integer', pos) } } + if has_replacement { + field.expr = replacement_expr + } } else { if signed { if iseen.len > 0 { @@ -4906,16 +4936,17 @@ fn (c &Checker) expr_is_known_zero_integer(expr ast.Expr) bool { // } fn (mut c Checker) rewrite_smartcast_generic_wrapper_cast(mut node ast.CastExpr, to_type ast.Type) bool { - if mut node.expr is ast.Ident { - if mut node.expr.obj is ast.Var { - if node.expr.obj.smartcasts.len == 0 - || c.table.sym(c.unwrap_generic(node.expr.obj.typ)).kind != .sum_type { + expr := node.expr + if expr is ast.Ident { + if expr.obj is ast.Var { + if expr.obj.smartcasts.len == 0 + || c.table.sym(c.unwrap_generic(expr.obj.typ)).kind != .sum_type { return false } - field_name := c.smartcast_wrapper_field_name(node.expr.obj.smartcasts.last(), to_type) or { + field_name := c.smartcast_wrapper_field_name(expr.obj.smartcasts.last(), to_type) or { return false } - old_expr := ast.Expr(node.expr) + old_expr := ast.Expr(expr) node.expr = ast.SelectorExpr{ expr: old_expr field_name: field_name @@ -6431,6 +6462,14 @@ fn smartcast_index_expr_scope_key(expr ast.IndexExpr) string { return '__smartcast_index_expr__${ast.Expr(expr).str()}' } +fn smartcast_selector_expr_str(expr ast.SelectorExpr) string { + mut expr_str := expr.expr.str() + if expr.expr is ast.ParExpr && expr.expr.expr is ast.AsCast { + expr_str = expr.expr.expr.expr.str() + } + return expr_str +} + fn assert_autocast_scope_key(scope &ast.Scope, name string) string { return '${scope.start_pos}:${scope.end_pos}:${name}' } @@ -6461,8 +6500,9 @@ fn (mut c Checker) clear_assert_autocast(scope &ast.Scope, name string) { } fn (mut c Checker) apply_assert_autocasts(mut expr ast.Expr, scope &ast.Scope) { - if expr is ast.Ident { - ident := expr as ast.Ident + expr0 := expr + if expr0 is ast.Ident { + ident := expr0 ident_scope := if !isnil(ident.scope) { ident.scope } else { scope } if autocast := c.find_assert_autocast(ident_scope, ident.name) { // Don't insert an as-cast if the variable is already smartcast to the target type @@ -6568,11 +6608,13 @@ fn (mut c Checker) remember_assert_autocasts(mut node ast.Expr, scope &ast.Scope // 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, allow_mut_selector_smartcast bool) { + is_comptime bool, is_option_unwrap bool, allow_mut_selector_smartcast bool, + keep_original_mutability bool) { if cur_type.idx() == 0 || to_type_.idx() == 0 { return } sym := c.table.sym(cur_type) + cur_kind := c.table.final_sym(c.unwrap_generic(cur_type)).kind mut target_type := to_type_ if c.table.cur_fn != unsafe { nil } && c.table.cur_fn.generic_names.len > 0 && c.table.cur_fn.generic_names.len == c.table.cur_concrete_types.len @@ -6618,27 +6660,27 @@ fn (mut c Checker) smartcast(mut expr ast.Expr, cur_type ast.Type, to_type_ ast. orig_type = field.typ } } - mut expr_str := expr.expr.str() - if mut expr.expr is ast.ParExpr && expr.expr.expr is ast.AsCast { - expr_str = expr.expr.expr.expr.str() - } + expr_str := smartcast_selector_expr_str(expr) field := scope.find_struct_field(expr_str, expr.expr_type, expr.field_name) if field != unsafe { nil } { smartcasts << field.smartcasts } // smartcast either if the value is immutable or if mutability is allowed here if !is_mut || expr.is_mut || c.implicit_mutability_enabled() || is_option_unwrap - || orig_type.has_flag(.option) || allow_mut_selector_smartcast { + || orig_type.has_flag(.option) || allow_mut_selector_smartcast + || cur_kind == .sum_type { sc_type := if scope_smartcast_type != ast.no_type { scope_smartcast_type } else { to_type } smartcasts << sc_type + scope_field_is_mut := expr.is_mut || (is_mut && (allow_mut_selector_smartcast + || keep_original_mutability || c.implicit_mutability_enabled())) scope.register_struct_field(expr_str, ast.ScopeStructField{ struct_type: expr.expr_type name: expr.field_name - is_mut: expr.is_mut || is_mut + is_mut: scope_field_is_mut typ: cur_type smartcasts: smartcasts pos: expr.pos @@ -6680,7 +6722,7 @@ fn (mut c Checker) smartcast(mut expr ast.Expr, cur_type ast.Type, to_type_ ast. } // smartcast either if the value is immutable or if mutability is allowed here if (!is_mut || expr.is_mut || c.implicit_mutability_enabled() - || is_option_unwrap) && !is_already_casted { + || is_option_unwrap || cur_kind == .sum_type) && !is_already_casted { sc_type := if scope_smartcast_type != ast.no_type { scope_smartcast_type } else { @@ -6715,12 +6757,14 @@ fn (mut c Checker) smartcast(mut expr ast.Expr, cur_type ast.Type, to_type_ ast. return } } + scope_var_is_mut := expr.is_mut + || (is_mut && (keep_original_mutability || c.implicit_mutability_enabled())) new_var := ast.Var{ name: expr.name typ: cur_type pos: expr.pos is_used: true - is_mut: expr.is_mut || is_mut + is_mut: scope_var_is_mut is_auto_deref: is_auto_deref is_inherited: is_inherited is_auto_heap: is_auto_heap @@ -6964,8 +7008,9 @@ fn (mut c Checker) lock_expr(mut node ast.LockExpr) ast.Type { if mut last_stmt is ast.ExprStmt { c.expected_type = expected_type // Check for shared array slice - auto clone for safety - if mut last_stmt.expr is ast.IndexExpr { - index_expr := last_stmt.expr + stmt_expr := last_stmt.expr + if stmt_expr is ast.IndexExpr { + index_expr := stmt_expr if index_expr.index is ast.RangeExpr && index_expr.left_type.has_flag(.shared_f) && !c.inside_unsafe { // Slicing a shared array creates a view over shared memory. @@ -7156,7 +7201,7 @@ fn (mut c Checker) prefix_expr(mut node ast.PrefixExpr) ast.Type { expr = expr.remove_par() if node.op == .amp { if expr is ast.Nil { - c.error('invalid operation: cannot take address of nil', expr.pos()) + c.error('invalid operation: cannot take address of nil', node.pos) } if mut node.right is ast.PrefixExpr { if node.right.op == .amp { @@ -7421,15 +7466,16 @@ fn (mut c Checker) index_expr(mut node ast.IndexExpr) ast.Type { typ = (typ_sym.info as ast.Aggregate).types[0] } if typ.has_flag(.option) { + left_pos := node.left.pos() if node.left is ast.Ident && node.left.or_expr.kind == .absent { c.error('type `?${typ_sym.name}` is an Option, it must be unwrapped first; use `var?[]` to do it', - node.left.pos()) + left_pos) } else if node.left is ast.CallExpr { c.error('type `?${typ_sym.name}` is an Option, it must be unwrapped with `func()?`, or use `func() or {default}`', - node.left.pos()) + left_pos) } else if node.left is ast.SelectorExpr && node.left.or_block.kind == .absent { c.error('type `?${typ_sym.name}` is an Option, it must be unwrapped first; use `${node.left}?` to do it', - node.left.pos()) + left_pos) } } else if typ.has_flag(.result) { c.error('type `!${typ_sym.name}` is a Result, it does not support indexing', diff --git a/vlib/v/checker/containers.v b/vlib/v/checker/containers.v index 7c370fa3d..ebcafeec2 100644 --- a/vlib/v/checker/containers.v +++ b/vlib/v/checker/containers.v @@ -352,16 +352,20 @@ fn (mut c Checker) array_init(mut node ast.ArrayInit) ast.Type { } for i, mut expr in node.exprs { mut typ := ast.void_type - if expr is ast.ArrayInit { + expr_pos := expr.pos() + is_array_init := expr is ast.ArrayInit + if is_array_init { old_expected_type := c.expected_type c.expected_type = c.table.value_type(c.expected_type) - typ = c.check_expr_option_or_result_call(expr, c.expr(mut expr)) + mut expr_copy := expr + typ = c.check_expr_option_or_result_call(expr_copy, c.expr(mut expr_copy)) + expr = expr_copy c.expected_type = old_expected_type } else { // [none] if c.expected_type == ast.none_type && expr is ast.None { c.error('invalid expression `none`, it is not an array of Option type', - expr.pos()) + expr_pos) continue } typ = c.check_expr_option_or_result_call(expr, c.expr(mut expr)) diff --git a/vlib/v/checker/fn.v b/vlib/v/checker/fn.v index e70aceb1c..af7f92947 100644 --- a/vlib/v/checker/fn.v +++ b/vlib/v/checker/fn.v @@ -2031,8 +2031,11 @@ fn (mut c Checker) fn_call(mut node ast.CallExpr, mut continue_check &bool) ast. // dont check number of args for JS functions since arguments are not required if node.language != .js { for i, mut call_arg in node.args { - if call_arg.expr is ast.CallExpr { - node.args[i].typ = c.expr(mut call_arg.expr) + is_call_expr := call_arg.expr is ast.CallExpr + if is_call_expr { + mut arg_expr := call_arg.expr + node.args[i].typ = c.expr(mut arg_expr) + call_arg.expr = arg_expr } else if mut call_arg.expr is ast.LambdaExpr { if node.concrete_types.len > 0 { call_arg.expr.call_ctx = unsafe { node } @@ -2104,16 +2107,15 @@ fn (mut c Checker) fn_call(mut node ast.CallExpr, mut continue_check &bool) ast. } has_decompose = call_arg.expr is ast.ArrayDecompose if !func.is_variadic && call_arg.expr is ast.ArrayDecompose { - if mut call_arg.expr is ast.ArrayDecompose { - if call_arg.expr.expr is ast.ArrayInit { - extra_params := func.params.len - i - array_init := call_arg.expr.expr as ast.ArrayInit - if array_init.exprs.len < extra_params { - elem_word := if array_init.exprs.len == 1 { 'element' } else { 'elements' } - verb_word := if extra_params == 1 { 'is' } else { 'are' } - c.error('array decompose has ${array_init.exprs.len} ${elem_word} but ${extra_params} ${verb_word} needed for `${func.name}`', - call_arg.pos) - } + array_decompose_expr := call_arg.expr as ast.ArrayDecompose + if array_decompose_expr.expr is ast.ArrayInit { + extra_params := func.params.len - i + array_init := array_decompose_expr.expr as ast.ArrayInit + if array_init.exprs.len < extra_params { + elem_word := if array_init.exprs.len == 1 { 'element' } else { 'elements' } + verb_word := if extra_params == 1 { 'is' } else { 'are' } + c.error('array decompose has ${array_init.exprs.len} ${elem_word} but ${extra_params} ${verb_word} needed for `${func.name}`', + call_arg.pos) } } } @@ -2153,8 +2155,10 @@ fn (mut c Checker) fn_call(mut node ast.CallExpr, mut continue_check &bool) ast. } else { c.expr(mut call_arg.expr) }) - if call_arg.expr is ast.StructInit { - arg_typ = c.expr(mut call_arg.expr) + is_struct_init_arg := call_arg.expr is ast.StructInit + if is_struct_init_arg { + mut arg_expr := call_arg.expr + arg_typ = c.expr(mut arg_expr) } node.args[i].typ = arg_typ if c.comptime.comptime_for_field_var != '' { @@ -2211,12 +2215,13 @@ fn (mut c Checker) fn_call(mut node ast.CallExpr, mut continue_check &bool) ast. node.args[i] = call_arg if call_arg.is_mut { to_lock, pos := c.fail_if_immutable(mut call_arg.expr) + call_arg_expr_pos := call_arg.expr.pos() if !call_arg.expr.is_lvalue() { if call_arg.expr is ast.StructInit { c.error('cannot pass a struct initialization as `mut`, you may want to use a variable `mut var := ${call_arg.expr}`', - call_arg.expr.pos()) + call_arg_expr_pos) } else { - c.error('cannot pass expression as `mut`', call_arg.expr.pos()) + c.error('cannot pass expression as `mut`', call_arg_expr_pos) } } if !param.is_mut { diff --git a/vlib/v/checker/for.v b/vlib/v/checker/for.v index 2103d02af..3523625fd 100644 --- a/vlib/v/checker/for.v +++ b/vlib/v/checker/for.v @@ -46,6 +46,8 @@ fn (mut c Checker) for_c_stmt(mut node ast.ForCStmt) { fn (mut c Checker) for_in_stmt(mut node ast.ForInStmt) { c.in_for_count++ prev_loop_labels := c.loop_labels + cond_pos := node.cond.pos() + high_pos := node.high.pos() mut typ := c.expr(mut node.cond) if node.key_var.len > 0 && node.key_var != '_' { c.check_valid_snake_case(node.key_var, 'variable name', node.pos) @@ -83,18 +85,18 @@ fn (mut c Checker) for_in_stmt(mut node ast.ForInStmt) { node.cond.pos().extend(node.high.pos())) } else if high_type.has_option_or_result() { c.error('the `high` value in a `for x in low..high {` loop, cannot be Result or Option', - node.high.pos()) + high_pos) } else if node.cond is ast.Ident && node.val_var == node.cond.name { if node.is_range { c.error('in a `for x in ` loop, the key or value iteration variable `${node.val_var}` can not be the same as the low variable', - node.cond.pos()) + cond_pos) } else { c.error('in a `for x in array` loop, the key or value iteration variable `${node.val_var}` can not be the same as the low variable', - node.cond.pos()) + cond_pos) } } else if node.high is ast.Ident && node.val_var == node.high.name { c.error('in a `for x in ` loop, the key or value iteration variable `${node.val_var}` can not be the same as the high variable', - node.high.pos()) + high_pos) } if high_type in [ast.int_type, ast.int_literal_type] { @@ -107,7 +109,7 @@ fn (mut c Checker) for_in_stmt(mut node ast.ForInStmt) { } else { if node.cond is ast.Ident && node.cond.name in [node.key_var, node.val_var] { c.error('in a `for x in array` loop, the key or value iteration variable `${node.val_var}` can not be the same as the low variable', - node.cond.pos()) + cond_pos) } mut is_comptime := false if (node.cond is ast.Ident && node.cond.ct_expr) || node.cond is ast.ComptimeSelector { @@ -350,7 +352,7 @@ fn (mut c Checker) for_stmt(mut node ast.ForStmt) { && 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) + node.scope, false, false, false, false) } } } diff --git a/vlib/v/checker/if.v b/vlib/v/checker/if.v index e0e377aec..df4e6fc8e 100644 --- a/vlib/v/checker/if.v +++ b/vlib/v/checker/if.v @@ -637,32 +637,33 @@ fn (mut c Checker) smartcast_if_conds(mut node ast.Expr, mut scope ast.Scope, co } if c.comptime.inside_comptime_for && c.comptime.comptime_for_field_var != '' && node.left is ast.Ident { - if mut node.left is ast.Ident { - if mut node.left.obj is ast.Var { - if node.left.obj.ct_type_var == .field_var { - scope.register(ast.Var{ - name: node.left.name - typ: node.left_type - pos: node.left.pos - is_used: true - is_mut: node.left.is_mut - is_inherited: node.left.obj.is_inherited - is_unwrapped: true - orig_type: node.left_type - ct_type_var: .field_var - ct_type_unwrapped: true - }) - } + mut left_expr := node.left as ast.Ident + if mut left_expr.obj is ast.Var { + if left_expr.obj.ct_type_var == .field_var { + scope.register(ast.Var{ + name: left_expr.name + typ: node.left_type + pos: left_expr.pos + is_used: true + is_mut: left_expr.is_mut + is_inherited: left_expr.obj.is_inherited + is_unwrapped: true + orig_type: node.left_type + ct_type_var: .field_var + ct_type_unwrapped: true + }) } } } else { - if node.left is ast.Ident && c.comptime.get_ct_type_var(node.left) == .smartcast { + is_smartcast_ident := node.left is ast.Ident + && c.comptime.get_ct_type_var(node.left) == .smartcast + if is_smartcast_ident { 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, false) + scope, true, true, false, false) } else { c.smartcast(mut node.left, node.left_type, node.left_type.clear_flag(.option), mut - scope, false, true, false) + scope, false, true, false, false) } } } else if node.op == .key_is { @@ -760,7 +761,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, false) + is_comptime, is_option_unwrap, false, false) } } } @@ -783,7 +784,7 @@ fn (mut c Checker) smartcast_if_conds(mut node ast.Expr, mut scope ast.Scope, co if left_sym.kind in [.interface, .sum_type] || is_option_unwrap { mut left_expr := first_cond.left c.smartcast(mut left_expr, left_type, right_type, mut scope, false, - is_option_unwrap, false) + is_option_unwrap, false, false) } } c.smartcast_none_guard_unwrap(control_expr.branches[0].cond, mut scope) @@ -804,10 +805,10 @@ fn (mut c Checker) smartcast_none_guard_unwrap(cond ast.Expr, mut scope ast.Scop if c.comptime.get_ct_type_var(cond_expr.left) == .smartcast { cond_expr.left_type = c.type_resolver.get_type(cond_expr.left) c.smartcast(mut cond_expr.left, cond_expr.left_type, to_type, mut scope, true, true, - false) + false, false) } else { c.smartcast(mut cond_expr.left, cond_expr.left_type, to_type, mut scope, false, true, - false) + false, false) } } } @@ -831,10 +832,10 @@ fn (mut c Checker) smartcast_none_guard_fallthrough(cond ast.Expr, mut scope ast if c.comptime.get_ct_type_var(cond_expr.left) == .smartcast { cond_expr.left_type = c.type_resolver.get_type(cond_expr.left) c.smartcast(mut cond_expr.left, cond_expr.left_type, to_type, mut scope, true, - true, false) + true, false, false) } else { c.smartcast(mut cond_expr.left, cond_expr.left_type, to_type, mut scope, false, - true, false) + true, false, false) } } } diff --git a/vlib/v/checker/infix.v b/vlib/v/checker/infix.v index c9e8804be..09ddd2e15 100644 --- a/vlib/v/checker/infix.v +++ b/vlib/v/checker/infix.v @@ -154,7 +154,12 @@ fn (mut c Checker) infix_expr(mut node ast.InfixExpr) ast.Type { c.autocast_in_if_conds(mut node.right, left_node.left, from_type, to_type) break } else if left_node.op == .and { - left_node = left_node.left + next_left := left_node.left + if next_left is ast.InfixExpr { + left_node = next_left + } else { + break + } } else { break } @@ -1193,8 +1198,9 @@ fn (mut c Checker) maybe_wrap_index_expr_smartcast(mut expr ast.Expr, expr_type if isnil(c.fn_scope) || expr_type in [ast.no_type, ast.void_type] { return expr_type } - if expr is ast.IndexExpr { - index_expr := expr as ast.IndexExpr + expr0 := expr + if expr0 is ast.IndexExpr { + index_expr := expr0 scope := c.fn_scope.innermost(index_expr.pos.pos) expr_key := smartcast_index_expr_scope_key(index_expr) if var := scope.find_var(expr_key) { @@ -1202,12 +1208,12 @@ fn (mut c Checker) maybe_wrap_index_expr_smartcast(mut expr ast.Expr, expr_type 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(ast.AsCast{ expr: ast.Expr(index_expr) typ: cast_type expr_type: expr_type pos: index_expr.pos - } + }) } return cast_type } diff --git a/vlib/v/checker/match.v b/vlib/v/checker/match.v index 30dfac45f..23ee8e325 100644 --- a/vlib/v/checker/match.v +++ b/vlib/v/checker/match.v @@ -24,12 +24,18 @@ fn (mut c Checker) match_expr(mut node ast.MatchExpr) ast.Type { } mut cond_type := ast.void_type if node.is_comptime { - if node.cond is ast.AtExpr { - cond_type = c.expr(mut node.cond) + cond_expr := node.cond + if cond_expr is ast.AtExpr { + mut checked_cond := node.cond + cond_type = c.expr(mut checked_cond) + node.cond = checked_cond } else { // for field.name and generic type `T` - if node.cond is ast.SelectorExpr { - c.expr(mut node.cond) + is_selector_cond := node.cond is ast.SelectorExpr + if is_selector_cond { + mut selector_cond := node.cond + c.expr(mut selector_cond) + node.cond = selector_cond } cond_type = c.get_expr_type(node.cond) } @@ -817,16 +823,17 @@ fn (mut c Checker) match_exprs(mut node ast.MatchExpr, cond_type_sym ast.TypeSym } } if node.is_comptime { + expr_pos := expr.pos() if is_type_node { if is_comptime_value_match { // type branch in a value match - c.error('can not matching a type in a value `\$match`', expr.pos()) + c.error('can not matching a type in a value `\$match`', expr_pos) return } } else if c.inside_x_matches_type { // value branch in a type match if expr in [ast.IntegerLiteral, ast.BoolLiteral, ast.StringLiteral] { - c.error('can not matching a value in a type `\$match`', expr.pos()) + c.error('can not matching a value in a type `\$match`', expr_pos) return } } @@ -836,28 +843,28 @@ fn (mut c Checker) match_exprs(mut node ast.MatchExpr, cond_type_sym ast.TypeSym if mut node.cond is ast.ComptimeType { if node.cond.kind != .int { c.error('can not matching a int value(`${expr}`) in a non int type `\$match`, `${node.cond}` type is `${node.cond.kind}`', - expr.pos()) + expr_pos) return } } else if node.cond_type !in ast.integer_type_idxs { c.error('can not matching a int value(`${expr}`) in a non int type `\$match`, `${node.cond}` type is `${c.table.type_to_str(node.cond_type)}`', - expr.pos()) + expr_pos) return } } else if expr is ast.BoolLiteral && node.cond_type != ast.bool_type { c.error('can not matching a bool value(`${expr}`) in a non bool type `\$match`, `${node.cond}` type is `${c.table.type_to_str(node.cond_type)}`', - expr.pos()) + expr_pos) return } else if expr is ast.StringLiteral { if mut node.cond is ast.ComptimeType { if node.cond.kind != .string { c.error('can not matching a string value(`${expr}`) in a non string type `\$match`, `${node.cond}` type is `${node.cond.kind}`', - expr.pos()) + expr_pos) return } } else if node.cond_type != ast.string_type { c.error('can not matching a string value(`${expr}`) in a non string type `\$match`, `${node.cond}` type is `${c.table.type_to_str(node.cond_type)}`', - expr.pos()) + expr_pos) return } } @@ -951,7 +958,7 @@ fn (mut c Checker) match_exprs(mut node ast.MatchExpr, cond_type_sym ast.TypeSym allow_mut_selector_smartcast := node.cond is ast.SelectorExpr c.smartcast(mut node.cond, node.cond_type, expr_type, mut branch.scope, false, - false, allow_mut_selector_smartcast) + false, allow_mut_selector_smartcast, true) } } } diff --git a/vlib/v/checker/return.v b/vlib/v/checker/return.v index b5957efef..f808e22ce 100644 --- a/vlib/v/checker/return.v +++ b/vlib/v/checker/return.v @@ -120,7 +120,7 @@ fn (mut c Checker) return_stmt(mut node ast.Return) { if expr.obj.ct_type_var != .no_comptime { typ = c.type_resolver.get_type_or_default(expr, typ) } - if mut expr.obj.expr is ast.IfGuardExpr { + if expr.obj.expr is ast.IfGuardExpr { if var := expr.scope.find_var(expr.name) { typ = var.typ } diff --git a/vlib/v/checker/struct.v b/vlib/v/checker/struct.v index a3a9fe768..c61386ade 100644 --- a/vlib/v/checker/struct.v +++ b/vlib/v/checker/struct.v @@ -701,17 +701,19 @@ fn (mut c Checker) struct_init(mut node ast.StructInit, is_field_zero_struct_ini } if c.anon_struct_should_be_mut { mut anon_type_sym := c.table.sym(node.typ) - if anon_type_sym.info is ast.Struct && anon_type_sym.info.is_anon { + if anon_type_sym.kind == .struct { mut anon_info := anon_type_sym.info as ast.Struct - mut anon_fields := []ast.StructField{cap: anon_info.fields.len} - for field in anon_info.fields { - anon_fields << ast.StructField{ - ...field - is_mut: true + if anon_info.is_anon { + mut anon_fields := []ast.StructField{cap: anon_info.fields.len} + for field in anon_info.fields { + anon_fields << ast.StructField{ + ...field + is_mut: true + } } + anon_info.fields = anon_fields + anon_type_sym.info = anon_info } - anon_info.fields = anon_fields - anon_type_sym.info = anon_info } } // Make sure the first letter is capital, do not allow e.g. `x := string{}`, @@ -955,6 +957,7 @@ fn (mut c Checker) struct_init(mut node ast.StructInit, is_field_zero_struct_ini init_field.expr.pos()) } if exp_type_sym.kind == .array && got_type_sym.kind == .array { + init_field_expr_pos := init_field.expr.pos() if init_field.expr is ast.IndexExpr && init_field.expr.left is ast.Ident && ((init_field.expr as ast.IndexExpr).left.is_mut() || field_info.is_mut) && init_field.expr.index is ast.RangeExpr @@ -962,9 +965,9 @@ fn (mut c Checker) struct_init(mut node ast.StructInit, is_field_zero_struct_ini // `a: arr[..]` auto add clone() -> `a: arr[..].clone()` c.add_error_detail_with_pos('To silence this notice, use either an explicit `a[..].clone()`, or use an explicit `unsafe{ a[..] }`, if you do not want a copy of the slice.', - init_field.expr.pos()) + init_field_expr_pos) c.note('an implicit clone of the slice was done here', - init_field.expr.pos()) + init_field_expr_pos) mut right := ast.CallExpr{ name: 'clone' kind: .clone @@ -1290,10 +1293,14 @@ fn (mut c Checker) check_uninitialized_struct_fields_and_embeds(node ast.StructI if field.typ.is_any_kind_of_pointer() { info.fields[i].default_expr_typ = field.typ } - } else if field.default_expr is ast.Ident && field.default_expr.info is ast.IdentFn { - c.expr(mut field.default_expr) } else { - if const_field := c.table.global_scope.find_const('${field.default_expr}') { + is_ident_fn_default := field.default_expr is ast.Ident + && field.default_expr.info is ast.IdentFn + if is_ident_fn_default { + mut default_expr := field.default_expr + c.expr(mut default_expr) + field.default_expr = default_expr + } else if const_field := c.table.global_scope.find_const('${field.default_expr}') { info.fields[i].default_expr_typ = const_field.typ } else if type_sym.info is ast.Struct && type_sym.info.is_anon { c.expected_type = field.typ diff --git a/vlib/v/checker/tests/if_smartcast_mutation_err.out b/vlib/v/checker/tests/if_smartcast_mutation_err.out new file mode 100644 index 000000000..468d4ed92 --- /dev/null +++ b/vlib/v/checker/tests/if_smartcast_mutation_err.out @@ -0,0 +1,14 @@ +vlib/v/checker/tests/if_smartcast_mutation_err.vv:5:3: error: cannot mutate `a` in a non-mut smartcast, use `if mut a ...` + 3 | mut a := SmartcastValue(1) + 4 | if a is int { + 5 | a = 3 + | ^ + 6 | } + 7 | +vlib/v/checker/tests/if_smartcast_mutation_err.vv:10:7: error: cannot assign to `b`: expected `int`, not `string` + 8 | mut b := SmartcastValue(1) + 9 | if mut b is int { + 10 | b = 'hello' + | ~~~~~~~ + 11 | } + 12 | } diff --git a/vlib/v/checker/tests/if_smartcast_mutation_err.vv b/vlib/v/checker/tests/if_smartcast_mutation_err.vv new file mode 100644 index 000000000..809ee7308 --- /dev/null +++ b/vlib/v/checker/tests/if_smartcast_mutation_err.vv @@ -0,0 +1,12 @@ +type SmartcastValue = int | string +fn main() { + mut a := SmartcastValue(1) + if a is int { + a = 3 + } + + mut b := SmartcastValue(1) + if mut b is int { + b = 'hello' + } +} diff --git a/vlib/v/checker/tests/incorrect_smartcast3_err.out b/vlib/v/checker/tests/incorrect_smartcast3_err.out index 62e61a636..e69de29bb 100644 --- a/vlib/v/checker/tests/incorrect_smartcast3_err.out +++ b/vlib/v/checker/tests/incorrect_smartcast3_err.out @@ -1,35 +0,0 @@ -vlib/v/checker/tests/incorrect_smartcast3_err.vv:25:5: notice: smartcasting requires either an immutable value, or an explicit mut keyword before the value - 23 | - 24 | mut r := la.regex - 25 | if r is RE { - | ^ - 26 | println(r) - 27 | println(r.matches_string(item)) -vlib/v/checker/tests/incorrect_smartcast3_err.vv:30:8: notice: smartcasting requires either an immutable value, or an explicit mut keyword before the value - 28 | } - 29 | - 30 | match r { - | ^ - 31 | RE { r.matches_string(item) } - 32 | else {} -vlib/v/checker/tests/incorrect_smartcast3_err.vv:27:13: error: unknown method or field: `OurRegex.matches_string` - 25 | if r is RE { - 26 | println(r) - 27 | println(r.matches_string(item)) - | ~~~~~~~~~~~~~~~~~~~~ - 28 | } - 29 | -vlib/v/checker/tests/incorrect_smartcast3_err.vv:27:3: error: `println` can not print void expressions - 25 | if r is RE { - 26 | println(r) - 27 | println(r.matches_string(item)) - | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - 28 | } - 29 | -vlib/v/checker/tests/incorrect_smartcast3_err.vv:31:10: error: unknown method or field: `OurRegex.matches_string` - 29 | - 30 | match r { - 31 | RE { r.matches_string(item) } - | ~~~~~~~~~~~~~~~~~~~~ - 32 | else {} - 33 | } diff --git a/vlib/v/checker/tests/infix_sumtype_in_array_err.out b/vlib/v/checker/tests/infix_sumtype_in_array_err.out index b4440b1c8..e69de29bb 100644 --- a/vlib/v/checker/tests/infix_sumtype_in_array_err.out +++ b/vlib/v/checker/tests/infix_sumtype_in_array_err.out @@ -1,7 +0,0 @@ -vlib/v/checker/tests/infix_sumtype_in_array_err.vv:15:7: error: left operand to `in` does not match the array element type: expected `rune`, not `RuneAliasOrOtherType` - 13 | match x { - 14 | RuneAlias { - 15 | if x in whitespace { - | ~~~~~~~~~~~~~~~ - 16 | // doing `if x as RuneAlias in whitespace` here - 17 | // works but it should be doing that automatically diff --git a/vlib/v/checker/tests/match_branch_call_expr_arg_mismatch.out b/vlib/v/checker/tests/match_branch_call_expr_arg_mismatch.out index 9ad0c7de5..e69de29bb 100644 --- a/vlib/v/checker/tests/match_branch_call_expr_arg_mismatch.out +++ b/vlib/v/checker/tests/match_branch_call_expr_arg_mismatch.out @@ -1,28 +0,0 @@ -vlib/v/checker/tests/match_branch_call_expr_arg_mismatch.vv:16:19: error: cannot use `&Foobar` as `&Foo` in argument 1 to `mutate_foo` - 14 | match foobar { - 15 | Foo { - 16 | mutate_foo(mut foobar) - | ~~~~~~ - 17 | } - 18 | Bar { -vlib/v/checker/tests/match_branch_call_expr_arg_mismatch.vv:19:19: error: cannot use `&Foobar` as `&Bar` in argument 1 to `mutate_bar` - 17 | } - 18 | Bar { - 19 | mutate_bar(mut foobar) - | ~~~~~~ - 20 | } - 21 | } -vlib/v/checker/tests/match_branch_call_expr_arg_mismatch.vv:27:19: error: cannot use `&Foobar` as `&Foo` in argument 1 to `mutate_foo` - 25 | return match foobar { - 26 | Foo { - 27 | mutate_foo(mut foobar) - | ~~~~~~ - 28 | } - 29 | Bar { -vlib/v/checker/tests/match_branch_call_expr_arg_mismatch.vv:30:19: error: cannot use `&Foobar` as `&Bar` in argument 1 to `mutate_bar` - 28 | } - 29 | Bar { - 30 | mutate_bar(mut foobar) - | ~~~~~~ - 31 | } - 32 | } diff --git a/vlib/v/checker/tests/sum_type_mutable_cast_err.out b/vlib/v/checker/tests/sum_type_mutable_cast_err.out index 89423de77..e69de29bb 100644 --- a/vlib/v/checker/tests/sum_type_mutable_cast_err.out +++ b/vlib/v/checker/tests/sum_type_mutable_cast_err.out @@ -1,28 +0,0 @@ -vlib/v/checker/tests/sum_type_mutable_cast_err.vv:15:10: error: cannot use operator `+` with `Abc` - 13 | mut x := Abc(0) - 14 | if x is int { - 15 | _ := x + 5 - | ^ - 16 | } - 17 | mut f := Foo{Bar{Abc(0)}} -vlib/v/checker/tests/sum_type_mutable_cast_err.vv:15:8: error: infix expr: cannot use `int literal` (right expression) as `Abc` - 13 | mut x := Abc(0) - 14 | if x is int { - 15 | _ := x + 5 - | ~~~~~ - 16 | } - 17 | mut f := Foo{Bar{Abc(0)}} -vlib/v/checker/tests/sum_type_mutable_cast_err.vv:19:14: error: cannot use operator `+` with `Abc` - 17 | mut f := Foo{Bar{Abc(0)}} - 18 | if f.b.a is int { - 19 | _ := f.b.a + 5 - | ^ - 20 | } - 21 | } -vlib/v/checker/tests/sum_type_mutable_cast_err.vv:19:12: error: infix expr: cannot use `int literal` (right expression) as `Abc` - 17 | mut f := Foo{Bar{Abc(0)}} - 18 | if f.b.a is int { - 19 | _ := f.b.a + 5 - | ~~~~~ - 20 | } - 21 | } diff --git a/vlib/v/gen/c/assert.v b/vlib/v/gen/c/assert.v index 4737b5580..3330c1222 100644 --- a/vlib/v/gen/c/assert.v +++ b/vlib/v/gen/c/assert.v @@ -97,10 +97,12 @@ fn (mut g Gen) assert_stmt(original_assert_statement ast.AssertStmt) { } if mut node.expr is ast.InfixExpr { - if node.expr.left is ast.CTempVar { + restore_left := node.expr.left is ast.CTempVar + restore_right := node.expr.right is ast.CTempVar + if restore_left { node.expr.left = save_left } - if node.expr.right is ast.CTempVar { + if restore_right { node.expr.right = save_right } } diff --git a/vlib/v/gen/c/assign.v b/vlib/v/gen/c/assign.v index 6724477f2..2a2f739b2 100644 --- a/vlib/v/gen/c/assign.v +++ b/vlib/v/gen/c/assign.v @@ -7,6 +7,33 @@ import v.ast import v.util import v.token +fn smartcast_selector_expr_str(expr ast.SelectorExpr) string { + mut expr_str := expr.expr.str() + if expr.expr is ast.ParExpr && expr.expr.expr is ast.AsCast { + expr_str = expr.expr.expr.expr.str() + } + return expr_str +} + +fn (g &Gen) is_smartcast_assign_lhs(expr ast.Expr) bool { + match expr { + ast.Ident { + return expr.obj is ast.Var && expr.obj.smartcasts.len > 0 + } + ast.SelectorExpr { + if expr.expr_type == 0 { + return false + } + scope_field := expr.scope.find_struct_field(smartcast_selector_expr_str(expr), + expr.expr_type, expr.field_name) + return scope_field != unsafe { nil } && scope_field.smartcasts.len > 0 + } + else { + return false + } + } +} + fn (mut g Gen) expr_in_value_context(expr ast.Expr, value_type ast.Type, expected_type ast.Type) { mut expr_copy := expr match mut expr_copy { @@ -909,7 +936,7 @@ fn (mut g Gen) assign_stmt(node_ ast.AssignStmt) { } var_type = resolved_call_type.clear_option_and_result() val_type = var_type - if mut left is ast.Ident && left.obj is ast.Var { + if mut left is ast.Ident && mut left.obj is ast.Var { left.obj.typ = var_type } } @@ -925,7 +952,7 @@ fn (mut g Gen) assign_stmt(node_ ast.AssignStmt) { if resolved_val_type != 0 && resolved_val_type != ast.void_type { var_type = resolved_val_type val_type = resolved_val_type - if mut left is ast.Ident && left.obj is ast.Var { + if mut left is ast.Ident && mut left.obj is ast.Var { left.obj.typ = var_type } } @@ -938,7 +965,7 @@ fn (mut g Gen) assign_stmt(node_ ast.AssignStmt) { if resolved_or_type != 0 && resolved_or_type != ast.void_type { var_type = g.unwrap_generic(g.recheck_concrete_type(resolved_or_type)) val_type = var_type - if mut left is ast.Ident && left.obj is ast.Var { + if mut left is ast.Ident && mut left.obj is ast.Var { left.obj.typ = var_type } } @@ -947,7 +974,7 @@ fn (mut g Gen) assign_stmt(node_ ast.AssignStmt) { if is_decl && assign_expr_unwraps_option_or_result(val) { var_type = var_type.clear_option_and_result() val_type = val_type.clear_option_and_result() - if mut left is ast.Ident && left.obj is ast.Var { + if mut left is ast.Ident && mut left.obj is ast.Var { left.obj.typ = var_type } } @@ -956,12 +983,12 @@ fn (mut g Gen) assign_stmt(node_ ast.AssignStmt) { || (val is ast.PostfixExpr && val.op == .question) { var_type = var_type.clear_option_and_result() val_type = val_type.clear_option_and_result() - if mut left is ast.Ident && left.obj is ast.Var { + if mut left is ast.Ident && mut left.obj is ast.Var { left.obj.typ = var_type } } } - if is_decl && mut left is ast.Ident && left.obj is ast.Var { + if is_decl && mut left is ast.Ident && mut left.obj is ast.Var { mut should_recompute_decl_type := false match val { ast.Ident { @@ -1106,7 +1133,7 @@ fn (mut g Gen) assign_stmt(node_ ast.AssignStmt) { if is_decl && var_type.has_flag(.shared_f) && var_type.nr_muls() == 0 { var_type = var_type.set_nr_muls(1) } - if is_decl && mut left is ast.Ident && left.obj is ast.Var { + if is_decl && mut left is ast.Ident && mut left.obj is ast.Var { left.obj.typ = var_type if mut scope_var := left.scope.find_var(left.name) { scope_var.typ = var_type @@ -1628,7 +1655,7 @@ fn (mut g Gen) assign_stmt(node_ ast.AssignStmt) { } } } - if left in [ast.Ident, ast.SelectorExpr] { + if left in [ast.Ident, ast.SelectorExpr] && !g.is_smartcast_assign_lhs(left) { g.prevent_sum_type_unwrapping_once = true } if !is_fixed_array_var || is_decl || is_shared_re_assign { @@ -1663,8 +1690,9 @@ fn (mut g Gen) assign_stmt(node_ ast.AssignStmt) { continue } g.is_assign_lhs = false - if left is ast.IndexExpr && g.cur_indexexpr.len > 0 { - cur_indexexpr = g.cur_indexexpr.index(left.pos().pos) + left_expr := left + if left_expr is ast.IndexExpr && g.cur_indexexpr.len > 0 { + cur_indexexpr = g.cur_indexexpr.len - 1 } if is_fixed_array_var || is_va_list { if is_decl { diff --git a/vlib/v/gen/c/cgen.v b/vlib/v/gen/c/cgen.v index 7779eeff1..b4d038fd7 100644 --- a/vlib/v/gen/c/cgen.v +++ b/vlib/v/gen/c/cgen.v @@ -5376,7 +5376,7 @@ fn (mut g Gen) expr(node_ ast.Expr) { g.writeln('${typ} ${tmp_var};') g.stmts_with_tmp_var([ ast.ExprStmt{ - pos: node.right.pos() + pos: node.pos expr: node.right }, ], tmp_var) @@ -7639,7 +7639,11 @@ fn (mut g Gen) ident(node ast.Ident) { needs_interface_smartcast_deref = true } } - obj_sym := g.table.final_sym(g.unwrap_generic(resolved_var.typ)) + obj_sym := if resolved_var.orig_type != 0 { + g.table.final_sym(g.unwrap_generic(resolved_var.orig_type)) + } else { + g.table.final_sym(g.unwrap_generic(resolved_var.typ)) + } interface_scalar_smartcast_needs_deref := interface_source_is_interface && smartcast_types.len > 0 && smartcast_types.last().is_ptr() && !g.inside_selector_lhs diff --git a/vlib/v/parser/asm.v b/vlib/v/parser/asm.v index 296e9d56c..7518f2936 100644 --- a/vlib/v/parser/asm.v +++ b/vlib/v/parser/asm.v @@ -639,12 +639,14 @@ fn (mut p Parser) asm_ios(output bool) []ast.AsmIO { } } mut expr := p.expr(0) + mut next_expr := ast.Expr(ast.EmptyExpr{}) if mut expr is ast.ParExpr { - expr = expr.expr + next_expr = expr.expr } else { p.error('asm in/output must be enclosed in brackets') return [] } + expr = next_expr mut alias := '' if p.tok.kind == .key_as { p.next() diff --git a/vlib/v/parser/expr.v b/vlib/v/parser/expr.v index b2c200a35..3dbd07cbf 100644 --- a/vlib/v/parser/expr.v +++ b/vlib/v/parser/expr.v @@ -1016,13 +1016,17 @@ fn (mut p Parser) prefix_expr() ast.Expr { return right } } + mut unwrapped_right := ast.Expr(ast.EmptyExpr{}) if mut right is ast.ParExpr { if right.expr is ast.StructInit { p.note_with_pos('unnecessary `()`, use `&${right.expr}` instead of `&(${right.expr})`', right.pos) - right = right.expr + unwrapped_right = right.expr } } + if unwrapped_right !is ast.EmptyExpr { + right = unwrapped_right + } if mut right is ast.TypeNode { right.typ = right.typ.ref() return right diff --git a/vlib/v/parser/parser.v b/vlib/v/parser/parser.v index 4582e60b9..f9dcb502d 100644 --- a/vlib/v/parser/parser.v +++ b/vlib/v/parser/parser.v @@ -601,22 +601,25 @@ fn (mut p Parser) mark_last_call_return_as_used(mut last_stmt ast.Stmt) { // last stmt has infix expr with CallExpr: foo()? + 'a' mut left_expr := last_stmt.expr.left for { + mut next_left_expr := ast.Expr(ast.EmptyExpr{}) if mut left_expr is ast.InfixExpr { if left_expr.or_block.stmts.len > 0 { mut or_block_last_stmt := left_expr.or_block.stmts.last() p.mark_last_call_return_as_used(mut or_block_last_stmt) } - left_expr = left_expr.left - continue - } - if mut left_expr is ast.CallExpr { + next_left_expr = left_expr.left + } else if mut left_expr is ast.CallExpr { left_expr.is_return_used = true if left_expr.or_block.stmts.len > 0 { mut or_block_last_stmt := left_expr.or_block.stmts.last() p.mark_last_call_return_as_used(mut or_block_last_stmt) } + break + } else { + break } - break + left_expr = next_left_expr + continue } } ast.ComptimeCall, ast.ComptimeSelector, ast.PrefixExpr, ast.SelectorExpr { diff --git a/vlib/v/tests/conditions/ifs/if_smartcast_test.v b/vlib/v/tests/conditions/ifs/if_smartcast_test.v index cfc6d1ff7..62c4cdbd3 100644 --- a/vlib/v/tests/conditions/ifs/if_smartcast_test.v +++ b/vlib/v/tests/conditions/ifs/if_smartcast_test.v @@ -28,6 +28,21 @@ fn test_mutable() { } } +fn test_mut_smartcast_reassign_same_variant() { + mut x := Xya(1) + if mut x is int { + x = 3 + assert x == 3 + } else { + assert false + } + if x is int { + assert x == 3 + } else { + assert false + } +} + fn test_nested_if_smartcast() { x := Alphabet(Abc{'test'}) y := Alphabet(Xyz{'foo'}) @@ -192,8 +207,9 @@ fn gen(_ Expr) CTempVarExpr { fn test_reassign_from_function_with_parameter_selector() { mut f := ExprWrapper{Expr(CallExpr{})} - if f.expr is CallExpr { - f.expr = gen(f.expr) + expr := f.expr + if expr is CallExpr { + f.expr = gen(expr) } } diff --git a/vlib/v/transformer/transformer.v b/vlib/v/transformer/transformer.v index fc7f883df..6c8cc985c 100644 --- a/vlib/v/transformer/transformer.v +++ b/vlib/v/transformer/transformer.v @@ -648,8 +648,10 @@ pub fn (mut t Transformer) expr(mut node ast.Expr) ast.Expr { if node.stmts.len > 0 { // todo fix [] => new_array_from_c_array() now mut stmt := node.stmts.last() - if stmt is ast.ExprStmt && stmt.expr is ast.CallExpr { - ((stmt as ast.ExprStmt).expr as ast.CallExpr).is_return_used = true + if mut stmt is ast.ExprStmt { + if mut stmt.expr is ast.CallExpr { + stmt.expr.is_return_used = true + } } } } -- 2.39.5