From 1a3953c5132380a7c72680934ea664546f31b6c4 Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Wed, 25 Mar 2026 16:42:25 +0300 Subject: [PATCH] checker: fix int <-> u8 promotions (fixes #25936) --- vlib/v/checker/infix.v | 86 +++++++++++++++++++++--------- vlib/v/tests/type_promotion_test.v | 18 +++++++ 2 files changed, 80 insertions(+), 24 deletions(-) diff --git a/vlib/v/checker/infix.v b/vlib/v/checker/infix.v index d4f4ee2b8..172600659 100644 --- a/vlib/v/checker/infix.v +++ b/vlib/v/checker/infix.v @@ -21,6 +21,54 @@ fn has_matching_reference_operator_overload(sym &ast.TypeSymbol, op string, rece && method.params[1].typ == operand_type } +fn (mut c Checker) int_literal_needs_promotion(expr ast.Expr, promoted_type ast.Type) bool { + base_type := promoted_type.clear_flags() + if base_type !in ast.int_promoted_type_idxs { + return false + } + value := c.eval_comptime_const_expr(expr, 0) or { return false } + size, _ := c.table.type_size(base_type.idx_type()) + bit_size := size * 8 + if bit_size == 0 { + return false + } + if base_type.is_signed() { + signed_value := value.i64() or { return true } + max_signed := if bit_size >= 64 { + max_i64 + } else { + i64((u64(1) << (bit_size - 1)) - 1) + } + min_signed := if bit_size >= 64 { + min_i64 + } else { + -max_signed - 1 + } + return signed_value < min_signed || signed_value > max_signed + } + unsigned_value := value.u64() or { return true } + max_unsigned := if bit_size >= 64 { + max_u64 + } else { + (u64(1) << bit_size) - 1 + } + return unsigned_value > max_unsigned +} + +fn (mut c Checker) adjust_infix_int_literal_promotion(left ast.Expr, right ast.Expr, left_type ast.Type, + right_type ast.Type, promoted_type ast.Type) ast.Type { + if promoted_type.clear_flags() !in ast.int_promoted_type_idxs { + return promoted_type + } + if left_type == ast.int_literal_type && c.int_literal_needs_promotion(left, promoted_type) { + return ast.int_type + } + if right_type == ast.int_literal_type && c.int_literal_needs_promotion(right, promoted_type) { + return ast.int_type + } + return promoted_type +} + fn (mut c Checker) infix_expr(mut node ast.InfixExpr) ast.Type { former_expected_type := c.expected_type defer { @@ -194,13 +242,17 @@ fn (mut c Checker) infix_expr(mut node ast.InfixExpr) ast.Type { if left_type.is_any_kind_of_pointer() && !left_type.has_flag(.shared_f) && !node.left.is_auto_deref_var() && node.op in [.plus, .minus, .mul, .div, .mod, .xor, .amp, .pipe] { - if has_matching_reference_operator_overload(left_sym, node.op.str(), left_type, - right_type) - { - // allow explicit operator overloads like `fn (x &Type) OP (y &Type) ReturnType {` - } else if !c.pref.translated && ((right_type.is_any_kind_of_pointer() && node.op != .minus) + if !c.pref.translated && ((right_type.is_any_kind_of_pointer() && node.op != .minus) || (!right_type.is_any_kind_of_pointer() && node.op !in [.plus, .minus])) { - c.invalid_operator_error(node.op, left_type, right_type, left_right_pos) + if _ := left_sym.find_method(node.op.str()) { + if left_sym.kind == .alias && right_sym.kind == .alias { + // allow an explicit operator override `fn (x &AliasType) OP (y &AliasType) &AliasType {` + } else { + c.invalid_operator_error(node.op, left_type, right_type, left_right_pos) + } + } else { + c.invalid_operator_error(node.op, left_type, right_type, left_right_pos) + } } else if node.op in [.plus, .minus] { if !c.inside_unsafe && !node.left.is_auto_deref_var() && !node.right.is_auto_deref_var() { if !c.pref.translated && !c.file.is_translated { @@ -408,14 +460,12 @@ fn (mut c Checker) infix_expr(mut node ast.InfixExpr) ast.Type { && !left_final_sym.is_primitive() { if left_sym.has_method(op_str) { if method := left_sym.find_method(op_str) { - c.mark_fn_decl_as_referenced(method.fkey()) return_type = method.return_type } else { return_type = left_type } } else if left_final_sym.has_method_with_generic_parent(op_str) { if method := left_final_sym.find_method_with_generic_parent(op_str) { - c.mark_fn_decl_as_referenced(method.fkey()) return_type = method.return_type } else { return_type = left_type @@ -435,28 +485,24 @@ fn (mut c Checker) infix_expr(mut node ast.InfixExpr) ast.Type { && !right_final_sym.is_primitive() { if right_sym.has_method(op_str) { if method := right_sym.find_method(op_str) { - c.mark_fn_decl_as_referenced(method.fkey()) return_type = method.return_type } else { return_type = right_type } } else if right_final_sym.has_method_with_generic_parent(op_str) { if method := right_final_sym.find_method_with_generic_parent(op_str) { - c.mark_fn_decl_as_referenced(method.fkey()) return_type = method.return_type } else { return_type = right_type } } else if left_sym.has_method(op_str) { if method := left_sym.find_method(op_str) { - c.mark_fn_decl_as_referenced(method.fkey()) return_type = method.return_type } else { return_type = left_type } } else if left_final_sym.has_method_with_generic_parent(op_str) { if method := left_final_sym.find_method_with_generic_parent(op_str) { - c.mark_fn_decl_as_referenced(method.fkey()) return_type = method.return_type } else { return_type = left_type @@ -483,7 +529,6 @@ fn (mut c Checker) infix_expr(mut node ast.InfixExpr) ast.Type { if !c.pref.translated && left_sym.kind in [.array, .array_fixed, .map, .struct] { if left_sym.has_method_with_generic_parent(op_str) { if method := left_sym.find_method_with_generic_parent(op_str) { - c.mark_fn_decl_as_referenced(method.fkey()) return_type = method.return_type } else { return_type = left_type @@ -502,7 +547,6 @@ fn (mut c Checker) infix_expr(mut node ast.InfixExpr) ast.Type { } else if !c.pref.translated && right_sym.kind in [.array, .array_fixed, .map, .struct] { if right_sym.has_method_with_generic_parent(op_str) { if method := right_sym.find_method_with_generic_parent(op_str) { - c.mark_fn_decl_as_referenced(method.fkey()) return_type = method.return_type } else { return_type = right_type @@ -540,6 +584,8 @@ fn (mut c Checker) infix_expr(mut node ast.InfixExpr) ast.Type { unalias_right_type := c.table.unalias_num_type(unwrapped_right_type) mut promoted_type := c.promote_keeping_aliases(unaliased_left_type, unalias_right_type, left_sym.kind, right_sym.kind) + promoted_type = c.adjust_infix_int_literal_promotion(node.left, node.right, + unaliased_left_type, unalias_right_type, promoted_type) // subtract pointers is allowed in unsafe block is_allowed_pointer_arithmetic := left_type.is_any_kind_of_pointer() && right_type.is_any_kind_of_pointer() && node.op == .minus @@ -584,14 +630,12 @@ fn (mut c Checker) infix_expr(mut node ast.InfixExpr) ast.Type { if left_sym.info is ast.Alias && left_final_sym.is_primitive() { if left_sym.has_method(op_str) { if method := left_sym.find_method(op_str) { - c.mark_fn_decl_as_referenced(method.fkey()) return_type = method.return_type } } } else if right_sym.info is ast.Alias && right_final_sym.is_primitive() { if right_sym.has_method(op_str) { if method := right_sym.find_method(op_str) { - c.mark_fn_decl_as_referenced(method.fkey()) return_type = method.return_type } } @@ -603,16 +647,14 @@ fn (mut c Checker) infix_expr(mut node ast.InfixExpr) ast.Type { } } .gt, .lt, .ge, .le { - cmp_method_name := '<' unwrapped_left_type := c.unwrap_generic(left_type) left_sym = c.table.sym(unwrapped_left_type) - if left_sym.kind == .alias && !left_sym.has_method_with_generic_parent(cmp_method_name) { + if left_sym.kind == .alias && !left_sym.has_method_with_generic_parent(node.op.str()) { left_sym = c.table.final_sym(unwrapped_left_type) } unwrapped_right_type := c.unwrap_generic(right_type) right_sym = c.table.sym(unwrapped_right_type) - if right_sym.kind == .alias - && !right_sym.has_method_with_generic_parent(cmp_method_name) { + if right_sym.kind == .alias && !right_sym.has_method_with_generic_parent(node.op.str()) { right_sym = c.table.final_sym(unwrapped_right_type) } if left_sym.kind in [.array, .array_fixed] && right_sym.kind in [.array, .array_fixed] { @@ -643,8 +685,6 @@ fn (mut c Checker) infix_expr(mut node ast.InfixExpr) ast.Type { left_right_pos) } else if !left_sym.has_method('<') && node.op == .gt { c.error('cannot use `>` as `<=` operator method is not defined', left_right_pos) - } else if method := left_sym.find_method_with_generic_parent('<') { - c.mark_fn_decl_as_referenced(method.fkey()) } } else if left_type.has_flag(.generic) && right_type.has_flag(.generic) { // Try to unwrap the generic type to make sure that @@ -659,8 +699,6 @@ fn (mut c Checker) infix_expr(mut node ast.InfixExpr) ast.Type { } else if need_overload && !gen_sym.has_method_with_generic_parent('<') && node.op == .gt { c.error('cannot use `>` as `<=` operator method is not defined', left_right_pos) - } else if method := gen_sym.find_method_with_generic_parent('<') { - c.mark_fn_decl_as_referenced(method.fkey()) } } else if left_type in ast.integer_type_idxs && right_type in ast.integer_type_idxs { is_left_type_signed := left_type in ast.signed_integer_type_idxs diff --git a/vlib/v/tests/type_promotion_test.v b/vlib/v/tests/type_promotion_test.v index 8465e847f..36092e107 100644 --- a/vlib/v/tests/type_promotion_test.v +++ b/vlib/v/tests/type_promotion_test.v @@ -98,3 +98,21 @@ fn test_rune() { assert e == i64(79) assert typeof(e).name == 'i64' } + +fn test_int_literal_promotes_like_int_cast_for_small_unsigned_types() { + a := 300 + u8(120) + b := int(300) + u8(120) + c := 65536 + u16(1) + + assert a == 420 + assert typeof(a).name == 'int' + assert a == b + assert typeof(a).name == typeof(b).name + + assert c == 65537 + assert typeof(c).name == 'int' + + d := 5 + u8(120) + assert d == u8(125) + assert typeof(d).name == 'u8' +} -- 2.39.5