From 2de42d378042c66f0e8a08b577d68677df1a6a41 Mon Sep 17 00:00:00 2001 From: RunDevelopment Date: Fri, 25 Jul 2025 13:33:23 +0200 Subject: [PATCH 01/13] PoC: Integer range analysis --- .../src/casts/cast_possible_truncation.rs | 26 +- clippy_lints/src/casts/cast_possible_wrap.rs | 27 +- clippy_lints/src/casts/cast_sign_loss.rs | 50 +- clippy_lints/src/casts/mod.rs | 2 +- clippy_utils/src/lib.rs | 1 + clippy_utils/src/rinterval/arithmetic.rs | 2266 +++++++++++++++++ clippy_utils/src/rinterval/bits.rs | 159 ++ clippy_utils/src/rinterval/iinterval.rs | 389 +++ clippy_utils/src/rinterval/mod.rs | 403 +++ clippy_utils/src/sym.rs | 38 + tests/ui/cast.rs | 114 +- tests/ui/cast.stderr | 256 +- tests/ui/cast_size.64bit.stderr | 15 + 13 files changed, 3719 insertions(+), 27 deletions(-) create mode 100644 clippy_utils/src/rinterval/arithmetic.rs create mode 100644 clippy_utils/src/rinterval/bits.rs create mode 100644 clippy_utils/src/rinterval/iinterval.rs create mode 100644 clippy_utils/src/rinterval/mod.rs diff --git a/clippy_lints/src/casts/cast_possible_truncation.rs b/clippy_lints/src/casts/cast_possible_truncation.rs index 2eebe8492327..bae9b3e10c04 100644 --- a/clippy_lints/src/casts/cast_possible_truncation.rs +++ b/clippy_lints/src/casts/cast_possible_truncation.rs @@ -3,7 +3,7 @@ use clippy_utils::diagnostics::{span_lint, span_lint_and_then}; use clippy_utils::source::snippet; use clippy_utils::sugg::Sugg; use clippy_utils::ty::{get_discriminant_value, is_isize_or_usize}; -use clippy_utils::{expr_or_init, is_in_const_context, sym}; +use clippy_utils::{expr_or_init, is_in_const_context, rinterval, sym}; use rustc_abi::IntegerType; use rustc_errors::{Applicability, Diag}; use rustc_hir::def::{DefKind, Res}; @@ -83,10 +83,10 @@ fn apply_reductions(cx: &LateContext<'_>, nbits: u64, expr: &Expr<'_>, signed: b } } -pub(super) fn check( - cx: &LateContext<'_>, +pub(super) fn check<'cx>( + cx: &LateContext<'cx>, expr: &Expr<'_>, - cast_expr: &Expr<'_>, + cast_expr: &Expr<'cx>, cast_from: Ty<'_>, cast_to: Ty<'_>, cast_to_span: Span, @@ -166,7 +166,25 @@ pub(super) fn check( _ => return, }; + let interval_ctx = rinterval::IntervalCtxt::new(cx); + let from_interval = interval_ctx.eval(cast_expr); + span_lint_and_then(cx, CAST_POSSIBLE_TRUNCATION, expr.span, msg, |diag| { + if let Some(from_interval) = from_interval { + let note = if from_interval.min == from_interval.max { + format!( + "the cast operant may assume the value `{}`", + from_interval.to_string_untyped() + ) + } else { + format!( + "the cast operant may contain values in the range `{}`", + from_interval.to_string_untyped() + ) + }; + diag.note(note); + } + diag.help("if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ..."); // TODO: Remove the condition for const contexts when `try_from` and other commonly used methods // become const fn. diff --git a/clippy_lints/src/casts/cast_possible_wrap.rs b/clippy_lints/src/casts/cast_possible_wrap.rs index e26c03ccda93..8fcd86d05934 100644 --- a/clippy_lints/src/casts/cast_possible_wrap.rs +++ b/clippy_lints/src/casts/cast_possible_wrap.rs @@ -1,4 +1,5 @@ use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::rinterval; use rustc_hir::Expr; use rustc_lint::LateContext; use rustc_middle::ty::Ty; @@ -16,7 +17,13 @@ enum EmitState { LintOnPtrSize(u64), } -pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>) { +pub(super) fn check<'cx>( + cx: &LateContext<'cx>, + expr: &Expr<'_>, + cast_op: &Expr<'cx>, + cast_from: Ty<'_>, + cast_to: Ty<'_>, +) { let (Some(from_nbits), Some(to_nbits)) = ( utils::int_ty_to_nbits(cx.tcx, cast_from), utils::int_ty_to_nbits(cx.tcx, cast_to), @@ -38,6 +45,9 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_from: Ty<'_>, ca return; } + let interval_ctx = rinterval::IntervalCtxt::new(cx); + let from_interval = interval_ctx.eval(cast_op); + let should_lint = match (cast_from.is_ptr_sized_integral(), cast_to.is_ptr_sized_integral()) { (true, true) => { // casts between two ptr sized integers are trivially always the same size @@ -80,6 +90,21 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_from: Ty<'_>, ca }; span_lint_and_then(cx, CAST_POSSIBLE_WRAP, expr.span, message, |diag| { + if let Some(from_interval) = from_interval { + let note = if from_interval.min == from_interval.max { + format!( + "the cast operant may assume the value `{}`", + from_interval.to_string_untyped() + ) + } else { + format!( + "the cast operant may contain values in the range `{}`", + from_interval.to_string_untyped() + ) + }; + diag.note(note); + } + if let EmitState::LintOnPtrSize(16) = should_lint { diag .note("`usize` and `isize` may be as small as 16 bits on some platforms") diff --git a/clippy_lints/src/casts/cast_sign_loss.rs b/clippy_lints/src/casts/cast_sign_loss.rs index a70bd8861919..d92289fdd7c0 100644 --- a/clippy_lints/src/casts/cast_sign_loss.rs +++ b/clippy_lints/src/casts/cast_sign_loss.rs @@ -2,9 +2,9 @@ use std::convert::Infallible; use std::ops::ControlFlow; use clippy_utils::consts::{ConstEvalCtxt, Constant}; -use clippy_utils::diagnostics::span_lint; +use clippy_utils::diagnostics::{span_lint, span_lint_and_note}; use clippy_utils::visitors::{Descend, for_each_expr_without_closures}; -use clippy_utils::{method_chain_args, sext, sym}; +use clippy_utils::{method_chain_args, rinterval, sext, sym}; use rustc_hir::{BinOpKind, Expr, ExprKind}; use rustc_lint::LateContext; use rustc_middle::ty::{self, Ty}; @@ -38,18 +38,44 @@ const METHODS_UNWRAP: &[Symbol] = &[sym::unwrap, sym::unwrap_unchecked, sym::exp pub(super) fn check<'cx>( cx: &LateContext<'cx>, - expr: &Expr<'_>, - cast_op: &Expr<'_>, + expr: &Expr<'cx>, + cast_op: &Expr<'cx>, cast_from: Ty<'cx>, cast_to: Ty<'_>, ) { if should_lint(cx, cast_op, cast_from, cast_to) { - span_lint( - cx, - CAST_SIGN_LOSS, - expr.span, - format!("casting `{cast_from}` to `{cast_to}` may lose the sign of the value"), - ); + let interval_ctx = rinterval::IntervalCtxt::new(cx); + let from_interval = interval_ctx.eval(cast_op); + + if let Some(from_interval) = from_interval { + let note = if from_interval.min == from_interval.max { + format!( + "the cast operant may assume the value `{}`", + from_interval.to_string_untyped() + ) + } else { + format!( + "the cast operant may contain values in the range `{}`", + from_interval.to_string_untyped() + ) + }; + + span_lint_and_note( + cx, + CAST_SIGN_LOSS, + expr.span, + format!("casting `{cast_from}` to `{cast_to}` may lose the sign of the value"), + None, + note, + ); + } else { + span_lint( + cx, + CAST_SIGN_LOSS, + expr.span, + format!("casting `{cast_from}` to `{cast_to}` may lose the sign of the value"), + ); + } } } @@ -87,7 +113,7 @@ fn get_const_signed_int_eval<'cx>( expr: &Expr<'_>, ty: impl Into>>, ) -> Option { - let ty = ty.into().unwrap_or_else(|| cx.typeck_results().expr_ty(expr)); + let ty: Ty<'cx> = ty.into().unwrap_or_else(|| cx.typeck_results().expr_ty(expr)); if let Constant::Int(n) = ConstEvalCtxt::new(cx).eval(expr)? && let ty::Int(ity) = *ty.kind() @@ -102,7 +128,7 @@ fn get_const_unsigned_int_eval<'cx>( expr: &Expr<'_>, ty: impl Into>>, ) -> Option { - let ty = ty.into().unwrap_or_else(|| cx.typeck_results().expr_ty(expr)); + let ty: Ty<'cx> = ty.into().unwrap_or_else(|| cx.typeck_results().expr_ty(expr)); if let Constant::Int(n) = ConstEvalCtxt::new(cx).eval(expr)? && let ty::Uint(_ity) = *ty.kind() diff --git a/clippy_lints/src/casts/mod.rs b/clippy_lints/src/casts/mod.rs index 37accff5eaa8..540a7dcbeeae 100644 --- a/clippy_lints/src/casts/mod.rs +++ b/clippy_lints/src/casts/mod.rs @@ -887,7 +887,7 @@ impl<'tcx> LateLintPass<'tcx> for Casts { if cast_to.is_numeric() { cast_possible_truncation::check(cx, expr, cast_from_expr, cast_from, cast_to, cast_to_hir.span); if cast_from.is_numeric() { - cast_possible_wrap::check(cx, expr, cast_from, cast_to); + cast_possible_wrap::check(cx, expr, cast_from_expr, cast_from, cast_to); cast_precision_loss::check(cx, expr, cast_from, cast_to); cast_sign_loss::check(cx, expr, cast_from_expr, cast_from, cast_to); cast_abs_to_unsigned::check(cx, expr, cast_from_expr, cast_from, cast_to, self.msrv); diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index 8b9cd6a54dd6..7bfda3fcf46d 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -66,6 +66,7 @@ pub mod numeric_literal; pub mod paths; pub mod ptr; pub mod qualify_min_const_fn; +pub mod rinterval; pub mod source; pub mod str_utils; pub mod sugg; diff --git a/clippy_utils/src/rinterval/arithmetic.rs b/clippy_utils/src/rinterval/arithmetic.rs new file mode 100644 index 000000000000..9e5713237acf --- /dev/null +++ b/clippy_utils/src/rinterval/arithmetic.rs @@ -0,0 +1,2266 @@ +use super::bits::Bits; +use super::{IInterval, IntType, IntTypeInfo}; + +#[derive(Debug)] +pub enum ArithError { + TypeError, + Unsupported, +} + +pub type ArithResult = Result; + +fn check_same_ty(lhs: &IInterval, rhs: &IInterval) -> ArithResult { + if lhs.ty != rhs.ty { + return Err(ArithError::TypeError); + } + Ok(lhs.ty) +} + +macro_rules! check_non_empty { + ($x:expr) => { + if $x.is_empty() { + return Ok(IInterval::empty($x.ty)); + } + }; + ($lhs:expr, $rhs:expr) => { + if $lhs.is_empty() || $rhs.is_empty() { + return Ok(IInterval::empty($lhs.ty)); + } + }; +} + +#[derive(Clone, Copy, PartialEq)] +enum Overflow { + None, + Under, + Over, +} + +#[derive(Clone, Copy, PartialEq, Eq)] +enum SignBit { + NonNeg = 1, + Neg = -1, +} + +fn min_4(values: &[i128; 4]) -> i128 { + values[0].min(values[1]).min(values[2]).min(values[3]) +} +fn max_4(values: &[i128; 4]) -> i128 { + values[0].max(values[1]).max(values[2]).max(values[3]) +} +fn range_4(ty: IntType, values: [i128; 4]) -> IInterval { + debug_assert!(ty.is_signed()); + IInterval::new_signed(ty, min_4(&values), max_4(&values)) +} + +/// Splits the interval by the sign bit of its values. The given function will +/// called with the min and max values of unsigned intervals. +/// +/// E.g. `f` will be called with `(1, 10)` for the interval `[1, 10]` and with +/// `(0, 5), (u128::MAX-4, u128::MAX)` for the interval `[-5, 5]`. +fn split_by_sign_bit(i: &IInterval, mut f: impl FnMut(u128, u128) -> IInterval) -> IInterval { + debug_assert!(!i.is_empty()); + + if i.ty.is_signed() { + let (min, max) = i.as_signed(); + + if min < 0 { + if max >= 0 { + f(min.cast_unsigned(), u128::MAX).hull_unwrap(&f(min.max(0).cast_unsigned(), max.cast_unsigned())) + } else { + f(min.cast_unsigned(), max.cast_unsigned()) + } + } else { + f(min.cast_unsigned(), max.cast_unsigned()) + } + } else { + let (min, max) = i.as_unsigned(); + f(min, max) + } +} +/// Same as `split_by_sign_bit`, but only for signed intervals. +fn split_by_sign_bit_signed(i: &IInterval, mut f: impl FnMut(i128, i128, SignBit) -> IInterval) -> IInterval { + debug_assert!(!i.is_empty()); + debug_assert!(i.ty.is_signed()); + + let (min, max) = i.as_signed(); + + if min < 0 { + if max >= 0 { + f(min, -1, SignBit::Neg).hull_unwrap(&f(min.max(0), max, SignBit::NonNeg)) + } else { + f(min, max, SignBit::Neg) + } + } else { + f(min, max, SignBit::NonNeg) + } +} + +pub struct Arithmetic { + /// If `true`, checked arithmetic will be assumed. + /// + /// Suppose we have an expression `x + y` and we know that `x` and `y` are + /// `u8`s with the ranges `0 <= x <= 200` and `100 <= y <= 200`. If not for + /// the limited bit width of `u8`, the expression `x + y` *would* have a + /// range `100 <= x + y <= 400`. However, since `u8` can only hold values + /// up to 255, so overflow occurs. + /// + /// If checked arithmetic is assumed, then the range of the expression is + /// `100 <= x + y <= 255`. Since the addition will panic on overflow, no + /// other numbers can be produced. + /// + /// If unchecked arithmetic is assumed, then the range of the expression is + /// `0 <= x + y <= 255`. Since addition will wrap on overflow, both 0 and + /// 255 are possible results. + pub checked: bool, +} + +impl Arithmetic { + pub fn add(&self, left: &IInterval, right: &IInterval) -> ArithResult { + if self.checked { + Self::strict_add(left, right) + } else { + Self::wrapping_add(left, right) + } + } + pub fn neg(&self, value: &IInterval) -> ArithResult { + if self.checked { + Self::strict_neg(value) + } else { + Self::wrapping_neg(value) + } + } + pub fn sub(&self, left: &IInterval, right: &IInterval) -> ArithResult { + if self.checked { + Self::strict_sub(left, right) + } else { + Self::wrapping_sub(left, right) + } + } + pub fn mul(&self, left: &IInterval, right: &IInterval) -> ArithResult { + if self.checked { + Self::strict_mul(left, right) + } else { + Self::wrapping_mul(left, right) + } + } + pub fn div(&self, left: &IInterval, right: &IInterval) -> ArithResult { + if self.checked { + Self::strict_div(left, right) + } else { + Self::wrapping_div(left, right) + } + } + pub fn rem(&self, left: &IInterval, right: &IInterval) -> ArithResult { + if self.checked { + Self::strict_rem(left, right) + } else { + Self::wrapping_rem(left, right) + } + } + pub fn rem_euclid(&self, left: &IInterval, right: &IInterval) -> ArithResult { + if self.checked { + Self::strict_rem_euclid(left, right) + } else { + Self::wrapping_rem_euclid(left, right) + } + } + pub fn abs(&self, value: &IInterval) -> ArithResult { + if self.checked { + Self::strict_abs(value) + } else { + Self::wrapping_abs(value) + } + } + pub fn shl(&self, value: &IInterval, shift: &IInterval) -> ArithResult { + if self.checked { + Self::strict_shl(value, shift) + } else { + Self::wrapping_shl(value, shift) + } + } + pub fn shr(&self, value: &IInterval, shift: &IInterval) -> ArithResult { + if self.checked { + Self::strict_shr(value, shift) + } else { + Self::wrapping_shr(value, shift) + } + } + + /// Addition which saturates on overflow. + pub fn saturating_add(lhs: &IInterval, rhs: &IInterval) -> ArithResult { + let ty = check_same_ty(lhs, rhs)?; + check_non_empty!(lhs, rhs); + + match ty.info() { + IntTypeInfo::Signed(t_min, t_max) => { + let (l_min, l_max) = lhs.as_signed(); + let (r_min, r_max) = rhs.as_signed(); + + let min = l_min.saturating_add(r_min).clamp(t_min, t_max); + let max = l_max.saturating_add(r_max).clamp(t_min, t_max); + + Ok(IInterval::new_signed(ty, min, max)) + }, + IntTypeInfo::Unsigned(t_max) => { + let (l_min, l_max) = lhs.as_unsigned(); + let (r_min, r_max) = rhs.as_unsigned(); + + let min = l_min.saturating_add(r_min).min(t_max); + let max = l_max.saturating_add(r_max).min(t_max); + + Ok(IInterval::new_unsigned(ty, min, max)) + }, + } + } + /// Addition which panics on overflow. + pub fn strict_add(lhs: &IInterval, rhs: &IInterval) -> ArithResult { + let ty = check_same_ty(lhs, rhs)?; + check_non_empty!(lhs, rhs); + + match ty.info() { + IntTypeInfo::Signed(t_min, t_max) => { + let (l_min, l_max) = lhs.as_signed(); + let (r_min, r_max) = rhs.as_signed(); + + let min = if l_min < 0 { + // only underflow is possible, so saturate + l_min.saturating_add(r_min).max(t_min) + } else { + // only overflow is possible + let Some(min) = l_min.checked_add(r_min) else { + // the sum will always overflow + return Ok(IInterval::empty(ty)); + }; + if min > t_max { + // the sum will always overflow + return Ok(IInterval::empty(ty)); + } + min + }; + + let max = if l_max < 0 { + // only underflow is possible + let Some(max) = l_max.checked_add(r_max) else { + // the sum will always underflow + return Ok(IInterval::empty(ty)); + }; + if max < t_min { + // the sum will always underflow + return Ok(IInterval::empty(ty)); + } + max + } else { + // only overflow is possible, so saturate + l_max.saturating_add(r_max).min(t_max) + }; + + Ok(IInterval::new_signed(ty, min, max)) + }, + IntTypeInfo::Unsigned(t_max) => { + let (l_min, l_max) = lhs.as_unsigned(); + let (r_min, r_max) = rhs.as_unsigned(); + + let Some(min) = l_min.checked_add(r_min) else { + // the sum will always overflow + return Ok(IInterval::empty(ty)); + }; + if min > t_max { + // the sum will always overflow + return Ok(IInterval::empty(ty)); + } + let max = l_max.saturating_add(r_max).min(t_max); + + Ok(IInterval::new_unsigned(ty, min, max)) + }, + } + } + /// Addition which wraps on overflow. + pub fn wrapping_add(lhs: &IInterval, rhs: &IInterval) -> ArithResult { + let ty = check_same_ty(lhs, rhs)?; + check_non_empty!(lhs, rhs); + + match ty.info() { + IntTypeInfo::Signed(t_min, t_max) => { + let (l_min, l_max) = lhs.as_signed(); + let (r_min, r_max) = rhs.as_signed(); + + let (mut min, min_overflow) = l_min.overflowing_add(r_min); + let (mut max, max_overflow) = l_max.overflowing_add(r_max); + + let min_overflow = if min_overflow { + if l_min < 0 { Overflow::Under } else { Overflow::Over } + } else if min < t_min { + min -= t_min * 2; + Overflow::Under + } else if min > t_max { + min += t_min * 2; + Overflow::Over + } else { + Overflow::None + }; + let max_overflow = if max_overflow { + if l_max < 0 { Overflow::Under } else { Overflow::Over } + } else if max < t_min { + max -= t_min * 2; + Overflow::Under + } else if max > t_max { + max += t_min * 2; + Overflow::Over + } else { + Overflow::None + }; + + if min_overflow == max_overflow { + // If both overflow the same way, the result is simply the range + Ok(IInterval::new_signed(ty, min, max)) + } else if min_overflow == Overflow::None || max_overflow == Overflow::None { + // If one doesn't over/underflow while the other does, + // then the result is the entire range. + Ok(IInterval::new_signed(ty, t_min, t_max)) + } else { + // Lastly, min underflow while max overflows. + // Idk what to do in this case, so just return the entire range. + Ok(IInterval::new_signed(ty, t_min, t_max)) + } + }, + IntTypeInfo::Unsigned(t_max) => { + let (l_min, l_max) = lhs.as_unsigned(); + let (r_min, r_max) = rhs.as_unsigned(); + + let (mut min, mut min_overflow) = l_min.overflowing_add(r_min); + let (mut max, mut max_overflow) = l_max.overflowing_add(r_max); + + if min > t_max { + min &= t_max; + min_overflow = true; + } + if max > t_max { + max &= t_max; + max_overflow = true; + } + + if !min_overflow && max_overflow { + // this means that both 0 and t_max are possible results + return Ok(IInterval::new_unsigned(ty, 0, t_max)); + } + + Ok(IInterval::new_unsigned(ty, min, max)) + }, + } + } + + /// Negation which saturates on overflow. + pub fn saturating_neg(x: &IInterval) -> ArithResult { + check_non_empty!(x); + + match x.ty.info() { + IntTypeInfo::Signed(t_min, t_max) => { + debug_assert_eq!(t_min, -t_max - 1); + + let (x_min, x_max) = x.as_signed(); + + let min = x_max.saturating_neg().min(t_max); + let max = x_min.saturating_neg().min(t_max); + + Ok(IInterval::new_signed(x.ty, min, max)) + }, + IntTypeInfo::Unsigned(_) => Err(ArithError::Unsupported), + } + } + /// Negation which panics on overflow. + pub fn strict_neg(x: &IInterval) -> ArithResult { + check_non_empty!(x); + + match x.ty.info() { + IntTypeInfo::Signed(t_min, t_max) => { + debug_assert_eq!(t_min, -t_max - 1); + + let (mut x_min, x_max) = x.as_signed(); + + if x_max == t_min { + // all values in the range will overflow + Ok(IInterval::empty(x.ty)) + } else { + if x_min == t_min { + x_min += 1; // ignore value that will overflow + } + + Ok(IInterval::new_signed(x.ty, -x_max, -x_min)) + } + }, + IntTypeInfo::Unsigned(_) => { + let (x_min, _) = x.as_unsigned(); + + if x_min == 0 { + // contains zero + Ok(IInterval::new_unsigned(x.ty, 0, 0)) + } else { + Ok(IInterval::empty(x.ty)) + } + }, + } + } + /// Negation which wraps on overflow. + pub fn wrapping_neg(x: &IInterval) -> ArithResult { + check_non_empty!(x); + + match x.ty.info() { + IntTypeInfo::Signed(t_min, t_max) => { + debug_assert_eq!(t_min, -t_max - 1); + + let (mut x_min, x_max) = x.as_signed(); + + if x_max == t_min { + // all values in the range will overflow + Ok(x.clone()) + } else { + let overflow = x_min == t_min; + if overflow { + x_min += 1; // ignore value that will overflow + } + + let min = if overflow { t_min } else { -x_max }; + let max = -x_min; + + Ok(IInterval::new_signed(x.ty, min, max)) + } + }, + IntTypeInfo::Unsigned(t_max) => { + let (x_min, x_max) = x.as_unsigned(); + + if x_min == 0 && x_max != 0 { + // this means that the range wraps around and covers both 0 + Ok(IInterval::new_unsigned(x.ty, 0, t_max)) + } else { + let min = x_max.wrapping_neg() & t_max; + let max = x_min.wrapping_neg() & t_max; + + Ok(IInterval::new_unsigned(x.ty, min, max)) + } + }, + } + } + + /// Subtraction which saturates on overflow. + pub fn saturating_sub(lhs: &IInterval, rhs: &IInterval) -> ArithResult { + let ty = check_same_ty(lhs, rhs)?; + check_non_empty!(lhs, rhs); + + match ty.info() { + IntTypeInfo::Signed(t_min, t_max) => { + let (l_min, l_max) = lhs.as_signed(); + let (r_min, r_max) = rhs.as_signed(); + + let min = l_min.saturating_sub(r_max).clamp(t_min, t_max); + let max = l_max.saturating_sub(r_min).clamp(t_min, t_max); + + Ok(IInterval::new_signed(ty, min, max)) + }, + IntTypeInfo::Unsigned(_) => { + let (l_min, l_max) = lhs.as_unsigned(); + let (r_min, r_max) = rhs.as_unsigned(); + + let min = l_min.saturating_sub(r_max); + let max = l_max.saturating_sub(r_min); + + Ok(IInterval::new_unsigned(ty, min, max)) + }, + } + } + /// Subtraction which panics on overflow. + pub fn strict_sub(lhs: &IInterval, rhs: &IInterval) -> ArithResult { + let ty = check_same_ty(lhs, rhs)?; + check_non_empty!(lhs, rhs); + + match ty.info() { + IntTypeInfo::Signed(t_min, t_max) => { + debug_assert_eq!(t_min, -t_max - 1); + + let (l_min, l_max) = lhs.as_signed(); + let (mut r_min, r_max) = rhs.as_signed(); + + // The idea here is to calculate `lhs - rhs` as `lhs + rhs.neg()`. + // This doesn't work for rhs == t_min, because negating it will overflow, + // so we have to handle that case separately. + + let min_range = if r_min == t_min { + // range for `lhs - t_min` + let min_range = if l_min >= 0 { + // lhs >= 0, so the result will always overflow + IInterval::empty(ty) + } else { + // lhs < 0, so the result will always underflow + IInterval::new_signed(ty, l_min - t_min, l_max.saturating_sub(t_min).min(t_max)) + }; + + r_min += 1; + + if r_max == t_min { + return Ok(min_range); + } + + min_range + } else { + IInterval::empty(ty) + }; + + // we can now safely negate rhs + let rhs_neg = IInterval::new_signed(ty, -r_max, -r_min); + let sum = Self::strict_add(lhs, &rhs_neg)?; + Ok(sum.hull_unwrap(&min_range)) + }, + IntTypeInfo::Unsigned(_) => { + let (l_min, l_max) = lhs.as_unsigned(); + let (r_min, r_max) = rhs.as_unsigned(); + + let min = l_min.saturating_sub(r_max); + let (max, overflows) = l_max.overflowing_sub(r_min); + + if overflows { + Ok(IInterval::empty(ty)) + } else { + Ok(IInterval::new_unsigned(ty, min, max)) + } + }, + } + } + /// Subtraction which wrap on overflow. + pub fn wrapping_sub(lhs: &IInterval, rhs: &IInterval) -> ArithResult { + Self::wrapping_add(lhs, &Self::wrapping_neg(rhs)?) + } + + /// Multiplication which saturates on overflow and panics on rhs == 0. + pub fn saturating_mul(lhs: &IInterval, rhs: &IInterval) -> ArithResult { + let ty = check_same_ty(lhs, rhs)?; + check_non_empty!(lhs, rhs); + + match ty.info() { + IntTypeInfo::Signed(t_min, t_max) => { + let (l_min, l_max) = lhs.as_signed(); + let (r_min, r_max) = rhs.as_signed(); + + let points = [ + l_min.saturating_mul(r_min), + l_min.saturating_mul(r_max), + l_max.saturating_mul(r_min), + l_max.saturating_mul(r_max), + ]; + let min = min_4(&points).clamp(t_min, t_max); + let max = max_4(&points).clamp(t_min, t_max); + + Ok(IInterval::new_signed(ty, min, max)) + }, + IntTypeInfo::Unsigned(t_max) => { + let (l_min, l_max) = lhs.as_unsigned(); + let (r_min, r_max) = rhs.as_unsigned(); + + let min = l_min.saturating_mul(r_min).min(t_max); + let max = l_max.saturating_mul(r_max).min(t_max); + + Ok(IInterval::new_unsigned(ty, min, max)) + }, + } + } + /// Multiplication which panics on overflow and panics on rhs == 0. + pub fn strict_mul(lhs: &IInterval, rhs: &IInterval) -> ArithResult { + let ty = check_same_ty(lhs, rhs)?; + check_non_empty!(lhs, rhs); + + match ty.info() { + IntTypeInfo::Signed(t_min, t_max) => { + let (l_min, l_max) = lhs.as_signed(); + let (r_min, r_max) = rhs.as_signed(); + + let quadrant = |mut l_min: i128, + mut l_max: i128, + mut l_sign: SignBit, + mut r_min: i128, + mut r_max: i128, + mut r_sign: SignBit| + -> IInterval { + debug_assert!(l_min > 0 || l_max < 0); + debug_assert!(r_min > 0 || r_max < 0); + + if l_sign == SignBit::NonNeg && r_sign == SignBit::Neg { + std::mem::swap(&mut l_min, &mut r_min); + std::mem::swap(&mut l_max, &mut r_max); + std::mem::swap(&mut l_sign, &mut r_sign); + } + + match (l_sign, r_sign) { + (SignBit::NonNeg, SignBit::NonNeg) => { + // both positive + let (min, min_overflow) = l_min.overflowing_mul(r_min); + if min_overflow || min > t_max { + // the multiplication will always overflow + return IInterval::empty(ty); + } + IInterval::new_signed(ty, min, l_max.saturating_mul(r_max).min(t_max)) + }, + (SignBit::NonNeg, SignBit::Neg) => unreachable!(), + (SignBit::Neg, SignBit::NonNeg) => { + // lhs negative, rhs positive + // both positive + let (max, max_overflow) = l_max.overflowing_mul(r_min); + if max_overflow || max < t_min { + // the multiplication will always overflow + return IInterval::empty(ty); + } + IInterval::new_signed(ty, l_min.saturating_mul(r_max).max(t_min), max) + }, + (SignBit::Neg, SignBit::Neg) => { + // both negative + let (min, min_overflow) = l_max.overflowing_mul(r_max); + if min_overflow || min > t_max { + // the multiplication will always overflow + return IInterval::empty(ty); + } + IInterval::new_signed(ty, l_max * r_max, l_min.saturating_mul(r_min).min(t_max)) + }, + } + }; + + let split_l = |r_min: i128, r_max: i128, r_sign: SignBit| -> IInterval { + debug_assert!(r_min > 0 || r_max < 0); + + let mut result = IInterval::empty(ty); + + if l_min < 0 { + result = + result.hull_unwrap(&quadrant(l_min, l_max.min(-1), SignBit::Neg, r_min, r_max, r_sign)); + } + if l_min <= 0 && 0 <= l_max { + result = result.hull_unwrap(&IInterval::single_signed(ty, 0)); + } + if l_max > 0 { + result = + result.hull_unwrap(&quadrant(l_min.max(1), l_max, SignBit::NonNeg, r_min, r_max, r_sign)); + } + + result + }; + + let mut result = IInterval::empty(ty); + + if r_min < 0 { + result = result.hull_unwrap(&split_l(r_min, r_max.min(-1), SignBit::Neg)); + } + if r_min <= 0 && 0 <= r_max { + result = result.hull_unwrap(&IInterval::single_signed(ty, 0)); + } + if r_max > 0 { + result = result.hull_unwrap(&split_l(r_min.max(1), r_max, SignBit::NonNeg)); + } + + Ok(result) + }, + IntTypeInfo::Unsigned(t_max) => { + let (l_min, l_max) = lhs.as_unsigned(); + let (r_min, r_max) = rhs.as_unsigned(); + + let (min, min_overflow) = l_min.overflowing_mul(r_min); + if min_overflow || min > t_max { + // the multiplication will always overflow + return Ok(IInterval::empty(ty)); + } + let max = l_max.saturating_mul(r_max).min(t_max); + + Ok(IInterval::new_unsigned(ty, min, max)) + }, + } + } + /// Multiplication which wraps on overflow and panics on rhs == 0. + pub fn wrapping_mul(lhs: &IInterval, rhs: &IInterval) -> ArithResult { + let ty = check_same_ty(lhs, rhs)?; + check_non_empty!(lhs, rhs); + + match ty.info() { + IntTypeInfo::Signed(t_min, t_max) => { + let (l_min, l_max) = lhs.as_signed(); + let (r_min, r_max) = rhs.as_signed(); + + let (p0, p0_overflow) = l_min.overflowing_mul(r_min); + let (p1, p1_overflow) = l_min.overflowing_mul(r_max); + let (p2, p2_overflow) = l_max.overflowing_mul(r_min); + let (p3, p3_overflow) = l_max.overflowing_mul(r_max); + + if !p0_overflow && !p1_overflow && !p2_overflow && !p3_overflow { + let points = [p0, p1, p2, p3]; + let min = min_4(&points); + let max = max_4(&points); + debug_assert!(min <= max); + if t_min <= min && max <= t_max { + return Ok(IInterval::new_signed(ty, min, max)); + } + } + + Ok(IInterval::full(ty)) + }, + IntTypeInfo::Unsigned(t_max) => { + let (l_min, l_max) = lhs.as_unsigned(); + let (r_min, r_max) = rhs.as_unsigned(); + + let mul_single = |l_min: u128, l_max: u128, r: u128| -> IInterval { + let min = l_min.wrapping_mul(r) & t_max; + let max = l_max.wrapping_mul(r) & t_max; + if min <= max && (l_max - l_min).saturating_mul(r) < t_max { + IInterval::new_unsigned(ty, min, max) + } else { + IInterval::full(ty) + } + }; + + let (max, max_overflow) = l_max.overflowing_mul(r_max); + if max_overflow || max > t_max { + let range = if l_min == l_max { + mul_single(r_min, r_max, l_min) + } else if r_min == r_max { + mul_single(l_min, l_max, r_min) + } else { + // I'm out of ideas + IInterval::full(ty) + }; + return Ok(range); + } + let min = l_min.wrapping_mul(r_min); + + Ok(IInterval::new_unsigned(ty, min, max)) + }, + } + } + + /// Division which saturates on overflow and panics on rhs == 0. + pub fn saturating_div(lhs: &IInterval, rhs: &IInterval) -> ArithResult { + let ty = check_same_ty(lhs, rhs)?; + check_non_empty!(lhs, rhs); + + match ty.info() { + IntTypeInfo::Signed(t_min, t_max) => { + // the only difference between saturating_div and strict_div is + // the case t_min / -1, because it's the only case which overflows + + let strict = Self::strict_div(lhs, rhs)?; + + let (l_min, _) = lhs.as_signed(); + let (r_min, r_max) = rhs.as_signed(); + + if l_min == t_min && r_min <= -1 && -1 <= r_max { + // t_min / -1 will overflow, so we have to add t_min to the result + Ok(IInterval::single_signed(ty, t_max).hull_unwrap(&strict)) + } else { + Ok(strict) + } + }, + // same as strict_div for unsigned types + IntTypeInfo::Unsigned(_) => Self::strict_div(lhs, rhs), + } + } + /// Division which panics on overflow and rhs == 0. + pub fn strict_div(lhs: &IInterval, rhs: &IInterval) -> ArithResult { + let ty = check_same_ty(lhs, rhs)?; + check_non_empty!(lhs, rhs); + + match ty.info() { + IntTypeInfo::Signed(t_min, t_max) => { + debug_assert_eq!(t_min, -t_max - 1); + + let (l_min, l_max) = lhs.as_signed(); + let (r_min, r_max) = rhs.as_signed(); + + // We have to split the rhs into 4 cases: + // 1. -inf..=-2: the negative range + // 2. -1: t_min / -1 overflows, which has to be handled separately + // 3. 0: division by zero panics + // 4. 1..=inf: the positive range + + // this will be the total union of all cases + let mut result = IInterval::empty(ty); + + // case 1: -inf..=-2 + if r_min <= -2 { + let r_max = r_max.min(-2); + + let points = [l_min / r_min, l_min / r_max, l_max / r_min, l_max / r_max]; + result = result.hull_unwrap(&range_4(ty, points)); + } + + // case 2: -1 + if r_min <= -1 && -1 <= r_max { + // same as strict_neg + result = result.hull_unwrap(&Self::strict_neg(lhs)?); + } + + // case 3: 0 + // This will always panic, so it doesn't contribute to the result. + + // case 4: 1..=inf + if r_max >= 1 { + let r_min = r_min.max(1); + + let points = [l_min / r_min, l_min / r_max, l_max / r_min, l_max / r_max]; + result = result.hull_unwrap(&range_4(ty, points)); + } + + Ok(result) + }, + IntTypeInfo::Unsigned(_) => { + let (l_min, l_max) = lhs.as_unsigned(); + let (mut r_min, r_max) = rhs.as_unsigned(); + + if r_max == 0 { + // always div by 0 + return Ok(IInterval::empty(ty)); + } + if r_min == 0 { + r_min = 1; // to avoid division by zero + } + + let min = l_min / r_max; + let max = l_max / r_min; + + Ok(IInterval::new_unsigned(ty, min, max)) + }, + } + } + /// Division which wrap on overflow and panics on rhs == 0. + pub fn wrapping_div(lhs: &IInterval, rhs: &IInterval) -> ArithResult { + let ty = check_same_ty(lhs, rhs)?; + check_non_empty!(lhs, rhs); + + match ty.info() { + IntTypeInfo::Signed(t_min, _) => { + // the only difference between wrapping_div and strict_div is + // the case t_min / -1, because it's the only case which overflows + + let strict = Self::strict_div(lhs, rhs)?; + + let (l_min, _) = lhs.as_signed(); + let (r_min, r_max) = rhs.as_signed(); + + if l_min == t_min && r_min <= -1 && -1 <= r_max { + // t_min / -1 will overflow, so we have to add t_min to the result + Ok(IInterval::single_signed(ty, t_min).hull_unwrap(&strict)) + } else { + Ok(strict) + } + }, + // same as strict_div for unsigned types + IntTypeInfo::Unsigned(_) => Self::strict_div(lhs, rhs), + } + } + + /// Division which panics on overflow and rhs == 0. + pub fn strict_div_euclid(lhs: &IInterval, rhs: &IInterval) -> ArithResult { + let ty = check_same_ty(lhs, rhs)?; + check_non_empty!(lhs, rhs); + + match ty.info() { + IntTypeInfo::Signed(t_min, t_max) => { + debug_assert_eq!(t_min, -t_max - 1); + + // TODO: implement this properly + + let (l_min, _) = lhs.as_signed(); + let (r_min, _) = rhs.as_signed(); + + if l_min >= 0 && r_min >= 0 { + // both positive + return Self::strict_div(lhs, rhs); + } + + Ok(IInterval::full(ty)) + }, + IntTypeInfo::Unsigned(_) => Self::strict_div(lhs, rhs), + } + } + /// Division which wrap on overflow and panics on rhs == 0. + pub fn wrapping_div_euclid(lhs: &IInterval, rhs: &IInterval) -> ArithResult { + let ty = check_same_ty(lhs, rhs)?; + check_non_empty!(lhs, rhs); + + match ty.info() { + IntTypeInfo::Signed(t_min, _) => { + // the only difference between wrapping_div_euclid and + // strict_div_euclid is the case t_min / -1 + + let strict = Self::strict_div_euclid(lhs, rhs)?; + + let (l_min, _) = lhs.as_signed(); + let (r_min, r_max) = rhs.as_signed(); + + if l_min == t_min && r_min <= -1 && -1 <= r_max { + // t_min / -1 will overflow, so we have to add t_min to the result + Ok(IInterval::single_signed(ty, t_min).hull_unwrap(&strict)) + } else { + Ok(strict) + } + }, + // same as strict_div for unsigned types + IntTypeInfo::Unsigned(_) => Self::strict_div(lhs, rhs), + } + } + + /// Division which rounds towards positive infinity and panics on rhs == 0. + pub fn div_ceil(lhs: &IInterval, rhs: &IInterval) -> ArithResult { + let ty = check_same_ty(lhs, rhs)?; + check_non_empty!(lhs, rhs); + + match ty.info() { + IntTypeInfo::Signed(_, _) => Err(ArithError::Unsupported), + IntTypeInfo::Unsigned(_) => { + let (l_min, l_max) = lhs.as_unsigned(); + let (mut r_min, r_max) = rhs.as_unsigned(); + + if r_max == 0 { + // always div by 0 + return Ok(IInterval::empty(ty)); + } + if r_min == 0 { + r_min = 1; + } + + let min = l_min.div_ceil(r_max); + let max = l_max.div_ceil(r_min); + + Ok(IInterval::new_unsigned(ty, min, max)) + }, + } + } + + /// Remainder which panics on overflow and rhs == 0. + pub fn strict_rem(lhs: &IInterval, rhs: &IInterval) -> ArithResult { + let ty = check_same_ty(lhs, rhs)?; + check_non_empty!(lhs, rhs); + + match ty.info() { + IntTypeInfo::Signed(t_min, t_max) => { + debug_assert_eq!(t_min, -t_max - 1); + + let (l_min, l_max) = lhs.as_signed(); + let (mut r_min, r_max) = rhs.as_signed(); + + // Okay, so remainder is a pain to implement, because the + // operation works as follows: + // 1. If rhs == 0, panic. + // 2. If rhs == -1 and lhs == t_min, panic. + // 3. If rhs < 0, return lhs % -rhs. + // 4. If lhs < 0, return -(-lhs % rhs). + // 5. Return lhs % rhs (everything unsigned). + // Note that -rhs and -lhs can overflow , so that needs + // to be handled separately too. + + let mut result = IInterval::empty(ty); + + // handle rhs == t_min separately + if r_min == t_min { + let min_range = if l_min == t_min { + let zero = IInterval::single_signed(ty, 0); + if l_max == t_min { + zero + } else { + zero.hull_unwrap(&IInterval::new_signed(ty, l_min + 1, l_max)) + } + } else { + lhs.clone() + }; + + if r_max == t_min { + return Ok(min_range); + } + + result = min_range; + r_min += 1; + // this case is handled now, which means we can now safely + // compute -r_min and -r_max + } + + let positive_everything = |l_min: i128, l_max: i128, r_min: i128, r_max: i128| -> IInterval { + debug_assert!(0 <= l_min && l_min <= l_max && l_max <= t_max); + debug_assert!(0 <= r_min && r_min <= r_max && r_max <= t_max); + + // if the rhs is a single value, this is possible + if r_min == r_max { + let r = r_min; + // if the lhs as more or equal values than the rhs, then the + // result is the trivial range [0, r - 1], which isn't + // interesting + if l_max - l_min < r { + let min = l_min % r; + let max = l_max % r; + if min <= max { + return IInterval::new_signed(ty, min, max); + } + } + } + + if l_max < r_min { + return IInterval::new_signed(ty, l_min, l_max); + } + + IInterval::new_signed(ty, 0, l_max.min(r_max - 1)) + }; + let positive_rhs = |r_min: i128, r_max: i128| -> IInterval { + debug_assert!(0 < r_min && r_min <= r_max && r_max <= t_max); + + let mut l_min = l_min; + + let min_range = if l_min == t_min { + l_min += 1; + let min_range = if r_min == r_max { + IInterval::single_signed(ty, t_min % r_min) + } else { + IInterval::new_signed(ty, -r_max + 1, 0) + }; + + if l_max == t_min { + return min_range; + } + + min_range + } else { + IInterval::empty(ty) + }; + + let negative = if l_min < 0 { + // this is -(-lhs & rhs) + let (min, max) = positive_everything((-l_max).max(0), -l_min, r_min, r_max).as_signed(); + IInterval::new_signed(ty, -max, -min) + } else { + IInterval::empty(ty) + }; + let positive = if l_max >= 0 { + positive_everything(l_min.max(0), l_max, r_min, r_max) + } else { + IInterval::empty(ty) + }; + + negative.hull_unwrap(&positive).hull_unwrap(&min_range) + }; + + // case 1: -inf..=-2 + if r_min <= -2 { + result = result.hull_unwrap(&positive_rhs(-r_max.min(-2), -r_min)); + } + + // case 2: -1 + if r_min <= -1 && -1 <= r_max { + // t_min % -1 panics, while everything else goes to 0 + if l_max != t_min { + result = result.hull_unwrap(&IInterval::single_signed(ty, 0)); + } + } + + // case 3: 0 + // This will always panic, so it doesn't contribute to the result. + + // case 4: 1..=inf + if r_max >= 1 { + result = result.hull_unwrap(&positive_rhs(r_min.max(1), r_max)); + } + + Ok(result) + }, + IntTypeInfo::Unsigned(_) => { + let (l_min, l_max) = lhs.as_unsigned(); + let (mut r_min, r_max) = rhs.as_unsigned(); + + if r_max == 0 { + // always div by 0 + return Ok(IInterval::empty(ty)); + } + if r_min == 0 { + r_min = 1; // to avoid division by zero + } + + // if the rhs is a single value, this is possible + if r_min == r_max { + let r = r_min; + // if the lhs as more or equal values than the rhs, then the + // result is the trivial range [0, r - 1], which isn't + // interesting + if l_max - l_min < r { + let min = l_min % r; + let max = l_max % r; + if min <= max { + return Ok(IInterval::new_unsigned(ty, min, max)); + } + } + } + + if l_max < r_min { + return Ok(lhs.clone()); + } + + Ok(IInterval::new_unsigned(ty, 0, l_max.min(r_max - 1))) + }, + } + } + /// Remainder which wrap on overflow and panics on rhs == 0. + pub fn wrapping_rem(lhs: &IInterval, rhs: &IInterval) -> ArithResult { + let ty = check_same_ty(lhs, rhs)?; + check_non_empty!(lhs, rhs); + + match ty.info() { + IntTypeInfo::Signed(t_min, _) => { + // the only difference between wrapping_rem and strict_rem is + // the case t_min % -1 + + let strict = Self::strict_rem(lhs, rhs)?; + + let (l_min, _) = lhs.as_signed(); + let (r_min, r_max) = rhs.as_signed(); + + if l_min == t_min && r_min <= -1 && -1 <= r_max { + // t_min % -1 == 0 when wrapping + Ok(IInterval::single_signed(ty, 0).hull_unwrap(&strict)) + } else { + Ok(strict) + } + }, + // same as strict_div for unsigned types + IntTypeInfo::Unsigned(_) => Self::strict_rem(lhs, rhs), + } + } + + /// Modulo which panics on overflow and rhs == 0. + pub fn strict_rem_euclid(lhs: &IInterval, rhs: &IInterval) -> ArithResult { + let ty = check_same_ty(lhs, rhs)?; + check_non_empty!(lhs, rhs); + + match ty.info() { + IntTypeInfo::Signed(t_min, t_max) => { + debug_assert_eq!(t_min, -t_max - 1); + + let (mut r_min, mut r_max) = rhs.as_signed(); + + // Okay, so modulo is a pain to implement, because the + // operation works as follows: + // 1. If rhs == 0, panic. + // 2. If rhs == -1 and lhs == t_min, panic. + // 3. Return lhs mod abs(rhs) + // Note that abs(rhs) can overflow. + + let mut result = IInterval::empty(ty); + + // handle rhs == t_min separately + if r_min == t_min { + let min_range = split_by_sign_bit_signed(lhs, |min, max, sign| { + if sign == SignBit::Neg { + IInterval::new_signed(ty, min - t_min, max - t_min) + } else { + IInterval::new_signed(ty, min, max) + } + }); + + if r_max == t_min { + return Ok(min_range); + } + + result = min_range; + r_min += 1; + // this case is handled now, which means we can now safely + // compute -r_min and -r_max + } + + debug_assert!( + r_min <= r_max && r_max <= t_max, + "Invalid rhs: [{r_min}, {r_max}] for type {ty:?}" + ); + + // Very annoyingly, t_min (mod -1) panics. Since all operations + // only guarantee to result a superset, I will just ignore this + // panic and pretend that lhs (mod -1) == lhs (mod 1). + // With that out of the way, calculate abs(rhs) + if r_max < 0 { + (r_min, r_max) = (-r_max, -r_min); + } else if r_min < 0 { + (r_min, r_max) = (0, r_max.max(-r_min)); + } + + if r_min == 0 { + // rhs=0 always panics, so ignore it + if r_max == 0 { + debug_assert!(result.is_empty()); + return Ok(IInterval::empty(ty)); + } + r_min = 1; // to avoid division by zero + } + + debug_assert!( + 0 < r_min && r_min <= r_max && r_max <= t_max, + "Invalid rhs: [{r_min}, {r_max}] for type {ty:?}" + ); + + // now the general case, aka the bulk of the function + let general = split_by_sign_bit_signed(lhs, |l_min, l_max, sign| { + if r_min == r_max { + // if the rhs is a single value, this is possible + // if the lhs as more or equal values than the rhs, then the + // result is the trivial range [0, r - 1], which isn't + // interesting + if l_max - l_min < r_min { + let min = l_min.rem_euclid(r_min); + let max = l_max.rem_euclid(r_min); + if min <= max { + return IInterval::new_signed(ty, min, max); + } + } + return IInterval::new_signed(ty, 0, r_max - 1); + } + + if sign == SignBit::NonNeg && l_max < r_min { + // Since 0 <= lhs <= rhs, lhs (mod rhs) == lhs + return IInterval::new_signed(ty, l_min, l_max); + } + + if sign == SignBit::NonNeg { + return IInterval::new_signed(ty, 0, l_max.min(r_max - 1)); + } + + IInterval::new_signed(ty, 0, r_max - 1) + }); + + Ok(result.hull_unwrap(&general)) + }, + // same as strict_rem for unsigned types + IntTypeInfo::Unsigned(_) => Self::strict_rem(lhs, rhs), + } + } + /// Modulo which wrap on overflow and panics on rhs == 0. + pub fn wrapping_rem_euclid(lhs: &IInterval, rhs: &IInterval) -> ArithResult { + let ty = check_same_ty(lhs, rhs)?; + check_non_empty!(lhs, rhs); + + match ty.info() { + IntTypeInfo::Signed(t_min, _) => { + // the only difference between wrapping_rem and strict_rem is + // the case t_min % -1 + + let strict = Self::strict_rem_euclid(lhs, rhs)?; + + let (l_min, _) = lhs.as_signed(); + let (r_min, r_max) = rhs.as_signed(); + + if l_min == t_min && r_min <= -1 && -1 <= r_max { + // t_min % -1 == 0 when wrapping + Ok(IInterval::single_signed(ty, 0).hull_unwrap(&strict)) + } else { + Ok(strict) + } + }, + // same as strict_rem for unsigned types + IntTypeInfo::Unsigned(_) => Self::strict_rem(lhs, rhs), + } + } + + /// Midpoint. + pub fn midpoint(lhs: &IInterval, rhs: &IInterval) -> ArithResult { + let ty = check_same_ty(lhs, rhs)?; + check_non_empty!(lhs, rhs); + + match ty.info() { + IntTypeInfo::Signed(_, _) => { + let (l_min, l_max) = lhs.as_signed(); + let (r_min, r_max) = rhs.as_signed(); + + let min = l_min.midpoint(r_min); + let max = l_max.midpoint(r_max); + + Ok(IInterval::new_signed(ty, min, max)) + }, + IntTypeInfo::Unsigned(_) => { + let (l_min, l_max) = lhs.as_unsigned(); + let (r_min, r_max) = rhs.as_unsigned(); + + let min = l_min.midpoint(r_min); + let max = l_max.midpoint(r_max); + + Ok(IInterval::new_unsigned(ty, min, max)) + }, + } + } + + /// Integer square root, which panics for negative values. + pub fn isqrt(x: &IInterval) -> ArithResult { + check_non_empty!(x); + + let ty = x.ty; + + match ty.info() { + IntTypeInfo::Signed(_, _) => { + let (mut x_min, x_max) = x.as_signed(); + if x_max < 0 { + return Ok(IInterval::empty(ty)); + } + if x_min < 0 { + x_min = 0; // ignore negative values + } + + let min = x_min.isqrt(); + let max = x_max.isqrt(); + + Ok(IInterval::new_signed(ty, min, max)) + }, + IntTypeInfo::Unsigned(_) => { + let (x_min, x_max) = x.as_unsigned(); + + let min = x_min.isqrt(); + let max = x_max.isqrt(); + + Ok(IInterval::new_unsigned(ty, min, max)) + }, + } + } + + /// Power which saturates on overflow. + pub fn saturating_pow(lhs: &IInterval, rhs: &IInterval) -> ArithResult { + if rhs.ty != IntType::U32 { + return Err(ArithError::TypeError); + } + + let ty = lhs.ty; + check_non_empty!(lhs, rhs); + + let (r_min, r_max) = rhs.as_unsigned(); + let (r_min, r_max) = (r_min as u32, r_max as u32); + + match ty.info() { + IntTypeInfo::Signed(t_min, t_max) => { + let (l_min, l_max) = lhs.as_signed(); + let l_has_zero = l_min <= 0 && 0 <= l_max; + + let single_r = |r: u32| -> IInterval { + if r == 0 { + // x^0 == 1, so return [1, 1] + return IInterval::single_signed(ty, 1); + } + + if r % 2 == 0 { + // exponent is even + let pow_min = l_min.saturating_pow(r).min(t_max); + let pow_max = l_max.saturating_pow(r).min(t_max); + + let max = pow_min.max(pow_max); + let min = if l_has_zero { 0 } else { pow_min.min(pow_max) }; + + IInterval::new_signed(ty, min, max) + } else { + IInterval::new_signed( + ty, + l_min.saturating_pow(r).clamp(t_min, t_max), + l_max.saturating_pow(r).clamp(t_min, t_max), + ) + } + }; + + if r_min == r_max { + return Ok(single_r(r_min)); + } + + let mut result = single_r(r_min).hull_unwrap(&single_r(r_max)); + + if r_min + 1 < r_max && l_min < 0 { + result = result.hull_unwrap(&single_r(r_min + 1)); + result = result.hull_unwrap(&single_r(r_max - 1)); + } + + Ok(result) + }, + IntTypeInfo::Unsigned(t_max) => { + let (l_min, l_max) = lhs.as_unsigned(); + + if r_max == 0 { + // x^0 == 1, so return [1, 1] + return Ok(IInterval::single_unsigned(ty, 1)); + } + if l_max == 0 { + if r_min == 0 { + return Ok(IInterval::new_unsigned(ty, 0, 1)); + } else { + return Ok(IInterval::single_unsigned(ty, 0)); + } + } + + let min = if r_min == 0 && l_min == 0 { + 0 + } else { + l_min.saturating_pow(r_min).min(t_max) + }; + let max = l_max.saturating_pow(r_max).min(t_max); + + Ok(IInterval::new_unsigned(ty, min, max)) + }, + } + } + /// Power which panics on overflow. + pub fn strict_pow(lhs: &IInterval, rhs: &IInterval) -> ArithResult { + if rhs.ty != IntType::U32 { + return Err(ArithError::TypeError); + } + + let ty = lhs.ty; + check_non_empty!(lhs, rhs); + + let (r_min, r_max) = rhs.as_unsigned(); + let (mut r_min, r_max) = (r_min as u32, r_max as u32); + + match ty.info() { + IntTypeInfo::Signed(t_min, t_max) => { + let (l_min, l_max) = lhs.as_signed(); + + let single_r = |r: u32| -> IInterval { + if r == 0 { + // x^0 == 1, so return [1, 1] + return IInterval::single_signed(ty, 1); + } + + if r % 2 == 0 { + // exponent is even + let (min_pow, mut min_overflow) = l_min.overflowing_pow(r); + let (max_pow, mut max_overflow) = l_max.overflowing_pow(r); + min_overflow |= min_pow > t_max; + max_overflow |= max_pow > t_max; + + let has_zero = l_min <= 0 && 0 <= l_max; + if has_zero { + return if min_overflow || max_overflow { + IInterval::new_signed(ty, 0, t_max) + } else { + IInterval::new_signed(ty, 0, min_pow.max(max_pow)) + }; + } + + if min_overflow && max_overflow { + IInterval::empty(ty) + } else if min_overflow { + IInterval::new_signed(ty, max_pow, t_max) + } else if max_overflow { + IInterval::new_signed(ty, min_pow, t_max) + } else { + IInterval::new_signed(ty, min_pow.min(max_pow), max_pow.max(min_pow)) + } + } else { + // exponent is odd + let (min_pow, min_overflow) = l_min.overflowing_pow(r); + let (max_pow, max_overflow) = l_max.overflowing_pow(r); + + if l_min >= 0 { + return if min_overflow || min_pow > t_max { + IInterval::empty(ty) + } else if max_overflow || max_pow > t_max { + IInterval::new_signed(ty, min_pow, t_max) + } else { + IInterval::new_signed(ty, min_pow, max_pow) + }; + } + + if l_max <= 0 { + return if max_overflow || max_pow < t_min { + IInterval::empty(ty) + } else if min_overflow || min_pow < t_min { + IInterval::new_signed(ty, t_min, max_pow) + } else { + IInterval::new_signed(ty, min_pow, max_pow) + }; + } + + if min_overflow || min_pow < t_min { + return if max_overflow || max_pow > t_max { + IInterval::full(ty) + } else { + IInterval::new_signed(ty, t_min, max_pow) + }; + } + + if max_overflow || max_pow > t_max { + return IInterval::new_signed(ty, min_pow, t_max); + } + + IInterval::new_signed(ty, min_pow, max_pow) + } + }; + + let min_range = single_r(r_min); + if min_range.is_empty() || min_range.min == t_min && min_range.max == t_max { + // if the min range is empty, then the result is empty + // similarly, if the min range is the full range, + // then the result is the full range + return Ok(min_range); + } + + if r_min == r_max { + return Ok(min_range); + } + + let mut result = min_range.hull_unwrap(&single_r(r_min + 1)); + drop(min_range); + + if result.max == t_max && (result.min == t_min || l_min >= 0) { + // the result won't change anymore + return Ok(result); + } + + if r_min + 1 == r_max { + return Ok(result); + } + + // find actual max + if result.max != t_max && l_max >= 0 { + if let Some(max_pow) = l_max.checked_pow(r_max).filter(|i| i <= &t_max) { + result.max = result.max.max(max_pow); + } else { + result.max = t_max; + } + } + if result.max != t_max && l_min < 0 { + // select the even integer in [r_max - 1, r_max] + let r_even = r_max & !1; + if let Some(min_pow) = l_min.checked_pow(r_even).filter(|i| i <= &t_max) { + result.max = result.max.max(min_pow); + } else { + result.max = t_max; + } + } + + // find actual min + if result.min != t_min && l_min < 0 { + // select the odd integer in [r_max - 1, r_max] + let r_odd = (r_max - 1) | 1; + if let Some(min_pow) = l_min.checked_pow(r_odd).filter(|i| i >= &t_min) { + result.min = result.min.min(min_pow); + } else { + result.min = t_min; + } + } + + Ok(result) + }, + IntTypeInfo::Unsigned(t_max) => { + let (l_min, l_max) = lhs.as_unsigned(); + + if r_max == 0 { + // x^0 == 1, so return [1, 1] + return Ok(IInterval::single_unsigned(ty, 1)); + } + + let r_zero = if r_min == 0 { + r_min = 1; + // x^0 == 1, so return [1, 1] + IInterval::single_unsigned(ty, 1) + } else { + IInterval::empty(ty) + }; + + let (min, min_overflow) = l_min.overflowing_pow(r_min); + if min_overflow || min > t_max { + // it will always overflow + return Ok(IInterval::empty(ty)); + } + let max = l_max.saturating_pow(r_max).min(t_max); + + Ok(IInterval::new_unsigned(ty, min, max).hull_unwrap(&r_zero)) + }, + } + } + /// Pow which wraps on overflow. + pub fn wrapping_pow(lhs: &IInterval, rhs: &IInterval) -> ArithResult { + if rhs.ty != IntType::U32 { + return Err(ArithError::TypeError); + } + + let ty = lhs.ty; + check_non_empty!(lhs, rhs); + + let (r_min, r_max) = rhs.as_unsigned(); + let (_, r_max) = (r_min as u32, r_max as u32); + + match ty.info() { + IntTypeInfo::Signed(t_min, t_max) => { + let (l_min, l_max) = lhs.as_signed(); + + let overflow = l_max.checked_pow(r_max).is_none_or(|i| i < t_min || i > t_max) + || l_min.checked_pow(r_max).is_none_or(|i| i < t_min || i > t_max); + + if overflow { + // it will overflow, so idk what to return + return Ok(IInterval::full(ty)); + } + + Self::strict_pow(lhs, rhs) + }, + IntTypeInfo::Unsigned(t_max) => { + let (_, l_max) = lhs.as_unsigned(); + + let (pow, pow_overflow) = l_max.overflowing_pow(r_max); + if pow_overflow || pow > t_max { + // it's hard to know the true range when overflow happens + return Ok(IInterval::full(ty)); + } + + Self::strict_pow(lhs, rhs) + }, + } + } + + /// Absolute value which saturates on overflow. + pub fn saturating_abs(x: &IInterval) -> ArithResult { + check_non_empty!(x); + + match x.ty.info() { + IntTypeInfo::Signed(t_min, t_max) => { + debug_assert_eq!(t_min, -t_max - 1); + + let (x_min, x_max) = x.as_signed(); + + if x_max <= 0 { + // already negative, so return the positive range + Ok(IInterval::new_signed( + x.ty, + x_max.saturating_neg().min(t_max), + x_min.saturating_neg().min(t_max), + )) + } else if x_min >= 0 { + // already positive + Ok(x.clone()) + } else { + // contains zero, so return the positive range + Ok(IInterval::new_signed( + x.ty, + 0, + x_max.max(x_min.saturating_neg()).min(t_max), + )) + } + }, + IntTypeInfo::Unsigned(_) => Err(ArithError::Unsupported), + } + } + /// Absolute value which panics on overflow. + pub fn strict_abs(x: &IInterval) -> ArithResult { + check_non_empty!(x); + + match x.ty.info() { + IntTypeInfo::Signed(t_min, t_max) => { + debug_assert_eq!(t_min, -t_max - 1); + + let (mut x_min, x_max) = x.as_signed(); + + if x_min >= 0 { + // already positive + Ok(x.clone()) + } else if x_max == t_min { + // all values in the range will overflow + Ok(IInterval::empty(x.ty)) + } else { + if x_min == t_min { + x_min += 1; // ignore value that will overflow + } + + if x_max <= 0 { + // already negative, so return the positive range + Ok(IInterval::new_signed(x.ty, -x_max, -x_min)) + } else { + Ok(IInterval::new_signed(x.ty, 0, x_max.max(-x_min))) + } + } + }, + IntTypeInfo::Unsigned(_) => Err(ArithError::Unsupported), + } + } + /// Absolute value which wraps on overflow. + pub fn wrapping_abs(x: &IInterval) -> ArithResult { + check_non_empty!(x); + + match x.ty.info() { + IntTypeInfo::Signed(t_min, t_max) => { + debug_assert_eq!(t_min, -t_max - 1); + + // This is the same strict_abs, but with different handling of + // the case where x_min == t_min. + + let strict = Self::strict_abs(x)?; + + let (x_min, _) = x.as_signed(); + + if x_min == t_min { + let min_range = IInterval::single_signed(x.ty, t_min); + + Ok(strict.hull_unwrap(&min_range)) + } else { + Ok(strict) + } + }, + IntTypeInfo::Unsigned(_) => Err(ArithError::Unsupported), + } + } + /// Absolute value which never overflows, because it returns an unsigned value. + pub fn unsigned_abs(x: &IInterval) -> ArithResult { + match x.ty.info() { + IntTypeInfo::Signed(t_min, t_max) => { + debug_assert_eq!(t_min, -t_max - 1); + + if x.is_empty() { + return Ok(IInterval::empty(x.ty.swap_signedness())); + } + + // This is the same strict_abs, but with different handling of + // the case where x_min == t_min. + + let strict = Self::strict_abs(x)?.cast_signed_to_unsigned(); + + let (x_min, _) = x.as_signed(); + + if x_min == t_min { + let would_overflow = IInterval::single_unsigned(x.ty.swap_signedness(), t_max as u128 + 1); + + Ok(strict.hull_unwrap(&would_overflow)) + } else { + Ok(strict) + } + }, + IntTypeInfo::Unsigned(_) => Err(ArithError::Unsupported), + } + } + + /// Minimum. + pub fn min(lhs: &IInterval, rhs: &IInterval) -> ArithResult { + let ty = check_same_ty(lhs, rhs)?; + check_non_empty!(lhs, rhs); + + match ty.info() { + IntTypeInfo::Signed(_, _) => { + let (l_min, l_max) = lhs.as_signed(); + let (r_min, r_max) = rhs.as_signed(); + + let min = l_min.min(r_min); + let max = l_max.min(r_max); + + Ok(IInterval::new_signed(ty, min, max)) + }, + IntTypeInfo::Unsigned(_) => { + let (l_min, l_max) = lhs.as_unsigned(); + let (r_min, r_max) = rhs.as_unsigned(); + + let min = l_min.min(r_min); + let max = l_max.min(r_max); + + Ok(IInterval::new_unsigned(ty, min, max)) + }, + } + } + /// Maximum. + pub fn max(lhs: &IInterval, rhs: &IInterval) -> ArithResult { + let ty = check_same_ty(lhs, rhs)?; + check_non_empty!(lhs, rhs); + + match ty.info() { + IntTypeInfo::Signed(_, _) => { + let (l_min, l_max) = lhs.as_signed(); + let (r_min, r_max) = rhs.as_signed(); + + let min = l_min.max(r_min); + let max = l_max.max(r_max); + + Ok(IInterval::new_signed(ty, min, max)) + }, + IntTypeInfo::Unsigned(_) => { + let (l_min, l_max) = lhs.as_unsigned(); + let (r_min, r_max) = rhs.as_unsigned(); + + let min = l_min.max(r_min); + let max = l_max.max(r_max); + + Ok(IInterval::new_unsigned(ty, min, max)) + }, + } + } + + /// Bitwise AND. + pub fn and(lhs: &IInterval, rhs: &IInterval) -> ArithResult { + let ty = check_same_ty(lhs, rhs)?; + check_non_empty!(lhs, rhs); + + fn and(lhs: &IInterval, rhs: &IInterval) -> IInterval { + debug_assert_eq!(lhs.ty, rhs.ty); + debug_assert!(!lhs.is_empty() && !rhs.is_empty()); + + let l_bits = Bits::from_non_empty(lhs); + let r_bits = Bits::from_non_empty(rhs); + + let zero = l_bits.zero & r_bits.zero; + let one = l_bits.one & r_bits.one; + + Bits::new(zero, one).to_interval(lhs.ty) + } + + if ty.is_signed() { + let (l_min, l_max) = lhs.as_signed(); + let (r_min, r_max) = rhs.as_signed(); + let l_neg = l_max < 0; + let r_neg = r_max < 0; + if l_min < 0 && r_min < 0 { + // Okay, so the problem here is that the `and` implementation is + // only correct if both lhs and rhs have equal sign bits. So the + // idea here is to split the ranges into negative and + // non-negative parts, compute the `and` for each part separately, + // and then combine the results. + if !l_neg { + let l_n = IInterval::new_signed(ty, l_min, -1); + let l_p = IInterval::new_signed(ty, 0, l_max); + + let result = if r_neg { + and(&l_n, rhs).hull_unwrap(&and(&l_p, rhs)) + } else { + let r_n = IInterval::new_signed(ty, r_min, -1); + let r_p = IInterval::new_signed(ty, 0, r_max); + and(&l_n, &r_n) + .hull_unwrap(&and(&l_n, &r_p)) + .hull_unwrap(&and(&l_p, &r_n)) + .hull_unwrap(&and(&l_p, &r_p)) + }; + return Ok(result); + } + + if !r_neg { + let r_n = IInterval::new_signed(ty, r_min, -1); + let r_p = IInterval::new_signed(ty, 0, r_max); + + return Ok(and(lhs, &r_n).hull_unwrap(&and(lhs, &r_p))); + } + } + Ok(and(lhs, rhs)) + } else { + Ok(and(lhs, rhs)) + } + } + /// Bitwise OR. + pub fn or(lhs: &IInterval, rhs: &IInterval) -> ArithResult { + let ty = check_same_ty(lhs, rhs)?; + check_non_empty!(lhs, rhs); + + let l_bits = Bits::from_non_empty(lhs); + let r_bits = Bits::from_non_empty(rhs); + + let zero = l_bits.zero | r_bits.zero; + let one = l_bits.one | r_bits.one; + + Ok(Bits::new(zero, one).to_interval(ty)) + } + /// Bitwise XOR. + pub fn xor(lhs: &IInterval, rhs: &IInterval) -> ArithResult { + let ty = check_same_ty(lhs, rhs)?; + check_non_empty!(lhs, rhs); + + let l_bits = Bits::from_non_empty(lhs); + let r_bits = Bits::from_non_empty(rhs); + + // bits that are different in lhs and rhs + let l_diff = l_bits.zero ^ l_bits.one; + let r_diff = r_bits.zero ^ r_bits.one; + let diff = l_diff | r_diff; + + let xor = l_bits.zero ^ r_bits.zero; + let zero = xor & !diff; + let one = xor | diff; + + Ok(Bits::new(zero, one).to_interval(ty)) + } + /// Bitwise NOT. + pub fn not(x: &IInterval) -> ArithResult { + check_non_empty!(x); + + let ty = x.ty; + + match ty.info() { + IntTypeInfo::Signed(_, _) => { + let (x_min, x_max) = x.as_signed(); + + // maybe the only operation where signed is simpler than unsigned + Ok(IInterval::new_signed(ty, !x_max, !x_min)) + }, + IntTypeInfo::Unsigned(t_max) => { + let (x_min, x_max) = x.as_unsigned(); + + Ok(IInterval::new_unsigned(ty, !x_max & t_max, !x_min & t_max)) + }, + } + } + + pub fn strict_shl(lhs: &IInterval, rhs: &IInterval) -> ArithResult { + if rhs.ty != IntType::U32 { + return Err(ArithError::TypeError); + } + + check_non_empty!(lhs, rhs); + + let ty = lhs.ty; + let bit_width: u32 = ty.bits() as u32; + + let (r_min, r_max) = rhs.as_unsigned(); + let r_min = r_min as u32; + let r_max = (r_max as u32).min(bit_width - 1); + if r_min >= bit_width { + return Ok(IInterval::empty(ty)); + } + + let mask = !u128::MAX.unbounded_shl(bit_width); + + let mut bits = Bits::from_non_empty(lhs); + bits.zero = (bits.zero << r_min) & mask; + bits.one = (bits.one << r_min) & mask; + + let mut result = bits.to_interval(ty); + for _ in r_min..r_max { + bits.zero = (bits.zero << 1) & mask; + bits.one = (bits.one << 1) & mask; + result = result.hull_unwrap(&bits.to_interval(ty)); + } + + Ok(result) + } + pub fn wrapping_shl(lhs: &IInterval, rhs: &IInterval) -> ArithResult { + if rhs.ty != IntType::U32 { + return Err(ArithError::TypeError); + } + + check_non_empty!(lhs, rhs); + + let ty = lhs.ty; + let bit_width = ty.bits() as u32; + + let (r_min, r_max) = rhs.as_unsigned(); + let mut r_min = r_min as u32; + let mut r_max = r_max as u32; + + if r_max - r_min >= bit_width - 1 { + r_min = 0; + r_max = bit_width - 1; + } else { + r_min %= bit_width; + r_max %= bit_width; + } + + let result = if r_min <= r_max { + Self::strict_shl( + lhs, + &IInterval::new_unsigned(IntType::U32, r_min as u128, r_max as u128), + )? + } else { + let left = Self::strict_shl(lhs, &IInterval::new_unsigned(IntType::U32, 0, r_max as u128))?; + let right = Self::strict_shl( + lhs, + &IInterval::new_unsigned(IntType::U32, r_min as u128, (bit_width - 1) as u128), + )?; + + left.hull_unwrap(&right) + }; + + Ok(result) + } + pub fn unbounded_shl(lhs: &IInterval, rhs: &IInterval) -> ArithResult { + if rhs.ty != IntType::U32 { + return Err(ArithError::TypeError); + } + + check_non_empty!(lhs, rhs); + + let mut result = Self::strict_shl(lhs, rhs)?; + + let ty = lhs.ty; + + let (_, r_max) = rhs.as_unsigned(); + if r_max as u32 >= ty.bits() as u32 { + let zero = if ty.is_signed() { + IInterval::single_signed(ty, 0) + } else { + IInterval::single_unsigned(ty, 0) + }; + result = result.hull_unwrap(&zero); + } + + Ok(result) + } + + pub fn strict_shr(lhs: &IInterval, rhs: &IInterval) -> ArithResult { + if rhs.ty != IntType::U32 { + return Err(ArithError::TypeError); + } + + check_non_empty!(lhs, rhs); + + let ty = lhs.ty; + let bit_width: u32 = ty.bits() as u32; + + let (r_min, r_max) = rhs.as_unsigned(); + let r_min = r_min as u32; + let r_max = (r_max as u32).min(bit_width - 1); + if r_min >= bit_width { + return Ok(IInterval::empty(ty)); + } + + if ty.is_signed() { + Ok(split_by_sign_bit_signed(lhs, |min, max, sign| { + if sign == SignBit::NonNeg { + IInterval::new_signed(ty, min >> r_max, max >> r_min) + } else { + IInterval::new_signed(ty, min >> r_min, max >> r_max) + } + })) + } else { + let (l_min, l_max) = lhs.as_unsigned(); + + Ok(IInterval::new_unsigned(ty, l_min >> r_max, l_max >> r_min)) + } + } + pub fn wrapping_shr(lhs: &IInterval, rhs: &IInterval) -> ArithResult { + if rhs.ty != IntType::U32 { + return Err(ArithError::TypeError); + } + + check_non_empty!(lhs, rhs); + + let ty = lhs.ty; + let bit_width = ty.bits() as u32; + + let (r_min, r_max) = rhs.as_unsigned(); + let mut r_min = r_min as u32; + let mut r_max = r_max as u32; + + if r_max - r_min >= bit_width - 1 { + r_min = 0; + r_max = bit_width - 1; + } else { + r_min %= bit_width; + r_max %= bit_width; + } + + if r_min <= r_max { + Self::strict_shr( + lhs, + &IInterval::new_unsigned(IntType::U32, r_min as u128, r_max as u128), + ) + } else { + Self::strict_shr( + lhs, + &IInterval::new_unsigned(IntType::U32, 0, (bit_width - 1) as u128), + ) + } + } + pub fn unbounded_shr(lhs: &IInterval, rhs: &IInterval) -> ArithResult { + if rhs.ty != IntType::U32 { + return Err(ArithError::TypeError); + } + + check_non_empty!(lhs, rhs); + + let mut result = Self::strict_shr(lhs, rhs)?; + + let ty = lhs.ty; + + let (_, r_max) = rhs.as_unsigned(); + if r_max as u32 >= ty.bits() as u32 { + let zero = if ty.is_signed() { + let has_neg = lhs.min < 0; + let has_pos = lhs.max >= 0; + if has_neg { + if has_pos { + IInterval::new_signed(ty, -1, 0) + } else { + IInterval::single_signed(ty, -1) + } + } else { + IInterval::single_signed(ty, 0) + } + } else { + IInterval::single_unsigned(ty, 0) + }; + result = result.hull_unwrap(&zero); + } + + Ok(result) + } + + pub fn leading_zeros(x: &IInterval) -> ArithResult { + if x.is_empty() { + return Ok(IInterval::empty(IntType::U32)); + } + + let bit_width = x.ty.bits() as u32; + let padding = 128 - bit_width; + + Ok(split_by_sign_bit(x, |min, max| { + let r_min = max.leading_zeros().saturating_sub(padding); + let r_max = min.leading_zeros().saturating_sub(padding); + + IInterval::new_unsigned(IntType::U32, r_min as u128, r_max as u128) + })) + } + pub fn leading_ones(x: &IInterval) -> ArithResult { + Self::leading_zeros(&Self::not(x)?) + } + pub fn trailing_zeros(x: &IInterval) -> ArithResult { + if x.is_empty() { + return Ok(IInterval::empty(IntType::U32)); + } + + let bit_width = x.ty.bits() as u32; + + Ok(split_by_sign_bit(x, |min, max| { + if min == max { + let trailing = min.trailing_zeros().min(bit_width); + return IInterval::single_unsigned(IntType::U32, trailing as u128); + } + + // if min != max, then the range contains at least one odd value, + // so the minimum trailing zeros is 0 + + if min == 0 { + // 0 is all 0s + return IInterval::new_unsigned(IntType::U32, 0, bit_width as u128); + } + + let mut a = min; + let mut b = max & !1; // make sure max isn't u128::MAX + + let mut scale: u32 = 0; + while a != b { + scale += 1; + a = (a + 1) >> 1; + b >>= 1; + } + + let most_even = a << scale; + + let r_max = most_even.trailing_zeros(); + + IInterval::new_unsigned(IntType::U32, 0, r_max as u128) + })) + } + pub fn trailing_ones(x: &IInterval) -> ArithResult { + Self::trailing_zeros(&Self::not(x)?) + } + pub fn count_ones(x: &IInterval) -> ArithResult { + if x.is_empty() { + return Ok(IInterval::empty(IntType::U32)); + } + + let bit_width = x.ty.bits() as u32; + + Ok(split_by_sign_bit(x, |min, max| { + let equal_prefix = (min ^ max).leading_zeros(); + let mut spread = 128 - equal_prefix; + + let mask = u128::MAX.unbounded_shl(bit_width); + let fixed_ones = (min & !mask).unbounded_shr(spread).count_ones(); + + let r_min = if min == 0 { 0 } else { 1 }; + + if max | u128::MAX.unbounded_shl(spread) != u128::MAX { + spread -= 1; + } + + IInterval::new_unsigned( + IntType::U32, + fixed_ones.min(bit_width).max(r_min) as u128, + (fixed_ones + spread).min(bit_width) as u128, + ) + })) + } + pub fn count_zeros(x: &IInterval) -> ArithResult { + Self::count_ones(&Self::not(x)?) + } + + /// Casts unsigned to signed. + pub fn cast_signed(x: &IInterval) -> ArithResult { + if x.ty.is_signed() { + return Err(ArithError::Unsupported); + } + + Ok(x.cast_unsigned_to_signed()) + } + /// Casts signed to unsigned. + pub fn cast_unsigned(x: &IInterval) -> ArithResult { + if !x.ty.is_signed() { + return Err(ArithError::Unsupported); + } + + Ok(x.cast_signed_to_unsigned()) + } + + pub fn cast_as(x: &IInterval, target: IntType) -> ArithResult { + if x.ty == target { + return Ok(x.clone()); + } + if x.is_empty() { + return Ok(IInterval::empty(target)); + } + + let src_width = x.ty.bits(); + let target_width = target.bits(); + let src_signed = x.ty.is_signed(); + let target_signed = target.is_signed(); + + let target_same_sign = if src_signed != target_signed { + target.swap_signedness() + } else { + target + }; + + let src: IInterval = if src_width < target_width { + // widening cast + x.cast_widen(target_same_sign) + } else if src_width > target_width { + // narrowing cast + match target_same_sign.info() { + IntTypeInfo::Signed(t_min, t_max) => { + let mask = (t_max.cast_unsigned() << 1) | 1; + split_by_sign_bit(x, |min, max| { + if max - min >= mask { + IInterval::new_signed(target_same_sign, t_min, t_max) + } else { + let min = min & mask; + let max = max & mask; + if min > max { + IInterval::new_signed(target_same_sign, t_min, t_max) + } else { + let unsigned = target_same_sign.swap_signedness(); + IInterval::new_unsigned(unsigned, min, max).cast_unsigned_to_signed() + } + } + }) + }, + IntTypeInfo::Unsigned(t_max) => { + let (s_min, s_max) = x.as_unsigned(); + + if s_max - s_min >= t_max { + IInterval::new_unsigned(target_same_sign, 0, t_max) + } else { + let min = s_min & t_max; + let max = s_max & t_max; + if min > max { + IInterval::new_unsigned(target_same_sign, 0, t_max) + } else { + IInterval::new_unsigned(target_same_sign, min, max) + } + } + }, + } + } else { + // only signedness cast + x.clone() + }; + + // cast to target signedness + let result = if src_signed != target_signed { + if target_signed { + src.cast_unsigned_to_signed() + } else { + src.cast_signed_to_unsigned() + } + } else { + src + }; + + Ok(result) + } +} diff --git a/clippy_utils/src/rinterval/bits.rs b/clippy_utils/src/rinterval/bits.rs new file mode 100644 index 000000000000..2294dd85edb9 --- /dev/null +++ b/clippy_utils/src/rinterval/bits.rs @@ -0,0 +1,159 @@ +use super::{IInterval, IntType}; + +/// A representation of the equal bits of an integer interval. +/// +/// This struct has 2 main fields: `zero` and `one`. They both represent the +/// equal bits, but they handle unequal bits differently. Unequal bits are +/// represented as `0` in `zero` and `1` in `one`. +/// +/// So e.g. if there is only one value in the interval, then all bits are +/// equal and `zero` and `one` will be equal. Similarly, if the interval +/// contains all values of the type, then `zero` will be all 0s and `one` +/// will be all 1s since all bits are different. +#[derive(Clone, Debug, Eq, PartialEq)] +#[must_use] +pub(crate) struct Bits { + pub zero: u128, + pub one: u128, +} +impl Bits { + pub const fn new(zero: u128, one: u128) -> Self { + debug_assert!(one & zero == zero); + debug_assert!(one | zero == one); + + Self { zero, one } + } + pub const fn from_non_empty(i: &IInterval) -> Self { + debug_assert!(!i.is_empty()); + + let min = i.min.cast_unsigned(); + let max = i.max.cast_unsigned(); + + // all bits that are the same will be 0 in this mask + let equal_bits = min ^ max; + + // number of buts that are the same + let equal = equal_bits.leading_zeros(); + + // mask for all unequal bits + let unequal_mask = u128::MAX.unbounded_shr(equal); + + let zero = min & !unequal_mask; + let one = max | unequal_mask; + + Self::new(zero, one) + } + + pub const fn to_interval(&self, ty: IntType) -> IInterval { + if ty.is_signed() { + let u_ty = ty.swap_signedness(); + let u_max = u_ty.max_value().cast_unsigned(); + IInterval::new_unsigned(u_ty, self.zero & u_max, self.one & u_max).cast_unsigned_to_signed() + } else { + #[cfg(debug_assertions)] + { + let u_max = ty.max_value().cast_unsigned(); + debug_assert!(self.zero <= u_max); + debug_assert!(self.one <= u_max); + } + + IInterval::new_unsigned(ty, self.zero, self.one) + } + } +} +impl std::fmt::Display for Bits { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "IntBits[")?; + + let mut zero = self.zero.reverse_bits(); + let mut one = self.one.reverse_bits(); + + for chunk_32 in 0..4 { + if chunk_32 > 0 { + write!(f, " ")?; + } + + let z_32 = zero as u32; + let o_32 = one as u32; + if z_32 == o_32 { + if z_32 == 0 { + write!(f, "0_x32")?; + continue; + } else if z_32 == u32::MAX { + write!(f, "1_x32")?; + continue; + } + } + if z_32 == !o_32 { + write!(f, "?_x32")?; + continue; + } + + for chunk_4 in 0..8 { + if chunk_4 > 0 { + write!(f, "_")?; + } + + for _ in 0..4 { + let z = zero & 1; + let o = one & 1; + + if z == o { + write!(f, "{}", z as u8)?; + } else { + write!(f, "?")?; + } + + zero >>= 1; + one >>= 1; + } + } + } + + write!(f, "]") + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_exact_bits_for_single_values() { + fn test(i: IInterval) { + let bits = Bits::from_non_empty(&i); + let back = bits.to_interval(i.ty); + assert_eq!(i, back); + } + + for x in i8::MIN..=i8::MAX { + test(IInterval::single_signed(IntType::I8, x as i128)); + } + for x in u8::MIN..=u8::MAX { + test(IInterval::single_unsigned(IntType::U8, x as u128)); + } + } + + #[test] + fn test_superset_for_ranges() { + fn test(i: IInterval) { + let bits = Bits::from_non_empty(&i); + let back = bits.to_interval(i.ty); + assert!( + back.is_superset_of(&i), + "Expected {back} to be a superset of {i} for bits {bits}" + ); + } + + for min in i8::MIN..i8::MAX { + for max in min + 1..=i8::MAX { + test(IInterval::new_signed(IntType::I8, min as i128, max as i128)); + } + } + for min in u8::MIN..u8::MAX { + for max in min + 1..=u8::MAX { + test(IInterval::new_unsigned(IntType::U8, min as u128, max as u128)); + } + } + } +} diff --git a/clippy_utils/src/rinterval/iinterval.rs b/clippy_utils/src/rinterval/iinterval.rs new file mode 100644 index 000000000000..10907208032c --- /dev/null +++ b/clippy_utils/src/rinterval/iinterval.rs @@ -0,0 +1,389 @@ +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[repr(i16)] +#[must_use] +pub enum IntType { + U8 = 8, + U16 = 16, + U32 = 32, + U64 = 64, + U128 = 128, + I8 = -8, + I16 = -16, + I32 = -32, + I64 = -64, + I128 = -128, +} +impl IntType { + pub const fn is_signed(self) -> bool { + (self as i16) < 0 + } + pub const fn bits(self) -> u8 { + (self as i16).unsigned_abs() as u8 + } + pub const fn min_value(self) -> i128 { + match self { + IntType::U8 => 0, + IntType::U16 => 0, + IntType::U32 => 0, + IntType::U64 => 0, + IntType::U128 => 0, + IntType::I8 => i8::MIN as i128, + IntType::I16 => i16::MIN as i128, + IntType::I32 => i32::MIN as i128, + IntType::I64 => i64::MIN as i128, + IntType::I128 => i128::MIN, + } + } + pub const fn max_value(self) -> i128 { + match self { + IntType::U8 => u8::MAX as i128, + IntType::U16 => u16::MAX as i128, + IntType::U32 => u32::MAX as i128, + IntType::U64 => u64::MAX as i128, + IntType::U128 => u128::MAX as i128, + IntType::I8 => i8::MAX as i128, + IntType::I16 => i16::MAX as i128, + IntType::I32 => i32::MAX as i128, + IntType::I64 => i64::MAX as i128, + IntType::I128 => i128::MAX, + } + } + pub(crate) const fn info(self) -> IntTypeInfo { + match self { + IntType::U8 => IntTypeInfo::Unsigned(u8::MAX as u128), + IntType::U16 => IntTypeInfo::Unsigned(u16::MAX as u128), + IntType::U32 => IntTypeInfo::Unsigned(u32::MAX as u128), + IntType::U64 => IntTypeInfo::Unsigned(u64::MAX as u128), + IntType::U128 => IntTypeInfo::Unsigned(u128::MAX), + IntType::I8 => IntTypeInfo::Signed(i8::MIN as i128, i8::MAX as i128), + IntType::I16 => IntTypeInfo::Signed(i16::MIN as i128, i16::MAX as i128), + IntType::I32 => IntTypeInfo::Signed(i32::MIN as i128, i32::MAX as i128), + IntType::I64 => IntTypeInfo::Signed(i64::MIN as i128, i64::MAX as i128), + IntType::I128 => IntTypeInfo::Signed(i128::MIN, i128::MAX), + } + } + + pub const fn swap_signedness(self) -> IntType { + match self { + IntType::U8 => IntType::I8, + IntType::U16 => IntType::I16, + IntType::U32 => IntType::I32, + IntType::U64 => IntType::I64, + IntType::U128 => IntType::I128, + IntType::I8 => IntType::U8, + IntType::I16 => IntType::U16, + IntType::I32 => IntType::U32, + IntType::I64 => IntType::U64, + IntType::I128 => IntType::U128, + } + } +} + +pub(crate) enum IntTypeInfo { + Signed(i128, i128), + Unsigned(u128), +} + +/// Represents a range of values for an integer type. +/// +/// ## Exactness +/// +/// Ranges must generally be assumed to be **inexact**. It is simply not +/// possible to accurately represent the set of all possible values of an +/// integer expression using only its minimum and maximum values. +/// +/// As such, this type represents a **subset** of the actual set of values of +/// an expression. + +#[derive(Clone, Debug, Eq, PartialEq)] +#[must_use] +pub struct IInterval { + pub ty: IntType, + pub min: i128, + pub max: i128, +} + +impl IInterval { + pub const fn new_signed(ty: IntType, min: i128, max: i128) -> Self { + #[cfg(debug_assertions)] + { + debug_assert!(min <= max); + debug_assert!(ty.is_signed()); + if let IntTypeInfo::Signed(t_min, t_max) = ty.info() { + debug_assert!(min >= t_min); + debug_assert!(max <= t_max); + } + } + + Self { ty, min, max } + } + pub const fn new_unsigned(ty: IntType, min: u128, max: u128) -> Self { + #[cfg(debug_assertions)] + { + debug_assert!(min <= max); + debug_assert!(!ty.is_signed()); + if let IntTypeInfo::Unsigned(t_max) = ty.info() { + debug_assert!(max <= t_max); + } + } + + Self { + ty, + min: min.cast_signed(), + max: max.cast_signed(), + } + } + pub const fn single_signed(ty: IntType, value: i128) -> Self { + Self::new_signed(ty, value, value) + } + pub const fn single_unsigned(ty: IntType, value: u128) -> Self { + Self::new_unsigned(ty, value, value) + } + + /// Creates an empty interval for the given integer type. + pub const fn empty(ty: IntType) -> Self { + Self { ty, min: 1, max: 0 } + } + /// Creates the smallest interval that contains all possible values of the + /// given integer type. + pub const fn full(ty: IntType) -> Self { + match ty.info() { + IntTypeInfo::Signed(min, max) => Self::new_signed(ty, min, max), + IntTypeInfo::Unsigned(max) => Self::new_unsigned(ty, 0, max), + } + } + + /// Whether the interval contains no values. + pub const fn is_empty(&self) -> bool { + if self.ty.is_signed() { + let min = self.min; + let max = self.max; + min > max + } else { + let min = self.min.cast_unsigned(); + let max = self.max.cast_unsigned(); + min > max + } + } + pub fn is_full(&self) -> bool { + self == &Self::full(self.ty) + } + + /// Returns whether the interval contains at least one negative value. + pub fn contains_negative(&self) -> bool { + if self.is_empty() || !self.ty.is_signed() { + false + } else { + let (min, _) = self.as_signed(); + min < 0 + } + } + /// Returns whether all values in the interval can be represented by the + /// given target type. + pub fn fits_into(&self, target: IntType) -> bool { + if self.is_empty() || self.ty == target { + return true; // empty set fits into any type, and same type always fits + } + + match target.info() { + IntTypeInfo::Signed(t_min, t_max) => { + if self.ty.is_signed() { + let (min, max) = self.as_signed(); + t_min <= min && max <= t_max + } else { + let (_, max) = self.as_unsigned(); + max <= t_max.cast_unsigned() + } + }, + IntTypeInfo::Unsigned(t_max) => { + if self.ty.is_signed() { + let (min, max) = self.as_signed(); + min >= 0 && max.cast_unsigned() <= t_max + } else { + let (_, max) = self.as_unsigned(); + max <= t_max + } + }, + } + } + + /// Returns the minimum and maximum values for intervals of signed types. + /// + /// If the interval is empty or the type is unsigned, the result is + /// implementation-defined. + pub const fn as_signed(&self) -> (i128, i128) { + debug_assert!(self.ty.is_signed()); + debug_assert!(!self.is_empty()); + (self.min, self.max) + } + /// Returns the minimum and maximum values for intervals of unsigned types. + /// + /// If the interval is empty or the type is signed, the result is unspecified. + pub const fn as_unsigned(&self) -> (u128, u128) { + debug_assert!(!self.ty.is_signed()); + debug_assert!(!self.is_empty()); + (self.min.cast_unsigned(), self.max.cast_unsigned()) + } + + /// Returns the smallest interval that contains both `self` and `other`. + /// + /// The result is unspecified if the two intervals have different types. + pub fn hull_unwrap(&self, other: &Self) -> Self { + debug_assert!(self.ty == other.ty); + + if self.is_empty() { + return other.clone(); + } + if other.is_empty() { + return self.clone(); + } + + if self.ty.is_signed() { + let min = self.min.min(other.min); + let max = self.max.max(other.max); + Self::new_signed(self.ty, min, max) + } else { + let (l_min, l_max) = self.as_unsigned(); + let (r_min, r_max) = other.as_unsigned(); + + let min = l_min.min(r_min); + let max = l_max.max(r_max); + Self::new_unsigned(self.ty, min, max) + } + } + /// Returns the smallest interval that contains both `self` and `other`. + /// + /// Returns `None` if the two intervals have different types. + pub fn hull(&self, other: &Self) -> Option { + if self.ty != other.ty { + return None; + } + Some(self.hull_unwrap(other)) + } + + /// Returns whether all values in `self` are also contained in `other`. + /// + /// The result is unspecified if the two intervals have types of different + /// signedness. + pub fn is_subset_of(&self, other: &Self) -> bool { + debug_assert!(self.ty.is_signed() == other.ty.is_signed()); + + if self.is_empty() { + return true; // Empty set is a subset of any set + } + if other.is_empty() { + return false; // Non-empty set cannot be a subset of an empty set + } + + if self.ty.is_signed() { + self.min >= other.min && self.max <= other.max + } else { + let (l_min, l_max) = self.as_unsigned(); + let (r_min, r_max) = other.as_unsigned(); + l_min >= r_min && l_max <= r_max + } + } + /// Same as `is_subset_of`, but checks if `self` is a superset of `other`. + pub fn is_superset_of(&self, other: &Self) -> bool { + other.is_subset_of(self) + } + + pub fn to_string_untyped(&self) -> String { + if self.is_empty() { + "".to_string() + } else if self.ty.is_signed() { + let (min, max) = self.as_signed(); + if min == max { + format!("{min}") + } else { + format!("{min}..={max}") + } + } else { + let (min, max) = self.as_unsigned(); + if min == max { + format!("{min}") + } else { + format!("{min}..={max}") + } + } + } + + /// Casts an unsigned interval to a signed one of a type with the same bit width. + /// + /// If the type is already signed, the result is unspecified. + pub(crate) const fn cast_unsigned_to_signed(&self) -> Self { + debug_assert!(!self.ty.is_signed()); + + let target = self.ty.swap_signedness(); + if self.is_empty() { + return Self::empty(target); + } + + let t_max = target.max_value().cast_unsigned(); + let (x_min, x_max) = self.as_unsigned(); + + if x_min > t_max { + IInterval::new_signed(target, (x_min | !t_max).cast_signed(), (x_max | !t_max).cast_signed()) + } else if x_max > t_max { + IInterval::full(target) + } else { + IInterval::new_signed(target, self.min, self.max) + } + } + /// Casts a signed interval to an unsigned one of a type with the same bit width. + /// + /// If the type is already unsigned, the result is unspecified. + pub(crate) const fn cast_signed_to_unsigned(&self) -> Self { + debug_assert!(self.ty.is_signed()); + + let target = self.ty.swap_signedness(); + if self.is_empty() { + return Self::empty(target); + } + + let t_max = target.max_value().cast_unsigned(); + let (x_min, x_max) = self.as_signed(); + + if x_max < 0 { + IInterval::new_unsigned(target, x_min.cast_unsigned() & t_max, x_max.cast_unsigned() & t_max) + } else if x_min < 0 { + IInterval::full(target) + } else { + IInterval::new_unsigned(target, self.min.cast_unsigned(), self.max.cast_unsigned()) + } + } + /// Casts an interval to a different wider type of the same signedness. + /// + /// If the signedness of the target type is different or the target type is + /// narrower, the result is unspecified. + pub(crate) fn cast_widen(&self, target: IntType) -> Self { + debug_assert!(self.ty.is_signed() == target.is_signed()); + debug_assert!(self.ty.bits() <= target.bits()); + + let mut result = self.clone(); + result.ty = target; + result + } +} + +impl std::fmt::Display for IInterval { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if self.is_empty() { + write!(f, "[{:?}]", self.ty) + } else if self.ty.is_signed() { + let (min, max) = self.as_signed(); + if min == max { + write!(f, "{min}[{:?}]", self.ty) + } else { + write!(f, "{min}..={max}[{:?}]", self.ty) + } + } else { + let (min, max) = self.as_unsigned(); + if min == max { + write!(f, "{min}[{:?}]", self.ty) + } else { + write!(f, "{min}..={max}[{:?}]", self.ty) + } + } + } +} diff --git a/clippy_utils/src/rinterval/mod.rs b/clippy_utils/src/rinterval/mod.rs new file mode 100644 index 000000000000..70f47e791e84 --- /dev/null +++ b/clippy_utils/src/rinterval/mod.rs @@ -0,0 +1,403 @@ +//! A module for modeling the behavior of std functions using integer +//! arithmetic. +//! +//! Currently, only integer intervals are supported, but floating point +//! intervals can be added later. + +mod arithmetic; +mod bits; +mod iinterval; + +pub use arithmetic::*; +pub use iinterval::*; + +use rustc_ast::LitKind; +use rustc_hir::{ + BinOpKind, Block, ConstBlock, Expr, ExprKind, HirId, Item, ItemKind, Node, PatExpr, PatExprKind, PathSegment, + QPath, UnOp, +}; +use rustc_lint::LateContext; +use rustc_middle::ty::{IntTy, Ty, TyCtxt, TyKind, TypeckResults, UintTy}; +use rustc_span::Symbol; + +use crate::consts::{ConstEvalCtxt, Constant}; +use crate::sym; +use crate::ty::is_type_diagnostic_item; + +pub struct IntervalCtxt<'c, 'cx> { + cx: &'c LateContext<'cx>, + typeck: &'cx TypeckResults<'cx>, + const_eval: ConstEvalCtxt<'cx>, + + arth: Arithmetic, + isize_ty: IntType, +} + +impl<'c, 'cx> IntervalCtxt<'c, 'cx> { + pub fn new(cxt: &'c LateContext<'cx>) -> Self { + let isize_int = rustc_abi::HasDataLayout::data_layout(&cxt.tcx).ptr_sized_integer(); + let isize_ty = match isize_int { + rustc_abi::Integer::I8 => IntType::I8, + rustc_abi::Integer::I16 => IntType::I16, + rustc_abi::Integer::I32 => IntType::I32, + rustc_abi::Integer::I64 => IntType::I64, + rustc_abi::Integer::I128 => IntType::I128, + }; + + IntervalCtxt { + cx: cxt, + typeck: cxt.typeck_results(), + const_eval: ConstEvalCtxt::new(cxt), + + arth: Arithmetic { checked: false }, + isize_ty, + } + } + + /// Evaluates an expression to an integer interval. + /// + /// If the given expression is not of a supported integer type, None is + /// returned. + pub fn eval(&self, expr: &Expr<'cx>) -> Option { + let ty = self.to_int_type(self.typeck.expr_ty(expr))?; + + let expr = self.cx.expr_or_init(expr.peel_borrows()); + + if let Some(evaluated) = self.eval_ty(expr, ty) { + Some(evaluated) + } else { + // we couldn't evaluate the expression for some reason, so just + // return the full range of the integer type. + Some(IInterval::full(ty)) + } + } + /// Evaluates an expression to an integer interval of the given type. + /// + /// If anything goes wrong or an expression cannot be evaluated, None is + /// returned. + fn eval_ty(&self, expr: &Expr<'cx>, ty: IntType) -> Option { + match expr.kind { + ExprKind::Lit(lit) => { + return self.literal(&lit.node, ty); + }, + ExprKind::Binary(op, lhs, rhs) => { + return self.binary_op(op.node, lhs, rhs); + }, + ExprKind::Unary(op, operand) => { + let operand_interval = self.eval(operand)?; + return self.unary_op(op, &operand_interval); + }, + ExprKind::Cast(expr, _) => { + let expr_interval = self.eval(expr)?; + return Arithmetic::cast_as(&expr_interval, ty).ok(); + }, + + // For conditional expressions, we evaluate all branches and + // return the hull (union) of them. + // + // No attempt is made at trimming down on branches. All branches + // are assumed to be reachable. + ExprKind::If(_cond, if_true, Some(if_false)) => { + let true_interval = self.eval(if_true)?; + let false_interval = self.eval(if_false)?; + return true_interval.hull(&false_interval); + }, + ExprKind::Match(_expr, arms, _) => { + let mut combined = IInterval::empty(ty); + for arm in arms { + let arm_interval = self.eval(&arm.body)?; + combined = combined.hull(&arm_interval)?; + } + return Some(combined); + }, + + // Known methods and functions of integer types. + ExprKind::MethodCall(path, self_arg, args, _) => return self.method_call(path, self_arg, args, ty), + + _ => {}, + } + + // if all else fails, try to evaluate the expression using const eval + self.const_eval(expr, ty) + } + + fn literal(&self, lit: &LitKind, ty: IntType) -> Option { + match lit { + LitKind::Int(n, _) => Self::u128_repr_to_interval(n.get(), ty), + _ => None, + } + } + fn binary_op(&self, op: BinOpKind, l_expr: &Expr<'cx>, r_expr: &Expr<'cx>) -> Option { + let lhs = &self.eval(l_expr)?; + + // shl and shr have weird issues with type inference, so we need to + // explicitly type the right-hand side as u32 + let rhs = if matches!(op, BinOpKind::Shl | BinOpKind::Shr) { + &self.eval_ty(r_expr, IntType::U32).unwrap_or_else(|| { + // if we can't evaluate the right-hand side, just return the full + // range of the integer type. + IInterval::full(IntType::U32) + }) + } else { + &self.eval(r_expr)? + }; + + match op { + BinOpKind::Add => self.arth.add(lhs, rhs).ok(), + BinOpKind::Sub => self.arth.sub(lhs, rhs).ok(), + BinOpKind::Mul => self.arth.mul(lhs, rhs).ok(), + BinOpKind::Div => self.arth.div(lhs, rhs).ok(), + BinOpKind::Rem => self.arth.rem(lhs, rhs).ok(), + BinOpKind::BitAnd => Arithmetic::and(lhs, rhs).ok(), + BinOpKind::BitOr => Arithmetic::or(lhs, rhs).ok(), + BinOpKind::BitXor => Arithmetic::xor(lhs, rhs).ok(), + BinOpKind::Shl => self.arth.shl(lhs, rhs).ok(), + BinOpKind::Shr => self.arth.shr(lhs, rhs).ok(), + _ => None, + } + } + fn unary_op(&self, op: UnOp, value: &IInterval) -> Option { + match op { + UnOp::Neg => self.arth.neg(value).ok(), + UnOp::Not => Arithmetic::not(value).ok(), + UnOp::Deref => { + // Deref doesn't really make sense for numbers, but it does make + // sense for references to numbers. Assuming that the value is + // indeed a reference to a number, we can just return the value + // of the number. + Some(value.clone()) + }, + } + } + + /// Calls to methods that returns an integer. + fn method_call( + &self, + path: &PathSegment<'_>, + self_arg: &Expr<'cx>, + args: &[Expr<'cx>], + ty: IntType, + ) -> Option { + match args { + [] => { + let f: Option ArithResult> = match path.ident.name { + sym::neg => Some(Arithmetic::neg), + sym::checked_neg => Some(|_, x| Arithmetic::strict_neg(x)), + sym::saturating_neg => Some(|_, x| Arithmetic::saturating_neg(x)), + sym::strict_neg => Some(|_, x| Arithmetic::strict_neg(x)), + sym::wrapping_neg => Some(|_, x| Arithmetic::wrapping_neg(x)), + + sym::isqrt => Some(|_, x| Arithmetic::isqrt(x)), + sym::checked_isqrt => Some(|_, x| Arithmetic::isqrt(x)), + + sym::abs => Some(Arithmetic::abs), + sym::checked_abs => Some(|_, x| Arithmetic::strict_abs(x)), + sym::saturating_abs => Some(|_, x| Arithmetic::saturating_abs(x)), + sym::strict_abs => Some(|_, x| Arithmetic::strict_abs(x)), + sym::wrapping_abs => Some(|_, x| Arithmetic::wrapping_abs(x)), + sym::unsigned_abs => Some(|_, x| Arithmetic::unsigned_abs(x)), + + sym::not => Some(|_, x| Arithmetic::not(x)), + + sym::cast_signed => Some(|_, x| Arithmetic::cast_signed(x)), + sym::cast_unsigned => Some(|_, x| Arithmetic::cast_unsigned(x)), + + sym::leading_zeros => Some(|_, x| Arithmetic::leading_zeros(x)), + sym::leading_ones => Some(|_, x| Arithmetic::leading_ones(x)), + sym::trailing_zeros => Some(|_, x| Arithmetic::trailing_zeros(x)), + sym::trailing_ones => Some(|_, x| Arithmetic::trailing_ones(x)), + sym::count_ones => Some(|_, x| Arithmetic::count_ones(x)), + sym::count_zeros => Some(|_, x| Arithmetic::count_zeros(x)), + + _ => None, + }; + + if let Some(f) = f { + return f(&self.arth, &self.eval(self_arg)?).ok(); + } + }, + + [arg1] => { + let f: Option ArithResult> = match path.ident.name { + sym::add => Some(Arithmetic::add), + sym::checked_add => Some(|_, l, r| Arithmetic::strict_add(l, r)), + sym::saturating_add => Some(|_, l, r| Arithmetic::saturating_add(l, r)), + sym::strict_add => Some(|_, l, r| Arithmetic::strict_add(l, r)), + sym::wrapping_add => Some(|_, l, r| Arithmetic::wrapping_add(l, r)), + + sym::sub => Some(Arithmetic::sub), + sym::checked_sub => Some(|_, l, r| Arithmetic::strict_sub(l, r)), + sym::saturating_sub => Some(|_, l, r| Arithmetic::saturating_sub(l, r)), + sym::strict_sub => Some(|_, l, r| Arithmetic::strict_sub(l, r)), + sym::wrapping_sub => Some(|_, l, r| Arithmetic::wrapping_sub(l, r)), + + sym::mul => Some(Arithmetic::mul), + sym::checked_mul => Some(|_, l, r| Arithmetic::strict_mul(l, r)), + sym::saturating_mul => Some(|_, l, r| Arithmetic::saturating_mul(l, r)), + sym::strict_mul => Some(|_, l, r| Arithmetic::strict_mul(l, r)), + sym::wrapping_mul => Some(|_, l, r| Arithmetic::wrapping_mul(l, r)), + + sym::div => Some(Arithmetic::div), + sym::checked_div => Some(|_, l, r| Arithmetic::strict_div(l, r)), + sym::saturating_div => Some(|_, l, r| Arithmetic::saturating_div(l, r)), + sym::strict_div => Some(|_, l, r| Arithmetic::strict_div(l, r)), + sym::wrapping_div => Some(|_, l, r| Arithmetic::wrapping_div(l, r)), + + sym::div_euclid => Some(|_, l, r| Arithmetic::strict_div_euclid(l, r)), + sym::checked_div_euclid => Some(|_, l, r| Arithmetic::strict_div_euclid(l, r)), + sym::wrapping_div_euclid => Some(|_, l, r| Arithmetic::wrapping_div_euclid(l, r)), + + sym::div_ceil => Some(|_, l, r| Arithmetic::div_ceil(l, r)), + + sym::rem => Some(Arithmetic::rem), + sym::checked_rem => Some(|_, l, r| Arithmetic::strict_rem(l, r)), + sym::strict_rem => Some(|_, l, r| Arithmetic::strict_rem(l, r)), + sym::wrapping_rem => Some(|_, l, r| Arithmetic::wrapping_rem(l, r)), + + sym::rem_euclid => Some(Arithmetic::rem_euclid), + sym::checked_rem_euclid => Some(|_, l, r| Arithmetic::strict_rem_euclid(l, r)), + sym::strict_rem_euclid => Some(|_, l, r| Arithmetic::strict_rem_euclid(l, r)), + sym::wrapping_rem_euclid => Some(|_, l, r| Arithmetic::wrapping_rem_euclid(l, r)), + + sym::midpoint => Some(|_, l, r| Arithmetic::midpoint(l, r)), + + sym::pow => Some(|_, l, r| Arithmetic::strict_pow(l, r)), + sym::checked_pow => Some(|_, l, r| Arithmetic::strict_pow(l, r)), + sym::saturating_pow => Some(|_, l, r| Arithmetic::saturating_pow(l, r)), + sym::strict_pow => Some(|_, l, r| Arithmetic::strict_pow(l, r)), + sym::wrapping_pow => Some(|_, l, r| Arithmetic::wrapping_pow(l, r)), + + sym::min => Some(|_, l, r| Arithmetic::min(l, r)), + sym::max => Some(|_, l, r| Arithmetic::max(l, r)), + + sym::bitand => Some(|_, l, r| Arithmetic::and(l, r)), + sym::bitor => Some(|_, l, r| Arithmetic::or(l, r)), + sym::bitxor => Some(|_, l, r| Arithmetic::xor(l, r)), + + sym::shl => Some(Arithmetic::shl), + sym::checked_shl => Some(|_, l, r| Arithmetic::strict_shl(l, r)), + sym::strict_shl => Some(|_, l, r| Arithmetic::strict_shl(l, r)), + sym::wrapping_shl => Some(|_, l, r| Arithmetic::wrapping_shl(l, r)), + sym::unbounded_shl => Some(|_, l, r| Arithmetic::unbounded_shl(l, r)), + + sym::shr => Some(Arithmetic::shr), + sym::checked_shr => Some(|_, l, r| Arithmetic::strict_shr(l, r)), + sym::strict_shr => Some(|_, l, r| Arithmetic::strict_shr(l, r)), + sym::wrapping_shr => Some(|_, l, r| Arithmetic::wrapping_shr(l, r)), + sym::unbounded_shr => Some(|_, l, r| Arithmetic::unbounded_shr(l, r)), + + _ => None, + }; + + if let Some(f) = f { + return f(&self.arth, &self.eval(self_arg)?, &self.eval(arg1)?).ok(); + } + }, + + [arg1, arg2] => { + let f: Option ArithResult> = + match path.ident.name { + sym::clamp => Some(|_, a, b, c| Arithmetic::max(b, &Arithmetic::min(a, c)?)), + + _ => None, + }; + + if let Some(f) = f { + return f(&self.arth, &self.eval(self_arg)?, &self.eval(arg1)?, &self.eval(arg2)?).ok(); + } + }, + _ => {}, + } + + /// A list of supported `Option` methods + const OPTION_METHODS: &[Symbol] = &[ + sym::unwrap, + sym::unwrap_unchecked, + sym::unwrap_or, + sym::unwrap_or_default, + sym::expect, + ]; + + if OPTION_METHODS.contains(&path.ident.name) { + // It's highly likely that self is an option, so check to the type + // to verify that. + let self_ty = self.typeck.expr_ty(self_arg); + let is_option = is_type_diagnostic_item(self.cx, self_ty, sym::Option); + + if is_option || true { + let self_interval = self.eval_ty(self_arg, ty)?; + + match path.ident.name { + sym::unwrap | sym::unwrap_unchecked | sym::expect => { + // these are all the same in that they return the Some value + return Some(self_interval); + }, + sym::unwrap_or_default => { + // the default value of all integer types is 0, so we can + // evaluate the Some value and add 0 to it. + let zero = if ty.is_signed() { + IInterval::single_signed(ty, 0) + } else { + IInterval::single_unsigned(ty, 0) + }; + return self_interval.hull(&zero); + }, + sym::unwrap_or => { + // the default value is given as the second argument + let or_interval = self.eval(args.get(0)?)?; + return self_interval.hull(&or_interval); + }, + _ => {}, + } + } + } + + None + } + + /// Uses the const eval machinery to evaluate an expression to a single + /// integer value. + fn const_eval(&self, expr: &Expr<'_>, ty: IntType) -> Option { + let const_val = self.const_eval.eval(expr)?; + if let Constant::Int(n) = const_val { + return Self::u128_repr_to_interval(n, ty); + } + None + } + + fn u128_repr_to_interval(n: u128, ty: IntType) -> Option { + match ty.info() { + IntTypeInfo::Signed(_, _) => { + let n = n as i128; + // sign extend + let amt = 128 - ty.bits() as u32; + let n = (n << amt) >> amt; + Some(IInterval::single_signed(ty, n)) + }, + IntTypeInfo::Unsigned(t_max) => { + if n > t_max { + // this really shouldn't happen, but just in case + return None; + } + Some(IInterval::single_unsigned(ty, n)) + }, + } + } + fn to_int_type(&self, ty: Ty<'_>) -> Option { + match ty.kind() { + TyKind::Int(IntTy::Isize) => Some(self.isize_ty), + TyKind::Int(IntTy::I8) => Some(IntType::I8), + TyKind::Int(IntTy::I16) => Some(IntType::I16), + TyKind::Int(IntTy::I32) => Some(IntType::I32), + TyKind::Int(IntTy::I64) => Some(IntType::I64), + TyKind::Int(IntTy::I128) => Some(IntType::I128), + TyKind::Uint(UintTy::Usize) => Some(self.isize_ty.swap_signedness()), + TyKind::Uint(UintTy::U8) => Some(IntType::U8), + TyKind::Uint(UintTy::U16) => Some(IntType::U16), + TyKind::Uint(UintTy::U32) => Some(IntType::U32), + TyKind::Uint(UintTy::U64) => Some(IntType::U64), + TyKind::Uint(UintTy::U128) => Some(IntType::U128), + _ => None, + } + } +} diff --git a/clippy_utils/src/sym.rs b/clippy_utils/src/sym.rs index 8a8218c6976f..48292c99a795 100644 --- a/clippy_utils/src/sym.rs +++ b/clippy_utils/src/sym.rs @@ -94,16 +94,24 @@ generate! { cast, cast_const, cast_mut, + cast_signed, + cast_unsigned, ceil, ceil_char_boundary, chain, chars, checked_abs, checked_add, + checked_div, + checked_div_euclid, checked_isqrt, checked_mul, + checked_neg, checked_pow, + checked_rem, checked_rem_euclid, + checked_shl, + checked_shr, checked_sub, clamp, clippy_utils, @@ -119,6 +127,7 @@ generate! { copy_to, copy_to_nonoverlapping, count_ones, + count_zeros, create, create_new, cycle, @@ -126,6 +135,8 @@ generate! { de, diagnostics, disallowed_types, + div_ceil, + div_euclid, drain, dump, ends_with, @@ -196,6 +207,8 @@ generate! { kw, last, lazy_static, + leading_ones, + leading_zeros, ln, lock, lock_api, @@ -214,6 +227,7 @@ generate! { max_by_key, max_value, maximum, + midpoint, min, min_by, min_by_key, @@ -287,6 +301,8 @@ generate! { rustfmt_skip, rwlock, saturating_abs, + saturating_mul, + saturating_neg, saturating_pow, scan, seek, @@ -317,6 +333,17 @@ generate! { sqrt, starts_with, step_by, + strict_abs, + strict_add, + strict_div, + strict_mul, + strict_neg, + strict_pow, + strict_rem, + strict_rem_euclid, + strict_shl, + strict_shr, + strict_sub, strlen, style, subsec_micros, @@ -339,14 +366,19 @@ generate! { to_path_buf, to_uppercase, tokio, + trailing_ones, + trailing_zeros, trim, trim_end, trim_end_matches, trim_start, trim_start_matches, truncate, + unbounded_shl, + unbounded_shr, unreachable_pub, unsafe_removed_from_name, + unsigned_abs, unused, unused_braces, unused_extern_crates, @@ -365,7 +397,13 @@ generate! { warnings, wildcard_imports, with_capacity, + wrapping_abs, + wrapping_div_euclid, + wrapping_neg, wrapping_offset, + wrapping_pow, + wrapping_shl, + wrapping_shr, write, write_unaligned, writeln, diff --git a/tests/ui/cast.rs b/tests/ui/cast.rs index 525be8216500..34ab4da78348 100644 --- a/tests/ui/cast.rs +++ b/tests/ui/cast.rs @@ -16,25 +16,25 @@ )] // FIXME(f16_f128): add tests once const casting is available for these types - +fn get_value() -> T { todo!() } fn main() { // Test clippy::cast_precision_loss - let x0 = 1i32; + let x0: i32 = get_value(); x0 as f32; //~^ cast_precision_loss - let x1 = 1i64; + let x1: i64 = get_value(); x1 as f32; //~^ cast_precision_loss x1 as f64; //~^ cast_precision_loss - let x2 = 1u32; + let x2: u32 = get_value(); x2 as f32; //~^ cast_precision_loss - let x3 = 1u64; + let x3: u64 = get_value(); x3 as f32; //~^ cast_precision_loss @@ -461,13 +461,13 @@ fn issue11642() { (-2_i32 >> 1) as u32; //~^ cast_sign_loss - let x: i32 = 10; + let x: i32 = get_value(); (x * x) as u32; //~^ cast_sign_loss (x * x * x) as u32; //~^ cast_sign_loss - let y: i16 = -2; + let y: i16 = get_value(); (y * y * y * y * -2) as u16; //~^ cast_sign_loss @@ -486,7 +486,7 @@ fn issue11642() { (y + y + y + 2) as u16; //~^ cast_sign_loss - let z: i16 = 2; + let z: i16 = get_value(); (z + -2) as u16; //~^ cast_sign_loss @@ -569,3 +569,101 @@ fn issue12721() { (255 % 999999u64) as u8; //~^ cast_possible_truncation } + +fn dxgi_xr10_to_unorm8(x: u16) -> u8 { + debug_assert!(x <= 1023); + // new range: [-384, 639] (or [-0.75294, 1.25294]) + let x = x as i16 - 0x180; + //~^ cast_possible_wrap + // new range: [0, 510] (or [0.0, 1.0]) + let x = x.clamp(0, 510) as u16; + //~^ cast_sign_loss + // this is round(x / 510 * 255), but faster + ((x + 1) >> 1) as u8 + //~^ cast_possible_truncation +} +fn dxgi_xr10_to_unorm16(x: u16) -> u16 { + debug_assert!(x <= 1023); + // new range: [-384, 639] (or [-0.75294, 1.25294]) + let x = x as i16 - 0x180; + //~^ cast_possible_wrap + // new range: [0, 510] (or [0.0, 1.0]) + let x = x.clamp(0, 510) as u16; + //~^ cast_sign_loss + // this is round(x / 510 * 65535), but faster + ((x as u32 * 8421376 + 65535) >> 16) as u16 +} +fn unorm16_to_unorm8(x: u16) -> u8 { + ((x as u32 * 255 + 32895) >> 16) as u8 + //~^ cast_possible_truncation +} + +fn f32_to_f16u(value: f32) -> u16 { + // Source: https://github.com/starkat99/half-rs/blob/2c4122db4e8f7d8ce030bb4b5ed8913bd6bbf2b1/src/binary16/arch.rs#L482 + // Author: Kathryn Long + // License: MIT OR Apache-2.0 + + // Convert to raw bytes + let x: u32 = value.to_bits(); + + // Extract IEEE754 components + let sign = x & 0x8000_0000u32; + let exp = x & 0x7F80_0000u32; + let man = x & 0x007F_FFFFu32; + + // Check for all exponent bits being set, which is Infinity or NaN + if exp == 0x7F80_0000u32 { + // Set mantissa MSB for NaN (and also keep shifted mantissa bits) + let nan_bit = if man == 0 { 0 } else { 0x0200u32 }; + return ((sign >> 16) | 0x7C00u32 | nan_bit | (man >> 13)) as u16; + //~^ cast_possible_truncation + } + + // The number is normalized, start assembling half precision version + let half_sign = sign >> 16; + // Unbias the exponent, then bias for half precision + let unbiased_exp = ((exp >> 23) as i32) - 127; + //~^ cast_possible_wrap + let half_exp = unbiased_exp + 15; + + // Check for exponent overflow, return +infinity + if half_exp >= 0x1F { + return (half_sign | 0x7C00u32) as u16; + //~^ cast_possible_truncation + } + + // Check for underflow + if half_exp <= 0 { + // Check mantissa for what we can do + if 14 - half_exp > 24 { + // No rounding possibility, so this is a full underflow, return signed zero + return half_sign as u16; + } + // Don't forget about hidden leading mantissa bit when assembling mantissa + let man = man | 0x0080_0000u32; + let mut half_man = man >> (14 - half_exp); + // Check for rounding (see comment above functions) + let round_bit = 1 << (13 - half_exp); + if (man & round_bit) != 0 && (man & (3 * round_bit - 1)) != 0 { + half_man += 1; + } + // No exponent for subnormals + return (half_sign | half_man) as u16; + //~^ cast_possible_truncation + } + + // Rebias the exponent + let half_exp = (half_exp as u32) << 10; + //~^ cast_sign_loss + let half_man = man >> 13; + // Check for rounding (see comment above functions) + let round_bit = 0x0000_1000u32; + if (man & round_bit) != 0 && (man & (3 * round_bit - 1)) != 0 { + // Round it + ((half_sign | half_exp | half_man) + 1) as u16 + //~^ cast_possible_truncation + } else { + (half_sign | half_exp | half_man) as u16 + //~^ cast_possible_truncation + } +} diff --git a/tests/ui/cast.stderr b/tests/ui/cast.stderr index 1cb30d956679..25eb0e4b88cb 100644 --- a/tests/ui/cast.stderr +++ b/tests/ui/cast.stderr @@ -78,6 +78,7 @@ error: casting `i32` to `i8` may truncate the value LL | 1i32 as i8; | ^^^^^^^^^^ | + = note: the cast operant may assume the value `1` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | @@ -91,6 +92,7 @@ error: casting `i32` to `u8` may truncate the value LL | 1i32 as u8; | ^^^^^^^^^^ | + = note: the cast operant may assume the value `1` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | @@ -126,6 +128,7 @@ error: casting `u32` to `u16` may truncate the value LL | 1f32 as u32 as u16; | ^^^^^^^^^^^^^^^^^^ | + = note: the cast operant may contain values in the range `0..=4294967295` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | @@ -153,6 +156,7 @@ error: casting `i32` to `i8` may truncate the value LL | let _x: i8 = 1i32 as _; | ^^^^^^^^^ | + = note: the cast operant may assume the value `1` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | @@ -196,6 +200,7 @@ error: casting `u8` to `i8` may wrap around the value LL | 1u8 as i8; | ^^^^^^^^^ | + = note: the cast operant may assume the value `1` = note: `-D clippy::cast-possible-wrap` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::cast_possible_wrap)]` @@ -204,24 +209,32 @@ error: casting `u16` to `i16` may wrap around the value | LL | 1u16 as i16; | ^^^^^^^^^^^ + | + = note: the cast operant may assume the value `1` error: casting `u32` to `i32` may wrap around the value --> tests/ui/cast.rs:94:5 | LL | 1u32 as i32; | ^^^^^^^^^^^ + | + = note: the cast operant may assume the value `1` error: casting `u64` to `i64` may wrap around the value --> tests/ui/cast.rs:97:5 | LL | 1u64 as i64; | ^^^^^^^^^^^ + | + = note: the cast operant may assume the value `1` error: casting `usize` to `isize` may wrap around the value --> tests/ui/cast.rs:100:5 | LL | 1usize as isize; | ^^^^^^^^^^^^^^^ + | + = note: the cast operant may assume the value `1` error: casting `usize` to `i8` may truncate the value --> tests/ui/cast.rs:104:5 @@ -229,6 +242,7 @@ error: casting `usize` to `i8` may truncate the value LL | 1usize as i8; | ^^^^^^^^^^^^ | + = note: the cast operant may assume the value `1` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | @@ -242,6 +256,7 @@ error: casting `usize` to `i16` may truncate the value LL | 1usize as i16; | ^^^^^^^^^^^^^ | + = note: the cast operant may assume the value `1` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | @@ -255,6 +270,7 @@ error: casting `usize` to `i16` may wrap around the value on targets with 16-bit LL | 1usize as i16; | ^^^^^^^^^^^^^ | + = note: the cast operant may assume the value `1` = note: `usize` and `isize` may be as small as 16 bits on some platforms = note: for more information see https://doc.rust-lang.org/reference/types/numeric.html#machine-dependent-integer-types @@ -264,6 +280,7 @@ error: casting `usize` to `i32` may truncate the value on targets with 64-bit wi LL | 1usize as i32; | ^^^^^^^^^^^^^ | + = note: the cast operant may assume the value `1` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | @@ -276,12 +293,16 @@ error: casting `usize` to `i32` may wrap around the value on targets with 32-bit | LL | 1usize as i32; | ^^^^^^^^^^^^^ + | + = note: the cast operant may assume the value `1` error: casting `usize` to `i64` may wrap around the value on targets with 64-bit wide pointers --> tests/ui/cast.rs:118:5 | LL | 1usize as i64; | ^^^^^^^^^^^^^ + | + = note: the cast operant may assume the value `1` error: casting `u16` to `isize` may wrap around the value on targets with 16-bit wide pointers --> tests/ui/cast.rs:124:5 @@ -289,6 +310,7 @@ error: casting `u16` to `isize` may wrap around the value on targets with 16-bit LL | 1u16 as isize; | ^^^^^^^^^^^^^ | + = note: the cast operant may assume the value `1` = note: `usize` and `isize` may be as small as 16 bits on some platforms = note: for more information see https://doc.rust-lang.org/reference/types/numeric.html#machine-dependent-integer-types @@ -297,6 +319,8 @@ error: casting `u32` to `isize` may wrap around the value on targets with 32-bit | LL | 1u32 as isize; | ^^^^^^^^^^^^^ + | + = note: the cast operant may assume the value `1` error: casting `u64` to `isize` may truncate the value on targets with 32-bit wide pointers --> tests/ui/cast.rs:132:5 @@ -304,6 +328,7 @@ error: casting `u64` to `isize` may truncate the value on targets with 32-bit wi LL | 1u64 as isize; | ^^^^^^^^^^^^^ | + = note: the cast operant may assume the value `1` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | @@ -316,48 +341,64 @@ error: casting `u64` to `isize` may wrap around the value on targets with 64-bit | LL | 1u64 as isize; | ^^^^^^^^^^^^^ + | + = note: the cast operant may assume the value `1` error: casting `i32` to `u32` may lose the sign of the value --> tests/ui/cast.rs:138:5 | LL | -1i32 as u32; | ^^^^^^^^^^^^ + | + = note: the cast operant may assume the value `-1` error: casting `isize` to `usize` may lose the sign of the value --> tests/ui/cast.rs:142:5 | LL | -1isize as usize; | ^^^^^^^^^^^^^^^^ + | + = note: the cast operant may assume the value `-1` error: casting `i8` to `u8` may lose the sign of the value --> tests/ui/cast.rs:154:5 | LL | (i8::MIN).abs() as u8; | ^^^^^^^^^^^^^^^^^^^^^ + | + = note: the cast operant may assume the value `-128` error: casting `i64` to `u64` may lose the sign of the value --> tests/ui/cast.rs:159:5 | LL | (-1i64).abs() as u64; | ^^^^^^^^^^^^^^^^^^^^ + | + = note: the cast operant may assume the value `1` error: casting `isize` to `usize` may lose the sign of the value --> tests/ui/cast.rs:161:5 | LL | (-1isize).abs() as usize; | ^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: the cast operant may assume the value `1` error: casting `i64` to `u64` may lose the sign of the value --> tests/ui/cast.rs:169:5 | LL | (unsafe { (-1i64).checked_abs().unwrap_unchecked() }) as u64; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: the cast operant may assume the value `1` error: casting `i64` to `u64` may lose the sign of the value --> tests/ui/cast.rs:185:5 | LL | (unsafe { (-1i64).checked_isqrt().unwrap_unchecked() }) as u64; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: the cast operant may contain values in the range `` error: casting `i64` to `i8` may truncate the value --> tests/ui/cast.rs:237:5 @@ -365,6 +406,7 @@ error: casting `i64` to `i8` may truncate the value LL | (-99999999999i64).min(1) as i8; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | + = note: the cast operant may assume the value `-99999999999` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | @@ -378,6 +420,7 @@ error: casting `u64` to `u8` may truncate the value LL | 999999u64.clamp(0, 256) as u8; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | + = note: the cast operant may assume the value `256` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | @@ -471,6 +514,7 @@ error: casting `u32` to `u8` may truncate the value LL | let c = (q >> 16) as u8; | ^^^^^^^^^^^^^^^ | + = note: the cast operant may contain values in the range `0..=65535` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | @@ -484,6 +528,7 @@ error: casting `u32` to `u8` may truncate the value LL | let c = (q / 1000) as u8; | ^^^^^^^^^^^^^^^^ | + = note: the cast operant may contain values in the range `0..=4294967` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | @@ -496,78 +541,104 @@ error: casting `i32` to `u32` may lose the sign of the value | LL | (x * x) as u32; | ^^^^^^^^^^^^^^ + | + = note: the cast operant may contain values in the range `-1073709056..=1073741824` error: casting `i32` to `u32` may lose the sign of the value --> tests/ui/cast.rs:444:32 | LL | let _a = |x: i32| -> u32 { (x * x * x * x) as u32 }; | ^^^^^^^^^^^^^^^^^^^^^^ + | + = note: the cast operant may contain values in the range `-2147483648..=2147483647` error: casting `i32` to `u32` may lose the sign of the value --> tests/ui/cast.rs:447:5 | LL | (2_i32).checked_pow(3).unwrap() as u32; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: the cast operant may assume the value `8` error: casting `i32` to `u32` may lose the sign of the value --> tests/ui/cast.rs:449:5 | LL | (-2_i32).pow(3) as u32; | ^^^^^^^^^^^^^^^^^^^^^^ + | + = note: the cast operant may assume the value `-8` error: casting `i32` to `u32` may lose the sign of the value --> tests/ui/cast.rs:454:5 | LL | (-5_i32 % 2) as u32; | ^^^^^^^^^^^^^^^^^^^ + | + = note: the cast operant may assume the value `-1` error: casting `i32` to `u32` may lose the sign of the value --> tests/ui/cast.rs:457:5 | LL | (-5_i32 % -2) as u32; | ^^^^^^^^^^^^^^^^^^^^ + | + = note: the cast operant may assume the value `-1` error: casting `i32` to `u32` may lose the sign of the value --> tests/ui/cast.rs:461:5 | LL | (-2_i32 >> 1) as u32; | ^^^^^^^^^^^^^^^^^^^^ + | + = note: the cast operant may assume the value `-1` error: casting `i32` to `u32` may lose the sign of the value --> tests/ui/cast.rs:465:5 | LL | (x * x) as u32; | ^^^^^^^^^^^^^^ + | + = note: the cast operant may contain values in the range `-2147483648..=2147483647` error: casting `i32` to `u32` may lose the sign of the value --> tests/ui/cast.rs:467:5 | LL | (x * x * x) as u32; | ^^^^^^^^^^^^^^^^^^ + | + = note: the cast operant may contain values in the range `-2147483648..=2147483647` error: casting `i16` to `u16` may lose the sign of the value --> tests/ui/cast.rs:471:5 | LL | (y * y * y * y * -2) as u16; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: the cast operant may contain values in the range `-32768..=32767` error: casting `i16` to `u16` may lose the sign of the value --> tests/ui/cast.rs:474:5 | LL | (y * y * y / y * 2) as u16; | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: the cast operant may contain values in the range `-32768..=32767` error: casting `i16` to `u16` may lose the sign of the value --> tests/ui/cast.rs:476:5 | LL | (y * y / y * 2) as u16; | ^^^^^^^^^^^^^^^^^^^^^^ + | + = note: the cast operant may contain values in the range `-32768..=32767` error: casting `i16` to `u16` may lose the sign of the value --> tests/ui/cast.rs:479:5 | LL | (y / y * y * -2) as u16; | ^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: the cast operant may contain values in the range `-32768..=32767` error: equal expressions as operands to `/` --> tests/ui/cast.rs:479:6 @@ -582,90 +653,120 @@ error: casting `i16` to `u16` may lose the sign of the value | LL | (y + y + y + -2) as u16; | ^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: the cast operant may contain values in the range `-32768..=32767` error: casting `i16` to `u16` may lose the sign of the value --> tests/ui/cast.rs:486:5 | LL | (y + y + y + 2) as u16; | ^^^^^^^^^^^^^^^^^^^^^^ + | + = note: the cast operant may contain values in the range `-32768..=32767` error: casting `i16` to `u16` may lose the sign of the value --> tests/ui/cast.rs:490:5 | LL | (z + -2) as u16; | ^^^^^^^^^^^^^^^ + | + = note: the cast operant may contain values in the range `-32768..=32767` error: casting `i16` to `u16` may lose the sign of the value --> tests/ui/cast.rs:493:5 | LL | (z + z + 2) as u16; | ^^^^^^^^^^^^^^^^^^ + | + = note: the cast operant may contain values in the range `-32768..=32767` error: casting `i32` to `u32` may lose the sign of the value --> tests/ui/cast.rs:497:9 | LL | (a * a * b * b * c * c) as u32; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: the cast operant may contain values in the range `-2147483648..=2147483647` error: casting `i32` to `u32` may lose the sign of the value --> tests/ui/cast.rs:499:9 | LL | (a * b * c) as u32; | ^^^^^^^^^^^^^^^^^^ + | + = note: the cast operant may contain values in the range `-2147483648..=2147483647` error: casting `i32` to `u32` may lose the sign of the value --> tests/ui/cast.rs:502:9 | LL | (a * -b * c) as u32; | ^^^^^^^^^^^^^^^^^^^ + | + = note: the cast operant may contain values in the range `-2147483648..=2147483647` error: casting `i32` to `u32` may lose the sign of the value --> tests/ui/cast.rs:505:9 | LL | (a * b * c * c) as u32; | ^^^^^^^^^^^^^^^^^^^^^^ + | + = note: the cast operant may contain values in the range `-2147483648..=2147483647` error: casting `i32` to `u32` may lose the sign of the value --> tests/ui/cast.rs:507:9 | LL | (a * -2) as u32; | ^^^^^^^^^^^^^^^ + | + = note: the cast operant may contain values in the range `-2147483648..=2147483647` error: casting `i32` to `u32` may lose the sign of the value --> tests/ui/cast.rs:510:9 | LL | (a * b * c * -2) as u32; | ^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: the cast operant may contain values in the range `-2147483648..=2147483647` error: casting `i32` to `u32` may lose the sign of the value --> tests/ui/cast.rs:513:9 | LL | (a / b) as u32; | ^^^^^^^^^^^^^^ + | + = note: the cast operant may contain values in the range `-2147483648..=2147483647` error: casting `i32` to `u32` may lose the sign of the value --> tests/ui/cast.rs:515:9 | LL | (a / b * c) as u32; | ^^^^^^^^^^^^^^^^^^ + | + = note: the cast operant may contain values in the range `-2147483648..=2147483647` error: casting `i32` to `u32` may lose the sign of the value --> tests/ui/cast.rs:518:9 | LL | (a / b + b * c) as u32; | ^^^^^^^^^^^^^^^^^^^^^^ + | + = note: the cast operant may contain values in the range `-2147483648..=2147483647` error: casting `i32` to `u32` may lose the sign of the value --> tests/ui/cast.rs:521:9 | LL | a.saturating_pow(3) as u32; | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: the cast operant may contain values in the range `-2147483648..=2147483647` error: casting `i32` to `u32` may lose the sign of the value --> tests/ui/cast.rs:524:9 | LL | (a.abs() * b.pow(2) / c.abs()) as u32 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: the cast operant may contain values in the range `-2147483648..=2147483647` error: casting `i32` to `u32` may lose the sign of the value --> tests/ui/cast.rs:532:21 @@ -676,6 +777,7 @@ LL | let _ = i32::MIN as u32; // cast_sign_loss LL | m!(); | ---- in this macro invocation | + = note: the cast operant may assume the value `-2147483648` = note: this error originates in the macro `m` (in Nightly builds, run with -Z macro-backtrace for more info) error: casting `u32` to `u8` may truncate the value @@ -687,6 +789,7 @@ LL | let _ = u32::MAX as u8; // cast_possible_truncation LL | m!(); | ---- in this macro invocation | + = note: the cast operant may assume the value `4294967295` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... = note: this error originates in the macro `m` (in Nightly builds, run with -Z macro-backtrace for more info) help: ... or use `try_from` and handle the error accordingly @@ -713,6 +816,7 @@ error: casting `i64` to `usize` may truncate the value on targets with 32-bit wi LL | bar.unwrap().unwrap() as usize | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | + = note: the cast operant may contain values in the range `-9223372036854775808..=9223372036854775807` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | @@ -725,6 +829,8 @@ error: casting `i64` to `usize` may lose the sign of the value | LL | bar.unwrap().unwrap() as usize | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: the cast operant may contain values in the range `-9223372036854775808..=9223372036854775807` error: casting `u64` to `u8` may truncate the value --> tests/ui/cast.rs:566:5 @@ -732,6 +838,7 @@ error: casting `u64` to `u8` may truncate the value LL | (256 & 999999u64) as u8; | ^^^^^^^^^^^^^^^^^^^^^^^ | + = note: the cast operant may assume the value `0` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | @@ -745,6 +852,7 @@ error: casting `u64` to `u8` may truncate the value LL | (255 % 999999u64) as u8; | ^^^^^^^^^^^^^^^^^^^^^^^ | + = note: the cast operant may assume the value `255` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | @@ -752,5 +860,151 @@ LL - (255 % 999999u64) as u8; LL + u8::try_from(255 % 999999u64); | -error: aborting due to 92 previous errors +error: casting `u16` to `i16` may wrap around the value + --> tests/ui/cast.rs:576:13 + | +LL | let x = x as i16 - 0x180; + | ^^^^^^^^ + | + = note: the cast operant may contain values in the range `0..=65535` + +error: casting `i16` to `u16` may lose the sign of the value + --> tests/ui/cast.rs:579:13 + | +LL | let x = x.clamp(0, 510) as u16; + | ^^^^^^^^^^^^^^^^^^^^^^ + | + = note: the cast operant may contain values in the range `0..=510` + +error: casting `u16` to `u8` may truncate the value + --> tests/ui/cast.rs:582:5 + | +LL | ((x + 1) >> 1) as u8 + | ^^^^^^^^^^^^^^^^^^^^ + | + = note: the cast operant may contain values in the range `0..=255` + = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... +help: ... or use `try_from` and handle the error accordingly + | +LL - ((x + 1) >> 1) as u8 +LL + u8::try_from((x + 1) >> 1) + | + +error: casting `u16` to `i16` may wrap around the value + --> tests/ui/cast.rs:588:13 + | +LL | let x = x as i16 - 0x180; + | ^^^^^^^^ + | + = note: the cast operant may contain values in the range `0..=65535` + +error: casting `i16` to `u16` may lose the sign of the value + --> tests/ui/cast.rs:591:13 + | +LL | let x = x.clamp(0, 510) as u16; + | ^^^^^^^^^^^^^^^^^^^^^^ + | + = note: the cast operant may contain values in the range `0..=510` + +error: casting `u32` to `u8` may truncate the value + --> tests/ui/cast.rs:597:5 + | +LL | ((x as u32 * 255 + 32895) >> 16) as u8 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: the cast operant may contain values in the range `0..=255` + = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... +help: ... or use `try_from` and handle the error accordingly + | +LL - ((x as u32 * 255 + 32895) >> 16) as u8 +LL + u8::try_from((x as u32 * 255 + 32895) >> 16) + | + +error: casting `u32` to `u16` may truncate the value + --> tests/ui/cast.rs:618:16 + | +LL | return ((sign >> 16) | 0x7C00u32 | nan_bit | (man >> 13)) as u16; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: the cast operant may contain values in the range `0..=65535` + = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... +help: ... or use `try_from` and handle the error accordingly + | +LL - return ((sign >> 16) | 0x7C00u32 | nan_bit | (man >> 13)) as u16; +LL + return u16::try_from((sign >> 16) | 0x7C00u32 | nan_bit | (man >> 13)); + | + +error: casting `u32` to `i32` may wrap around the value + --> tests/ui/cast.rs:625:24 + | +LL | let unbiased_exp = ((exp >> 23) as i32) - 127; + | ^^^^^^^^^^^^^^^^^^^^ + | + = note: the cast operant may contain values in the range `0..=255` + +error: casting `u32` to `u16` may truncate the value + --> tests/ui/cast.rs:631:16 + | +LL | return (half_sign | 0x7C00u32) as u16; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: the cast operant may contain values in the range `31744..=65535` + = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... +help: ... or use `try_from` and handle the error accordingly + | +LL - return (half_sign | 0x7C00u32) as u16; +LL + return u16::try_from(half_sign | 0x7C00u32); + | + +error: casting `u32` to `u16` may truncate the value + --> tests/ui/cast.rs:651:16 + | +LL | return (half_sign | half_man) as u16; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: the cast operant may contain values in the range `0..=4294967295` + = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... +help: ... or use `try_from` and handle the error accordingly + | +LL - return (half_sign | half_man) as u16; +LL + return u16::try_from(half_sign | half_man); + | + +error: casting `i32` to `u32` may lose the sign of the value + --> tests/ui/cast.rs:656:20 + | +LL | let half_exp = (half_exp as u32) << 10; + | ^^^^^^^^^^^^^^^^^ + | + = note: the cast operant may contain values in the range `-112..=143` + +error: casting `u32` to `u16` may truncate the value + --> tests/ui/cast.rs:663:9 + | +LL | ((half_sign | half_exp | half_man) + 1) as u16 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: the cast operant may contain values in the range `0..=4294967295` + = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... +help: ... or use `try_from` and handle the error accordingly + | +LL - ((half_sign | half_exp | half_man) + 1) as u16 +LL + u16::try_from((half_sign | half_exp | half_man) + 1) + | + +error: casting `u32` to `u16` may truncate the value + --> tests/ui/cast.rs:666:9 + | +LL | (half_sign | half_exp | half_man) as u16 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: the cast operant may contain values in the range `0..=4294967295` + = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... +help: ... or use `try_from` and handle the error accordingly + | +LL - (half_sign | half_exp | half_man) as u16 +LL + u16::try_from(half_sign | half_exp | half_man) + | + +error: aborting due to 105 previous errors diff --git a/tests/ui/cast_size.64bit.stderr b/tests/ui/cast_size.64bit.stderr index ba1419583aeb..9fa20e997f73 100644 --- a/tests/ui/cast_size.64bit.stderr +++ b/tests/ui/cast_size.64bit.stderr @@ -4,6 +4,7 @@ error: casting `isize` to `i8` may truncate the value LL | 1isize as i8; | ^^^^^^^^^^^^ | + = note: the cast operant may assume the value `1` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... = note: `-D clippy::cast-possible-truncation` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::cast_possible_truncation)]` @@ -46,6 +47,7 @@ error: casting `isize` to `i32` may truncate the value on targets with 64-bit wi LL | 1isize as i32; | ^^^^^^^^^^^^^ | + = note: the cast operant may assume the value `1` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | @@ -59,6 +61,7 @@ error: casting `isize` to `u32` may truncate the value on targets with 64-bit wi LL | 1isize as u32; | ^^^^^^^^^^^^^ | + = note: the cast operant may assume the value `1` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | @@ -72,6 +75,7 @@ error: casting `usize` to `u32` may truncate the value on targets with 64-bit wi LL | 1usize as u32; | ^^^^^^^^^^^^^ | + = note: the cast operant may assume the value `1` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | @@ -85,6 +89,7 @@ error: casting `usize` to `i32` may truncate the value on targets with 64-bit wi LL | 1usize as i32; | ^^^^^^^^^^^^^ | + = note: the cast operant may assume the value `1` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | @@ -98,6 +103,7 @@ error: casting `usize` to `i32` may wrap around the value on targets with 32-bit LL | 1usize as i32; | ^^^^^^^^^^^^^ | + = note: the cast operant may assume the value `1` = note: `-D clippy::cast-possible-wrap` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::cast_possible_wrap)]` @@ -107,6 +113,7 @@ error: casting `i64` to `isize` may truncate the value on targets with 32-bit wi LL | 1i64 as isize; | ^^^^^^^^^^^^^ | + = note: the cast operant may assume the value `1` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | @@ -120,6 +127,7 @@ error: casting `i64` to `usize` may truncate the value on targets with 32-bit wi LL | 1i64 as usize; | ^^^^^^^^^^^^^ | + = note: the cast operant may assume the value `1` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | @@ -133,6 +141,7 @@ error: casting `u64` to `isize` may truncate the value on targets with 32-bit wi LL | 1u64 as isize; | ^^^^^^^^^^^^^ | + = note: the cast operant may assume the value `1` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | @@ -145,6 +154,8 @@ error: casting `u64` to `isize` may wrap around the value on targets with 64-bit | LL | 1u64 as isize; | ^^^^^^^^^^^^^ + | + = note: the cast operant may assume the value `1` error: casting `u64` to `usize` may truncate the value on targets with 32-bit wide pointers --> tests/ui/cast_size.rs:51:5 @@ -152,6 +163,7 @@ error: casting `u64` to `usize` may truncate the value on targets with 32-bit wi LL | 1u64 as usize; | ^^^^^^^^^^^^^ | + = note: the cast operant may assume the value `1` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | @@ -164,6 +176,8 @@ error: casting `u32` to `isize` may wrap around the value on targets with 32-bit | LL | 1u32 as isize; | ^^^^^^^^^^^^^ + | + = note: the cast operant may assume the value `1` error: casting `i32` to `f32` causes a loss of precision (`i32` is 32 bits wide, but `f32`'s mantissa is only 23 bits wide) --> tests/ui/cast_size.rs:61:5 @@ -183,6 +197,7 @@ error: casting `usize` to `u16` may truncate the value LL | const N: u16 = M as u16; | ^^^^^^^^ | + = note: the cast operant may assume the value `100` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... error: aborting due to 19 previous errors From aee8049c37467e0fa09f824c2abd336e377dc3c8 Mon Sep 17 00:00:00 2001 From: RunDevelopment Date: Fri, 25 Jul 2025 15:11:31 +0200 Subject: [PATCH 02/13] issue7486 --- tests/ui/cast.rs | 11 +++++++++++ tests/ui/cast.stderr | 42 ++++++++++++++++++++++++++++-------------- 2 files changed, 39 insertions(+), 14 deletions(-) diff --git a/tests/ui/cast.rs b/tests/ui/cast.rs index 34ab4da78348..ef67b8a67d2b 100644 --- a/tests/ui/cast.rs +++ b/tests/ui/cast.rs @@ -570,6 +570,17 @@ fn issue12721() { //~^ cast_possible_truncation } +fn issue7486(number: u64, other: u32) -> u32 { + // a u64 with guaranteed value to be less than or equal to u32::max_value() + let other_u64 = other as u64; + // modulo guarantees that the following invariant holds: + // result < other_u64 + // which implies that result < u32::max_value() + let result = number % other_u64; + result as u32 + //~^ cast_possible_truncation +} + fn dxgi_xr10_to_unorm8(x: u16) -> u8 { debug_assert!(x <= 1023); // new range: [-384, 639] (or [-0.75294, 1.25294]) diff --git a/tests/ui/cast.stderr b/tests/ui/cast.stderr index 25eb0e4b88cb..3901a8be86e9 100644 --- a/tests/ui/cast.stderr +++ b/tests/ui/cast.stderr @@ -860,8 +860,22 @@ LL - (255 % 999999u64) as u8; LL + u8::try_from(255 % 999999u64); | +error: casting `u64` to `u32` may truncate the value + --> tests/ui/cast.rs:580:5 + | +LL | result as u32 + | ^^^^^^^^^^^^^ + | + = note: the cast operant may contain values in the range `0..=4294967294` + = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... +help: ... or use `try_from` and handle the error accordingly + | +LL - result as u32 +LL + u32::try_from(result) + | + error: casting `u16` to `i16` may wrap around the value - --> tests/ui/cast.rs:576:13 + --> tests/ui/cast.rs:587:13 | LL | let x = x as i16 - 0x180; | ^^^^^^^^ @@ -869,7 +883,7 @@ LL | let x = x as i16 - 0x180; = note: the cast operant may contain values in the range `0..=65535` error: casting `i16` to `u16` may lose the sign of the value - --> tests/ui/cast.rs:579:13 + --> tests/ui/cast.rs:590:13 | LL | let x = x.clamp(0, 510) as u16; | ^^^^^^^^^^^^^^^^^^^^^^ @@ -877,7 +891,7 @@ LL | let x = x.clamp(0, 510) as u16; = note: the cast operant may contain values in the range `0..=510` error: casting `u16` to `u8` may truncate the value - --> tests/ui/cast.rs:582:5 + --> tests/ui/cast.rs:593:5 | LL | ((x + 1) >> 1) as u8 | ^^^^^^^^^^^^^^^^^^^^ @@ -891,7 +905,7 @@ LL + u8::try_from((x + 1) >> 1) | error: casting `u16` to `i16` may wrap around the value - --> tests/ui/cast.rs:588:13 + --> tests/ui/cast.rs:599:13 | LL | let x = x as i16 - 0x180; | ^^^^^^^^ @@ -899,7 +913,7 @@ LL | let x = x as i16 - 0x180; = note: the cast operant may contain values in the range `0..=65535` error: casting `i16` to `u16` may lose the sign of the value - --> tests/ui/cast.rs:591:13 + --> tests/ui/cast.rs:602:13 | LL | let x = x.clamp(0, 510) as u16; | ^^^^^^^^^^^^^^^^^^^^^^ @@ -907,7 +921,7 @@ LL | let x = x.clamp(0, 510) as u16; = note: the cast operant may contain values in the range `0..=510` error: casting `u32` to `u8` may truncate the value - --> tests/ui/cast.rs:597:5 + --> tests/ui/cast.rs:608:5 | LL | ((x as u32 * 255 + 32895) >> 16) as u8 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -921,7 +935,7 @@ LL + u8::try_from((x as u32 * 255 + 32895) >> 16) | error: casting `u32` to `u16` may truncate the value - --> tests/ui/cast.rs:618:16 + --> tests/ui/cast.rs:629:16 | LL | return ((sign >> 16) | 0x7C00u32 | nan_bit | (man >> 13)) as u16; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -935,7 +949,7 @@ LL + return u16::try_from((sign >> 16) | 0x7C00u32 | nan_bit | (man >> 1 | error: casting `u32` to `i32` may wrap around the value - --> tests/ui/cast.rs:625:24 + --> tests/ui/cast.rs:636:24 | LL | let unbiased_exp = ((exp >> 23) as i32) - 127; | ^^^^^^^^^^^^^^^^^^^^ @@ -943,7 +957,7 @@ LL | let unbiased_exp = ((exp >> 23) as i32) - 127; = note: the cast operant may contain values in the range `0..=255` error: casting `u32` to `u16` may truncate the value - --> tests/ui/cast.rs:631:16 + --> tests/ui/cast.rs:642:16 | LL | return (half_sign | 0x7C00u32) as u16; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -957,7 +971,7 @@ LL + return u16::try_from(half_sign | 0x7C00u32); | error: casting `u32` to `u16` may truncate the value - --> tests/ui/cast.rs:651:16 + --> tests/ui/cast.rs:662:16 | LL | return (half_sign | half_man) as u16; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -971,7 +985,7 @@ LL + return u16::try_from(half_sign | half_man); | error: casting `i32` to `u32` may lose the sign of the value - --> tests/ui/cast.rs:656:20 + --> tests/ui/cast.rs:667:20 | LL | let half_exp = (half_exp as u32) << 10; | ^^^^^^^^^^^^^^^^^ @@ -979,7 +993,7 @@ LL | let half_exp = (half_exp as u32) << 10; = note: the cast operant may contain values in the range `-112..=143` error: casting `u32` to `u16` may truncate the value - --> tests/ui/cast.rs:663:9 + --> tests/ui/cast.rs:674:9 | LL | ((half_sign | half_exp | half_man) + 1) as u16 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -993,7 +1007,7 @@ LL + u16::try_from((half_sign | half_exp | half_man) + 1) | error: casting `u32` to `u16` may truncate the value - --> tests/ui/cast.rs:666:9 + --> tests/ui/cast.rs:677:9 | LL | (half_sign | half_exp | half_man) as u16 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -1006,5 +1020,5 @@ LL - (half_sign | half_exp | half_man) as u16 LL + u16::try_from(half_sign | half_exp | half_man) | -error: aborting due to 105 previous errors +error: aborting due to 106 previous errors From c06f5dc97b95f895a11266d08660277a98952bc8 Mon Sep 17 00:00:00 2001 From: RunDevelopment Date: Fri, 25 Jul 2025 16:56:50 +0200 Subject: [PATCH 03/13] Removed unused imports --- clippy_utils/src/rinterval/mod.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/clippy_utils/src/rinterval/mod.rs b/clippy_utils/src/rinterval/mod.rs index 70f47e791e84..0e023892be74 100644 --- a/clippy_utils/src/rinterval/mod.rs +++ b/clippy_utils/src/rinterval/mod.rs @@ -12,12 +12,9 @@ pub use arithmetic::*; pub use iinterval::*; use rustc_ast::LitKind; -use rustc_hir::{ - BinOpKind, Block, ConstBlock, Expr, ExprKind, HirId, Item, ItemKind, Node, PatExpr, PatExprKind, PathSegment, - QPath, UnOp, -}; +use rustc_hir::{BinOpKind, Expr, ExprKind, PathSegment, UnOp}; use rustc_lint::LateContext; -use rustc_middle::ty::{IntTy, Ty, TyCtxt, TyKind, TypeckResults, UintTy}; +use rustc_middle::ty::{IntTy, Ty, TyKind, TypeckResults, UintTy}; use rustc_span::Symbol; use crate::consts::{ConstEvalCtxt, Constant}; From cb11ccaa4c02d988a45b69f02dd2e867721a1409 Mon Sep 17 00:00:00 2001 From: RunDevelopment Date: Sat, 2 Aug 2025 16:36:52 +0200 Subject: [PATCH 04/13] Rely solely on range analysis for cast_sign_loss --- .../src/casts/cast_possible_truncation.rs | 13 +- clippy_lints/src/casts/cast_possible_wrap.rs | 13 +- clippy_lints/src/casts/cast_sign_loss.rs | 361 ++-------------- clippy_lints/src/casts/utils.rs | 27 ++ tests/ui/cast.rs | 14 +- tests/ui/cast.stderr | 396 ++++++++---------- tests/ui/cast_size.64bit.stderr | 26 +- 7 files changed, 243 insertions(+), 607 deletions(-) diff --git a/clippy_lints/src/casts/cast_possible_truncation.rs b/clippy_lints/src/casts/cast_possible_truncation.rs index bae9b3e10c04..5a9c97cfe9af 100644 --- a/clippy_lints/src/casts/cast_possible_truncation.rs +++ b/clippy_lints/src/casts/cast_possible_truncation.rs @@ -171,18 +171,7 @@ pub(super) fn check<'cx>( span_lint_and_then(cx, CAST_POSSIBLE_TRUNCATION, expr.span, msg, |diag| { if let Some(from_interval) = from_interval { - let note = if from_interval.min == from_interval.max { - format!( - "the cast operant may assume the value `{}`", - from_interval.to_string_untyped() - ) - } else { - format!( - "the cast operant may contain values in the range `{}`", - from_interval.to_string_untyped() - ) - }; - diag.note(note); + diag.note(utils::format_cast_operand(from_interval)); } diag.help("if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ..."); diff --git a/clippy_lints/src/casts/cast_possible_wrap.rs b/clippy_lints/src/casts/cast_possible_wrap.rs index 8fcd86d05934..a4552dea9d37 100644 --- a/clippy_lints/src/casts/cast_possible_wrap.rs +++ b/clippy_lints/src/casts/cast_possible_wrap.rs @@ -91,18 +91,7 @@ pub(super) fn check<'cx>( span_lint_and_then(cx, CAST_POSSIBLE_WRAP, expr.span, message, |diag| { if let Some(from_interval) = from_interval { - let note = if from_interval.min == from_interval.max { - format!( - "the cast operant may assume the value `{}`", - from_interval.to_string_untyped() - ) - } else { - format!( - "the cast operant may contain values in the range `{}`", - from_interval.to_string_untyped() - ) - }; - diag.note(note); + diag.note(utils::format_cast_operand(from_interval)); } if let EmitState::LintOnPtrSize(16) = should_lint { diff --git a/clippy_lints/src/casts/cast_sign_loss.rs b/clippy_lints/src/casts/cast_sign_loss.rs index d92289fdd7c0..eccb1045c018 100644 --- a/clippy_lints/src/casts/cast_sign_loss.rs +++ b/clippy_lints/src/casts/cast_sign_loss.rs @@ -10,31 +10,7 @@ use rustc_lint::LateContext; use rustc_middle::ty::{self, Ty}; use rustc_span::Symbol; -use super::CAST_SIGN_LOSS; - -/// A list of methods that can never return a negative value. -/// Includes methods that panic rather than returning a negative value. -/// -/// Methods that can overflow and return a negative value must not be included in this list, -/// because casting their return values can still result in sign loss. -const METHODS_RET_POSITIVE: &[Symbol] = &[ - sym::checked_abs, - sym::saturating_abs, - sym::isqrt, - sym::checked_isqrt, - sym::rem_euclid, - sym::checked_rem_euclid, - sym::wrapping_rem_euclid, -]; - -/// A list of methods that act like `pow()`. See `pow_call_result_sign()` for details. -/// -/// Methods that can overflow and return a negative value must not be included in this list, -/// because casting their return values can still result in sign loss. -const METHODS_POW: &[Symbol] = &[sym::pow, sym::saturating_pow, sym::checked_pow]; - -/// A list of methods that act like `unwrap()`, and don't change the sign of the inner value. -const METHODS_UNWRAP: &[Symbol] = &[sym::unwrap, sym::unwrap_unchecked, sym::expect, sym::into_ok]; +use super::{CAST_SIGN_LOSS, utils}; pub(super) fn check<'cx>( cx: &LateContext<'cx>, @@ -43,324 +19,39 @@ pub(super) fn check<'cx>( cast_from: Ty<'cx>, cast_to: Ty<'_>, ) { - if should_lint(cx, cast_op, cast_from, cast_to) { + // the to type has the be an unsigned integer type + if !cast_to.is_integral() || cast_to.is_signed() { + return; + } + + // floating-point values can hold negative numbers that will all map to 0 + if cast_from.is_floating_point() { + span_lint( + cx, + CAST_SIGN_LOSS, + expr.span, + format!("casting `{cast_from}` to `{cast_to}` may lose the sign of the value"), + ); + return; + } + + // Lastly, casting from signed integers to unsigned integers should only be + // reported if the signed integer expression can actually contain negative + // values. + if cast_from.is_integral() && cast_from.is_signed() { let interval_ctx = rinterval::IntervalCtxt::new(cx); - let from_interval = interval_ctx.eval(cast_op); - - if let Some(from_interval) = from_interval { - let note = if from_interval.min == from_interval.max { - format!( - "the cast operant may assume the value `{}`", - from_interval.to_string_untyped() - ) - } else { - format!( - "the cast operant may contain values in the range `{}`", - from_interval.to_string_untyped() - ) - }; - + if let Some(from_interval) = interval_ctx.eval(cast_op) + && from_interval.ty.is_signed() + && from_interval.contains_negative() + { span_lint_and_note( cx, CAST_SIGN_LOSS, expr.span, format!("casting `{cast_from}` to `{cast_to}` may lose the sign of the value"), None, - note, - ); - } else { - span_lint( - cx, - CAST_SIGN_LOSS, - expr.span, - format!("casting `{cast_from}` to `{cast_to}` may lose the sign of the value"), + utils::format_cast_operand(from_interval), ); } } } - -fn should_lint<'cx>(cx: &LateContext<'cx>, cast_op: &Expr<'_>, cast_from: Ty<'cx>, cast_to: Ty<'_>) -> bool { - match (cast_from.is_integral(), cast_to.is_integral()) { - (true, true) => { - if !cast_from.is_signed() || cast_to.is_signed() { - return false; - } - - // Don't lint if `cast_op` is known to be positive, ignoring overflow. - if let Sign::ZeroOrPositive = expr_sign(cx, cast_op, cast_from) { - return false; - } - - if let Sign::ZeroOrPositive = expr_muldiv_sign(cx, cast_op) { - return false; - } - - if let Sign::ZeroOrPositive = expr_add_sign(cx, cast_op) { - return false; - } - - true - }, - - (false, true) => !cast_to.is_signed(), - - (_, _) => false, - } -} - -fn get_const_signed_int_eval<'cx>( - cx: &LateContext<'cx>, - expr: &Expr<'_>, - ty: impl Into>>, -) -> Option { - let ty: Ty<'cx> = ty.into().unwrap_or_else(|| cx.typeck_results().expr_ty(expr)); - - if let Constant::Int(n) = ConstEvalCtxt::new(cx).eval(expr)? - && let ty::Int(ity) = *ty.kind() - { - return Some(sext(cx.tcx, n, ity)); - } - None -} - -fn get_const_unsigned_int_eval<'cx>( - cx: &LateContext<'cx>, - expr: &Expr<'_>, - ty: impl Into>>, -) -> Option { - let ty: Ty<'cx> = ty.into().unwrap_or_else(|| cx.typeck_results().expr_ty(expr)); - - if let Constant::Int(n) = ConstEvalCtxt::new(cx).eval(expr)? - && let ty::Uint(_ity) = *ty.kind() - { - return Some(n); - } - None -} - -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -enum Sign { - ZeroOrPositive, - Negative, - Uncertain, -} - -fn expr_sign<'cx, 'tcx>(cx: &LateContext<'cx>, mut expr: &'tcx Expr<'tcx>, ty: impl Into>>) -> Sign { - // Try evaluate this expr first to see if it's positive - if let Some(val) = get_const_signed_int_eval(cx, expr, ty) { - return if val >= 0 { Sign::ZeroOrPositive } else { Sign::Negative }; - } - if let Some(_val) = get_const_unsigned_int_eval(cx, expr, None) { - return Sign::ZeroOrPositive; - } - - // Calling on methods that always return non-negative values. - if let ExprKind::MethodCall(path, caller, args, ..) = expr.kind { - let mut method_name = path.ident.name; - - // Peel unwrap(), expect(), etc. - while let Some(&found_name) = METHODS_UNWRAP.iter().find(|&name| &method_name == name) - && let Some(arglist) = method_chain_args(expr, &[found_name]) - && let ExprKind::MethodCall(inner_path, recv, ..) = &arglist[0].0.kind - { - // The original type has changed, but we can't use `ty` here anyway, because it has been - // moved. - method_name = inner_path.ident.name; - expr = recv; - } - - if METHODS_POW.contains(&method_name) - && let [arg] = args - { - return pow_call_result_sign(cx, caller, arg); - } else if METHODS_RET_POSITIVE.contains(&method_name) { - return Sign::ZeroOrPositive; - } - } - - Sign::Uncertain -} - -/// Return the sign of the `pow` call's result, ignoring overflow. -/// -/// If the base is positive, the result is always positive. -/// If the exponent is a even number, the result is always positive, -/// Otherwise, if the base is negative, and the exponent is an odd number, the result is always -/// negative. -/// -/// Otherwise, returns [`Sign::Uncertain`]. -fn pow_call_result_sign(cx: &LateContext<'_>, base: &Expr<'_>, exponent: &Expr<'_>) -> Sign { - let base_sign = expr_sign(cx, base, None); - - // Rust's integer pow() functions take an unsigned exponent. - let exponent_val = get_const_unsigned_int_eval(cx, exponent, None); - let exponent_is_even = exponent_val.map(|val| val.is_multiple_of(2)); - - match (base_sign, exponent_is_even) { - // Non-negative bases always return non-negative results, ignoring overflow. - (Sign::ZeroOrPositive, _) | - // Any base raised to an even exponent is non-negative. - // These both hold even if we don't know the value of the base. - (_, Some(true)) - => Sign::ZeroOrPositive, - - // A negative base raised to an odd exponent is non-negative. - (Sign::Negative, Some(false)) => Sign::Negative, - - // Negative/unknown base to an unknown exponent, or unknown base to an odd exponent. - // Could be negative or positive depending on the actual values. - (Sign::Negative | Sign::Uncertain, None) | - (Sign::Uncertain, Some(false)) => Sign::Uncertain, - } -} - -/// Peels binary operators such as [`BinOpKind::Mul`] or [`BinOpKind::Rem`], -/// where the result could always be positive. See [`exprs_with_muldiv_binop_peeled()`] for details. -/// -/// Returns the sign of the list of peeled expressions. -fn expr_muldiv_sign(cx: &LateContext<'_>, expr: &Expr<'_>) -> Sign { - let mut negative_count = 0; - - // Peel off possible binary expressions, for example: - // x * x / y => [x, x, y] - // a % b => [a] - let exprs = exprs_with_muldiv_binop_peeled(expr); - for expr in exprs { - match expr_sign(cx, expr, None) { - Sign::Negative => negative_count += 1, - // A mul/div is: - // - uncertain if there are any uncertain values (because they could be negative or positive), - Sign::Uncertain => return Sign::Uncertain, - Sign::ZeroOrPositive => (), - } - } - - // A mul/div is: - // - negative if there are an odd number of negative values, - // - positive or zero otherwise. - if negative_count % 2 == 1 { - Sign::Negative - } else { - Sign::ZeroOrPositive - } -} - -/// Peels binary operators such as [`BinOpKind::Add`], where the result could always be positive. -/// See [`exprs_with_add_binop_peeled()`] for details. -/// -/// Returns the sign of the list of peeled expressions. -fn expr_add_sign(cx: &LateContext<'_>, expr: &Expr<'_>) -> Sign { - let mut negative_count = 0; - let mut positive_count = 0; - - // Peel off possible binary expressions, for example: - // a + b + c => [a, b, c] - let exprs = exprs_with_add_binop_peeled(expr); - for expr in exprs { - match expr_sign(cx, expr, None) { - Sign::Negative => negative_count += 1, - // A sum is: - // - uncertain if there are any uncertain values (because they could be negative or positive), - Sign::Uncertain => return Sign::Uncertain, - Sign::ZeroOrPositive => positive_count += 1, - } - } - - // A sum is: - // - positive or zero if there are only positive (or zero) values, - // - negative if there are only negative (or zero) values, or - // - uncertain if there are both. - // We could split Zero out into its own variant, but we don't yet. - if negative_count == 0 { - Sign::ZeroOrPositive - } else if positive_count == 0 { - Sign::Negative - } else { - Sign::Uncertain - } -} - -/// Peels binary operators such as [`BinOpKind::Mul`], [`BinOpKind::Div`] or [`BinOpKind::Rem`], -/// where the result depends on: -/// -/// - the number of negative values in the entire expression, or -/// - the number of negative values on the left hand side of the expression. -/// -/// Ignores overflow. -/// -/// -/// Expressions using other operators are preserved, so we can try to evaluate them later. -fn exprs_with_muldiv_binop_peeled<'e>(expr: &'e Expr<'_>) -> Vec<&'e Expr<'e>> { - let mut res = vec![]; - - for_each_expr_without_closures(expr, |sub_expr| -> ControlFlow { - // We don't check for mul/div/rem methods here, but we could. - if let ExprKind::Binary(op, lhs, _rhs) = sub_expr.kind { - if matches!(op.node, BinOpKind::Mul | BinOpKind::Div) { - // For binary operators where both sides contribute to the sign of the result, - // collect all their operands, recursively. This ignores overflow. - ControlFlow::Continue(Descend::Yes) - } else if matches!(op.node, BinOpKind::Rem | BinOpKind::Shr) { - // For binary operators where the left hand side determines the sign of the result, - // only collect that side, recursively. Overflow panics, so this always holds. - // - // Large left shifts turn negatives into zeroes, so we can't use it here. - // - // > Given remainder = dividend % divisor, the remainder will have the same sign as the dividend - // > ... - // > Arithmetic right shift on signed integer types - // https://doc.rust-lang.org/reference/expressions/operator-expr.html#arithmetic-and-logical-binary-operators - - // We want to descend into the lhs, but skip the rhs. - // That's tricky to do using for_each_expr(), so we just keep the lhs intact. - res.push(lhs); - ControlFlow::Continue(Descend::No) - } else { - // The sign of the result of other binary operators depends on the values of the operands, - // so try to evaluate the expression. - res.push(sub_expr); - ControlFlow::Continue(Descend::No) - } - } else { - // For other expressions, including unary operators and constants, try to evaluate the expression. - res.push(sub_expr); - ControlFlow::Continue(Descend::No) - } - }); - - res -} - -/// Peels binary operators such as [`BinOpKind::Add`], where the result depends on: -/// -/// - all the expressions being positive, or -/// - all the expressions being negative. -/// -/// Ignores overflow. -/// -/// Expressions using other operators are preserved, so we can try to evaluate them later. -fn exprs_with_add_binop_peeled<'e>(expr: &'e Expr<'_>) -> Vec<&'e Expr<'e>> { - let mut res = vec![]; - - for_each_expr_without_closures(expr, |sub_expr| -> ControlFlow { - // We don't check for add methods here, but we could. - if let ExprKind::Binary(op, _lhs, _rhs) = sub_expr.kind { - if matches!(op.node, BinOpKind::Add) { - // For binary operators where both sides contribute to the sign of the result, - // collect all their operands, recursively. This ignores overflow. - ControlFlow::Continue(Descend::Yes) - } else { - // The sign of the result of other binary operators depends on the values of the operands, - // so try to evaluate the expression. - res.push(sub_expr); - ControlFlow::Continue(Descend::No) - } - } else { - // For other expressions, including unary operators and constants, try to evaluate the expression. - res.push(sub_expr); - ControlFlow::Continue(Descend::No) - } - }); - - res -} diff --git a/clippy_lints/src/casts/utils.rs b/clippy_lints/src/casts/utils.rs index d846d78b9ee7..6e595b16bd18 100644 --- a/clippy_lints/src/casts/utils.rs +++ b/clippy_lints/src/casts/utils.rs @@ -1,3 +1,4 @@ +use clippy_utils::rinterval::IInterval; use clippy_utils::ty::{EnumValue, read_explicit_enum_value}; use rustc_middle::ty::{self, AdtDef, IntTy, Ty, TyCtxt, UintTy, VariantDiscr}; @@ -60,3 +61,29 @@ pub(super) fn enum_ty_to_nbits(adt: AdtDef<'_>, tcx: TyCtxt<'_>) -> u64 { neg_bits.max(pos_bits).into() } } + +pub(super) fn format_cast_operand(range: IInterval) -> String { + if range.is_empty() { + // This is the weird edge cast where we know that the cast will never + // actually happen, because it's unreachable. + return "the cast operand is unreachable".to_string(); + } + + if range.ty.is_signed() { + let (min, max) = range.as_signed(); + + if min == max { + format!("the cast operand is `{min}`") + } else { + format!("the cast operand may contain values in the range `{min}..={max}`") + } + } else { + let (min, max) = range.as_unsigned(); + + if min == max { + format!("the cast operand is `{min}`") + } else { + format!("the cast operand may contain values in the range `{min}..={max}`") + } + } +} diff --git a/tests/ui/cast.rs b/tests/ui/cast.rs index ef67b8a67d2b..88f66492f3e1 100644 --- a/tests/ui/cast.rs +++ b/tests/ui/cast.rs @@ -16,7 +16,10 @@ )] // FIXME(f16_f128): add tests once const casting is available for these types -fn get_value() -> T { todo!() } +fn get_value() -> T { + todo!() +} + fn main() { // Test clippy::cast_precision_loss let x0: i32 = get_value(); @@ -157,9 +160,7 @@ fn main() { (-1i16).saturating_abs() as u16; (-1i32).saturating_abs() as u32; (-1i64).abs() as u64; - //~^ cast_sign_loss (-1isize).abs() as usize; - //~^ cast_sign_loss (-1i8).checked_abs().unwrap() as u8; (i8::MIN).checked_abs().unwrap() as u8; @@ -167,7 +168,6 @@ fn main() { (-1i32).checked_abs().unwrap() as u32; // SAFETY: -1 is a small number which will always return Some (unsafe { (-1i64).checked_abs().unwrap_unchecked() }) as u64; - //~^ cast_sign_loss (-1isize).checked_abs().expect("-1 is a small number") as usize; (-1i8).isqrt() as u8; @@ -181,9 +181,8 @@ fn main() { (i8::MIN).checked_isqrt().unwrap() as u8; (-1i16).checked_isqrt().unwrap() as u16; (-1i32).checked_isqrt().unwrap() as u32; - // SAFETY: -1 is a small number which will always return Some + // SAFETY: this is UB, but whatever (unsafe { (-1i64).checked_isqrt().unwrap_unchecked() }) as u64; - //~^ cast_sign_loss (-1isize).checked_isqrt().expect("-1 is a small number") as usize; (-1i8).rem_euclid(1i8) as u8; @@ -445,7 +444,6 @@ fn issue11642() { //~^ cast_sign_loss (2_i32).checked_pow(3).unwrap() as u32; - //~^ cast_sign_loss (-2_i32).pow(3) as u32; //~^ cast_sign_loss @@ -588,7 +586,6 @@ fn dxgi_xr10_to_unorm8(x: u16) -> u8 { //~^ cast_possible_wrap // new range: [0, 510] (or [0.0, 1.0]) let x = x.clamp(0, 510) as u16; - //~^ cast_sign_loss // this is round(x / 510 * 255), but faster ((x + 1) >> 1) as u8 //~^ cast_possible_truncation @@ -600,7 +597,6 @@ fn dxgi_xr10_to_unorm16(x: u16) -> u16 { //~^ cast_possible_wrap // new range: [0, 510] (or [0.0, 1.0]) let x = x.clamp(0, 510) as u16; - //~^ cast_sign_loss // this is round(x / 510 * 65535), but faster ((x as u32 * 8421376 + 65535) >> 16) as u16 } diff --git a/tests/ui/cast.stderr b/tests/ui/cast.stderr index 3901a8be86e9..e263efa5984e 100644 --- a/tests/ui/cast.stderr +++ b/tests/ui/cast.stderr @@ -1,5 +1,5 @@ error: casting `i32` to `f32` causes a loss of precision (`i32` is 32 bits wide, but `f32`'s mantissa is only 23 bits wide) - --> tests/ui/cast.rs:23:5 + --> tests/ui/cast.rs:26:5 | LL | x0 as f32; | ^^^^^^^^^ @@ -8,37 +8,37 @@ LL | x0 as f32; = help: to override `-D warnings` add `#[allow(clippy::cast_precision_loss)]` error: casting `i64` to `f32` causes a loss of precision (`i64` is 64 bits wide, but `f32`'s mantissa is only 23 bits wide) - --> tests/ui/cast.rs:27:5 + --> tests/ui/cast.rs:30:5 | LL | x1 as f32; | ^^^^^^^^^ error: casting `i64` to `f64` causes a loss of precision (`i64` is 64 bits wide, but `f64`'s mantissa is only 52 bits wide) - --> tests/ui/cast.rs:30:5 + --> tests/ui/cast.rs:33:5 | LL | x1 as f64; | ^^^^^^^^^ error: casting `u32` to `f32` causes a loss of precision (`u32` is 32 bits wide, but `f32`'s mantissa is only 23 bits wide) - --> tests/ui/cast.rs:34:5 + --> tests/ui/cast.rs:37:5 | LL | x2 as f32; | ^^^^^^^^^ error: casting `u64` to `f32` causes a loss of precision (`u64` is 64 bits wide, but `f32`'s mantissa is only 23 bits wide) - --> tests/ui/cast.rs:38:5 + --> tests/ui/cast.rs:41:5 | LL | x3 as f32; | ^^^^^^^^^ error: casting `u64` to `f64` causes a loss of precision (`u64` is 64 bits wide, but `f64`'s mantissa is only 52 bits wide) - --> tests/ui/cast.rs:41:5 + --> tests/ui/cast.rs:44:5 | LL | x3 as f64; | ^^^^^^^^^ error: casting `f32` to `i32` may truncate the value - --> tests/ui/cast.rs:45:5 + --> tests/ui/cast.rs:48:5 | LL | 1f32 as i32; | ^^^^^^^^^^^ @@ -48,7 +48,7 @@ LL | 1f32 as i32; = help: to override `-D warnings` add `#[allow(clippy::cast_possible_truncation)]` error: casting `f32` to `u32` may truncate the value - --> tests/ui/cast.rs:48:5 + --> tests/ui/cast.rs:51:5 | LL | 1f32 as u32; | ^^^^^^^^^^^ @@ -56,7 +56,7 @@ LL | 1f32 as u32; = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... error: casting `f32` to `u32` may lose the sign of the value - --> tests/ui/cast.rs:48:5 + --> tests/ui/cast.rs:51:5 | LL | 1f32 as u32; | ^^^^^^^^^^^ @@ -65,7 +65,7 @@ LL | 1f32 as u32; = help: to override `-D warnings` add `#[allow(clippy::cast_sign_loss)]` error: casting `f64` to `f32` may truncate the value - --> tests/ui/cast.rs:52:5 + --> tests/ui/cast.rs:55:5 | LL | 1f64 as f32; | ^^^^^^^^^^^ @@ -73,12 +73,12 @@ LL | 1f64 as f32; = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... error: casting `i32` to `i8` may truncate the value - --> tests/ui/cast.rs:55:5 + --> tests/ui/cast.rs:58:5 | LL | 1i32 as i8; | ^^^^^^^^^^ | - = note: the cast operant may assume the value `1` + = note: the cast operand is `1` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | @@ -87,12 +87,12 @@ LL + i8::try_from(1i32); | error: casting `i32` to `u8` may truncate the value - --> tests/ui/cast.rs:58:5 + --> tests/ui/cast.rs:61:5 | LL | 1i32 as u8; | ^^^^^^^^^^ | - = note: the cast operant may assume the value `1` + = note: the cast operand is `1` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | @@ -101,7 +101,7 @@ LL + u8::try_from(1i32); | error: casting `f64` to `isize` may truncate the value - --> tests/ui/cast.rs:61:5 + --> tests/ui/cast.rs:64:5 | LL | 1f64 as isize; | ^^^^^^^^^^^^^ @@ -109,7 +109,7 @@ LL | 1f64 as isize; = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... error: casting `f64` to `usize` may truncate the value - --> tests/ui/cast.rs:64:5 + --> tests/ui/cast.rs:67:5 | LL | 1f64 as usize; | ^^^^^^^^^^^^^ @@ -117,18 +117,18 @@ LL | 1f64 as usize; = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... error: casting `f64` to `usize` may lose the sign of the value - --> tests/ui/cast.rs:64:5 + --> tests/ui/cast.rs:67:5 | LL | 1f64 as usize; | ^^^^^^^^^^^^^ error: casting `u32` to `u16` may truncate the value - --> tests/ui/cast.rs:68:5 + --> tests/ui/cast.rs:71:5 | LL | 1f32 as u32 as u16; | ^^^^^^^^^^^^^^^^^^ | - = note: the cast operant may contain values in the range `0..=4294967295` + = note: the cast operand may contain values in the range `0..=4294967295` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | @@ -137,7 +137,7 @@ LL + u16::try_from(1f32 as u32); | error: casting `f32` to `u32` may truncate the value - --> tests/ui/cast.rs:68:5 + --> tests/ui/cast.rs:71:5 | LL | 1f32 as u32 as u16; | ^^^^^^^^^^^ @@ -145,18 +145,18 @@ LL | 1f32 as u32 as u16; = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... error: casting `f32` to `u32` may lose the sign of the value - --> tests/ui/cast.rs:68:5 + --> tests/ui/cast.rs:71:5 | LL | 1f32 as u32 as u16; | ^^^^^^^^^^^ error: casting `i32` to `i8` may truncate the value - --> tests/ui/cast.rs:74:22 + --> tests/ui/cast.rs:77:22 | LL | let _x: i8 = 1i32 as _; | ^^^^^^^^^ | - = note: the cast operant may assume the value `1` + = note: the cast operand is `1` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | @@ -165,7 +165,7 @@ LL + let _x: i8 = 1i32.try_into(); | error: casting `f32` to `i32` may truncate the value - --> tests/ui/cast.rs:77:9 + --> tests/ui/cast.rs:80:9 | LL | 1f32 as i32; | ^^^^^^^^^^^ @@ -173,7 +173,7 @@ LL | 1f32 as i32; = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... error: casting `f64` to `i32` may truncate the value - --> tests/ui/cast.rs:80:9 + --> tests/ui/cast.rs:83:9 | LL | 1f64 as i32; | ^^^^^^^^^^^ @@ -181,7 +181,7 @@ LL | 1f64 as i32; = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... error: casting `f32` to `u8` may truncate the value - --> tests/ui/cast.rs:83:9 + --> tests/ui/cast.rs:86:9 | LL | 1f32 as u8; | ^^^^^^^^^^ @@ -189,60 +189,60 @@ LL | 1f32 as u8; = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... error: casting `f32` to `u8` may lose the sign of the value - --> tests/ui/cast.rs:83:9 + --> tests/ui/cast.rs:86:9 | LL | 1f32 as u8; | ^^^^^^^^^^ error: casting `u8` to `i8` may wrap around the value - --> tests/ui/cast.rs:88:5 + --> tests/ui/cast.rs:91:5 | LL | 1u8 as i8; | ^^^^^^^^^ | - = note: the cast operant may assume the value `1` + = note: the cast operand is `1` = note: `-D clippy::cast-possible-wrap` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::cast_possible_wrap)]` error: casting `u16` to `i16` may wrap around the value - --> tests/ui/cast.rs:91:5 + --> tests/ui/cast.rs:94:5 | LL | 1u16 as i16; | ^^^^^^^^^^^ | - = note: the cast operant may assume the value `1` + = note: the cast operand is `1` error: casting `u32` to `i32` may wrap around the value - --> tests/ui/cast.rs:94:5 + --> tests/ui/cast.rs:97:5 | LL | 1u32 as i32; | ^^^^^^^^^^^ | - = note: the cast operant may assume the value `1` + = note: the cast operand is `1` error: casting `u64` to `i64` may wrap around the value - --> tests/ui/cast.rs:97:5 + --> tests/ui/cast.rs:100:5 | LL | 1u64 as i64; | ^^^^^^^^^^^ | - = note: the cast operant may assume the value `1` + = note: the cast operand is `1` error: casting `usize` to `isize` may wrap around the value - --> tests/ui/cast.rs:100:5 + --> tests/ui/cast.rs:103:5 | LL | 1usize as isize; | ^^^^^^^^^^^^^^^ | - = note: the cast operant may assume the value `1` + = note: the cast operand is `1` error: casting `usize` to `i8` may truncate the value - --> tests/ui/cast.rs:104:5 + --> tests/ui/cast.rs:107:5 | LL | 1usize as i8; | ^^^^^^^^^^^^ | - = note: the cast operant may assume the value `1` + = note: the cast operand is `1` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | @@ -251,12 +251,12 @@ LL + i8::try_from(1usize); | error: casting `usize` to `i16` may truncate the value - --> tests/ui/cast.rs:108:5 + --> tests/ui/cast.rs:111:5 | LL | 1usize as i16; | ^^^^^^^^^^^^^ | - = note: the cast operant may assume the value `1` + = note: the cast operand is `1` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | @@ -265,22 +265,22 @@ LL + i16::try_from(1usize); | error: casting `usize` to `i16` may wrap around the value on targets with 16-bit wide pointers - --> tests/ui/cast.rs:108:5 + --> tests/ui/cast.rs:111:5 | LL | 1usize as i16; | ^^^^^^^^^^^^^ | - = note: the cast operant may assume the value `1` + = note: the cast operand is `1` = note: `usize` and `isize` may be as small as 16 bits on some platforms = note: for more information see https://doc.rust-lang.org/reference/types/numeric.html#machine-dependent-integer-types error: casting `usize` to `i32` may truncate the value on targets with 64-bit wide pointers - --> tests/ui/cast.rs:113:5 + --> tests/ui/cast.rs:116:5 | LL | 1usize as i32; | ^^^^^^^^^^^^^ | - = note: the cast operant may assume the value `1` + = note: the cast operand is `1` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | @@ -289,46 +289,46 @@ LL + i32::try_from(1usize); | error: casting `usize` to `i32` may wrap around the value on targets with 32-bit wide pointers - --> tests/ui/cast.rs:113:5 + --> tests/ui/cast.rs:116:5 | LL | 1usize as i32; | ^^^^^^^^^^^^^ | - = note: the cast operant may assume the value `1` + = note: the cast operand is `1` error: casting `usize` to `i64` may wrap around the value on targets with 64-bit wide pointers - --> tests/ui/cast.rs:118:5 + --> tests/ui/cast.rs:121:5 | LL | 1usize as i64; | ^^^^^^^^^^^^^ | - = note: the cast operant may assume the value `1` + = note: the cast operand is `1` error: casting `u16` to `isize` may wrap around the value on targets with 16-bit wide pointers - --> tests/ui/cast.rs:124:5 + --> tests/ui/cast.rs:127:5 | LL | 1u16 as isize; | ^^^^^^^^^^^^^ | - = note: the cast operant may assume the value `1` + = note: the cast operand is `1` = note: `usize` and `isize` may be as small as 16 bits on some platforms = note: for more information see https://doc.rust-lang.org/reference/types/numeric.html#machine-dependent-integer-types error: casting `u32` to `isize` may wrap around the value on targets with 32-bit wide pointers - --> tests/ui/cast.rs:128:5 + --> tests/ui/cast.rs:131:5 | LL | 1u32 as isize; | ^^^^^^^^^^^^^ | - = note: the cast operant may assume the value `1` + = note: the cast operand is `1` error: casting `u64` to `isize` may truncate the value on targets with 32-bit wide pointers - --> tests/ui/cast.rs:132:5 + --> tests/ui/cast.rs:135:5 | LL | 1u64 as isize; | ^^^^^^^^^^^^^ | - = note: the cast operant may assume the value `1` + = note: the cast operand is `1` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | @@ -337,76 +337,44 @@ LL + isize::try_from(1u64); | error: casting `u64` to `isize` may wrap around the value on targets with 64-bit wide pointers - --> tests/ui/cast.rs:132:5 + --> tests/ui/cast.rs:135:5 | LL | 1u64 as isize; | ^^^^^^^^^^^^^ | - = note: the cast operant may assume the value `1` + = note: the cast operand is `1` error: casting `i32` to `u32` may lose the sign of the value - --> tests/ui/cast.rs:138:5 + --> tests/ui/cast.rs:141:5 | LL | -1i32 as u32; | ^^^^^^^^^^^^ | - = note: the cast operant may assume the value `-1` + = note: the cast operand is `-1` error: casting `isize` to `usize` may lose the sign of the value - --> tests/ui/cast.rs:142:5 + --> tests/ui/cast.rs:145:5 | LL | -1isize as usize; | ^^^^^^^^^^^^^^^^ | - = note: the cast operant may assume the value `-1` + = note: the cast operand is `-1` error: casting `i8` to `u8` may lose the sign of the value - --> tests/ui/cast.rs:154:5 + --> tests/ui/cast.rs:157:5 | LL | (i8::MIN).abs() as u8; | ^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operant may assume the value `-128` - -error: casting `i64` to `u64` may lose the sign of the value - --> tests/ui/cast.rs:159:5 - | -LL | (-1i64).abs() as u64; - | ^^^^^^^^^^^^^^^^^^^^ - | - = note: the cast operant may assume the value `1` - -error: casting `isize` to `usize` may lose the sign of the value - --> tests/ui/cast.rs:161:5 - | -LL | (-1isize).abs() as usize; - | ^^^^^^^^^^^^^^^^^^^^^^^^ - | - = note: the cast operant may assume the value `1` - -error: casting `i64` to `u64` may lose the sign of the value - --> tests/ui/cast.rs:169:5 - | -LL | (unsafe { (-1i64).checked_abs().unwrap_unchecked() }) as u64; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = note: the cast operant may assume the value `1` - -error: casting `i64` to `u64` may lose the sign of the value - --> tests/ui/cast.rs:185:5 - | -LL | (unsafe { (-1i64).checked_isqrt().unwrap_unchecked() }) as u64; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = note: the cast operant may contain values in the range `` + = note: the cast operand is `-128` error: casting `i64` to `i8` may truncate the value - --> tests/ui/cast.rs:237:5 + --> tests/ui/cast.rs:236:5 | LL | (-99999999999i64).min(1) as i8; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operant may assume the value `-99999999999` + = note: the cast operand is `-99999999999` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | @@ -415,12 +383,12 @@ LL + i8::try_from((-99999999999i64).min(1)); | error: casting `u64` to `u8` may truncate the value - --> tests/ui/cast.rs:251:5 + --> tests/ui/cast.rs:250:5 | LL | 999999u64.clamp(0, 256) as u8; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operant may assume the value `256` + = note: the cast operand is `256` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | @@ -429,7 +397,7 @@ LL + u8::try_from(999999u64.clamp(0, 256)); | error: casting `main::E2` to `u8` may truncate the value - --> tests/ui/cast.rs:274:21 + --> tests/ui/cast.rs:273:21 | LL | let _ = self as u8; | ^^^^^^^^^^ @@ -442,7 +410,7 @@ LL + let _ = u8::try_from(self); | error: casting `main::E2::B` to `u8` will truncate the value - --> tests/ui/cast.rs:277:21 + --> tests/ui/cast.rs:276:21 | LL | let _ = Self::B as u8; | ^^^^^^^^^^^^^ @@ -451,7 +419,7 @@ LL | let _ = Self::B as u8; = help: to override `-D warnings` add `#[allow(clippy::cast_enum_truncation)]` error: casting `main::E5` to `i8` may truncate the value - --> tests/ui/cast.rs:319:21 + --> tests/ui/cast.rs:318:21 | LL | let _ = self as i8; | ^^^^^^^^^^ @@ -464,13 +432,13 @@ LL + let _ = i8::try_from(self); | error: casting `main::E5::A` to `i8` will truncate the value - --> tests/ui/cast.rs:322:21 + --> tests/ui/cast.rs:321:21 | LL | let _ = Self::A as i8; | ^^^^^^^^^^^^^ error: casting `main::E6` to `i16` may truncate the value - --> tests/ui/cast.rs:340:21 + --> tests/ui/cast.rs:339:21 | LL | let _ = self as i16; | ^^^^^^^^^^^ @@ -483,7 +451,7 @@ LL + let _ = i16::try_from(self); | error: casting `main::E7` to `usize` may truncate the value on targets with 32-bit wide pointers - --> tests/ui/cast.rs:360:21 + --> tests/ui/cast.rs:359:21 | LL | let _ = self as usize; | ^^^^^^^^^^^^^ @@ -496,7 +464,7 @@ LL + let _ = usize::try_from(self); | error: casting `main::E10` to `u16` may truncate the value - --> tests/ui/cast.rs:408:21 + --> tests/ui/cast.rs:407:21 | LL | let _ = self as u16; | ^^^^^^^^^^^ @@ -509,12 +477,12 @@ LL + let _ = u16::try_from(self); | error: casting `u32` to `u8` may truncate the value - --> tests/ui/cast.rs:420:13 + --> tests/ui/cast.rs:419:13 | LL | let c = (q >> 16) as u8; | ^^^^^^^^^^^^^^^ | - = note: the cast operant may contain values in the range `0..=65535` + = note: the cast operand may contain values in the range `0..=65535` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | @@ -523,12 +491,12 @@ LL + let c = u8::try_from(q >> 16); | error: casting `u32` to `u8` may truncate the value - --> tests/ui/cast.rs:425:13 + --> tests/ui/cast.rs:424:13 | LL | let c = (q / 1000) as u8; | ^^^^^^^^^^^^^^^^ | - = note: the cast operant may contain values in the range `0..=4294967` + = note: the cast operand may contain values in the range `0..=4294967` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | @@ -537,111 +505,103 @@ LL + let c = u8::try_from(q / 1000); | error: casting `i32` to `u32` may lose the sign of the value - --> tests/ui/cast.rs:438:9 + --> tests/ui/cast.rs:437:9 | LL | (x * x) as u32; | ^^^^^^^^^^^^^^ | - = note: the cast operant may contain values in the range `-1073709056..=1073741824` + = note: the cast operand may contain values in the range `-1073709056..=1073741824` error: casting `i32` to `u32` may lose the sign of the value - --> tests/ui/cast.rs:444:32 + --> tests/ui/cast.rs:443:32 | LL | let _a = |x: i32| -> u32 { (x * x * x * x) as u32 }; | ^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operant may contain values in the range `-2147483648..=2147483647` + = note: the cast operand may contain values in the range `-2147483648..=2147483647` error: casting `i32` to `u32` may lose the sign of the value --> tests/ui/cast.rs:447:5 | -LL | (2_i32).checked_pow(3).unwrap() as u32; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = note: the cast operant may assume the value `8` - -error: casting `i32` to `u32` may lose the sign of the value - --> tests/ui/cast.rs:449:5 - | LL | (-2_i32).pow(3) as u32; | ^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operant may assume the value `-8` + = note: the cast operand is `-8` error: casting `i32` to `u32` may lose the sign of the value - --> tests/ui/cast.rs:454:5 + --> tests/ui/cast.rs:452:5 | LL | (-5_i32 % 2) as u32; | ^^^^^^^^^^^^^^^^^^^ | - = note: the cast operant may assume the value `-1` + = note: the cast operand is `-1` error: casting `i32` to `u32` may lose the sign of the value - --> tests/ui/cast.rs:457:5 + --> tests/ui/cast.rs:455:5 | LL | (-5_i32 % -2) as u32; | ^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operant may assume the value `-1` + = note: the cast operand is `-1` error: casting `i32` to `u32` may lose the sign of the value - --> tests/ui/cast.rs:461:5 + --> tests/ui/cast.rs:459:5 | LL | (-2_i32 >> 1) as u32; | ^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operant may assume the value `-1` + = note: the cast operand is `-1` error: casting `i32` to `u32` may lose the sign of the value - --> tests/ui/cast.rs:465:5 + --> tests/ui/cast.rs:463:5 | LL | (x * x) as u32; | ^^^^^^^^^^^^^^ | - = note: the cast operant may contain values in the range `-2147483648..=2147483647` + = note: the cast operand may contain values in the range `-2147483648..=2147483647` error: casting `i32` to `u32` may lose the sign of the value - --> tests/ui/cast.rs:467:5 + --> tests/ui/cast.rs:465:5 | LL | (x * x * x) as u32; | ^^^^^^^^^^^^^^^^^^ | - = note: the cast operant may contain values in the range `-2147483648..=2147483647` + = note: the cast operand may contain values in the range `-2147483648..=2147483647` error: casting `i16` to `u16` may lose the sign of the value - --> tests/ui/cast.rs:471:5 + --> tests/ui/cast.rs:469:5 | LL | (y * y * y * y * -2) as u16; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operant may contain values in the range `-32768..=32767` + = note: the cast operand may contain values in the range `-32768..=32767` error: casting `i16` to `u16` may lose the sign of the value - --> tests/ui/cast.rs:474:5 + --> tests/ui/cast.rs:472:5 | LL | (y * y * y / y * 2) as u16; | ^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operant may contain values in the range `-32768..=32767` + = note: the cast operand may contain values in the range `-32768..=32767` error: casting `i16` to `u16` may lose the sign of the value - --> tests/ui/cast.rs:476:5 + --> tests/ui/cast.rs:474:5 | LL | (y * y / y * 2) as u16; | ^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operant may contain values in the range `-32768..=32767` + = note: the cast operand may contain values in the range `-32768..=32767` error: casting `i16` to `u16` may lose the sign of the value - --> tests/ui/cast.rs:479:5 + --> tests/ui/cast.rs:477:5 | LL | (y / y * y * -2) as u16; | ^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operant may contain values in the range `-32768..=32767` + = note: the cast operand may contain values in the range `-32768..=32767` error: equal expressions as operands to `/` - --> tests/ui/cast.rs:479:6 + --> tests/ui/cast.rs:477:6 | LL | (y / y * y * -2) as u16; | ^^^^^ @@ -649,127 +609,127 @@ LL | (y / y * y * -2) as u16; = note: `#[deny(clippy::eq_op)]` on by default error: casting `i16` to `u16` may lose the sign of the value - --> tests/ui/cast.rs:483:5 + --> tests/ui/cast.rs:481:5 | LL | (y + y + y + -2) as u16; | ^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operant may contain values in the range `-32768..=32767` + = note: the cast operand may contain values in the range `-32768..=32767` error: casting `i16` to `u16` may lose the sign of the value - --> tests/ui/cast.rs:486:5 + --> tests/ui/cast.rs:484:5 | LL | (y + y + y + 2) as u16; | ^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operant may contain values in the range `-32768..=32767` + = note: the cast operand may contain values in the range `-32768..=32767` error: casting `i16` to `u16` may lose the sign of the value - --> tests/ui/cast.rs:490:5 + --> tests/ui/cast.rs:488:5 | LL | (z + -2) as u16; | ^^^^^^^^^^^^^^^ | - = note: the cast operant may contain values in the range `-32768..=32767` + = note: the cast operand may contain values in the range `-32768..=32767` error: casting `i16` to `u16` may lose the sign of the value - --> tests/ui/cast.rs:493:5 + --> tests/ui/cast.rs:491:5 | LL | (z + z + 2) as u16; | ^^^^^^^^^^^^^^^^^^ | - = note: the cast operant may contain values in the range `-32768..=32767` + = note: the cast operand may contain values in the range `-32768..=32767` error: casting `i32` to `u32` may lose the sign of the value - --> tests/ui/cast.rs:497:9 + --> tests/ui/cast.rs:495:9 | LL | (a * a * b * b * c * c) as u32; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operant may contain values in the range `-2147483648..=2147483647` + = note: the cast operand may contain values in the range `-2147483648..=2147483647` error: casting `i32` to `u32` may lose the sign of the value - --> tests/ui/cast.rs:499:9 + --> tests/ui/cast.rs:497:9 | LL | (a * b * c) as u32; | ^^^^^^^^^^^^^^^^^^ | - = note: the cast operant may contain values in the range `-2147483648..=2147483647` + = note: the cast operand may contain values in the range `-2147483648..=2147483647` error: casting `i32` to `u32` may lose the sign of the value - --> tests/ui/cast.rs:502:9 + --> tests/ui/cast.rs:500:9 | LL | (a * -b * c) as u32; | ^^^^^^^^^^^^^^^^^^^ | - = note: the cast operant may contain values in the range `-2147483648..=2147483647` + = note: the cast operand may contain values in the range `-2147483648..=2147483647` error: casting `i32` to `u32` may lose the sign of the value - --> tests/ui/cast.rs:505:9 + --> tests/ui/cast.rs:503:9 | LL | (a * b * c * c) as u32; | ^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operant may contain values in the range `-2147483648..=2147483647` + = note: the cast operand may contain values in the range `-2147483648..=2147483647` error: casting `i32` to `u32` may lose the sign of the value - --> tests/ui/cast.rs:507:9 + --> tests/ui/cast.rs:505:9 | LL | (a * -2) as u32; | ^^^^^^^^^^^^^^^ | - = note: the cast operant may contain values in the range `-2147483648..=2147483647` + = note: the cast operand may contain values in the range `-2147483648..=2147483647` error: casting `i32` to `u32` may lose the sign of the value - --> tests/ui/cast.rs:510:9 + --> tests/ui/cast.rs:508:9 | LL | (a * b * c * -2) as u32; | ^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operant may contain values in the range `-2147483648..=2147483647` + = note: the cast operand may contain values in the range `-2147483648..=2147483647` error: casting `i32` to `u32` may lose the sign of the value - --> tests/ui/cast.rs:513:9 + --> tests/ui/cast.rs:511:9 | LL | (a / b) as u32; | ^^^^^^^^^^^^^^ | - = note: the cast operant may contain values in the range `-2147483648..=2147483647` + = note: the cast operand may contain values in the range `-2147483648..=2147483647` error: casting `i32` to `u32` may lose the sign of the value - --> tests/ui/cast.rs:515:9 + --> tests/ui/cast.rs:513:9 | LL | (a / b * c) as u32; | ^^^^^^^^^^^^^^^^^^ | - = note: the cast operant may contain values in the range `-2147483648..=2147483647` + = note: the cast operand may contain values in the range `-2147483648..=2147483647` error: casting `i32` to `u32` may lose the sign of the value - --> tests/ui/cast.rs:518:9 + --> tests/ui/cast.rs:516:9 | LL | (a / b + b * c) as u32; | ^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operant may contain values in the range `-2147483648..=2147483647` + = note: the cast operand may contain values in the range `-2147483648..=2147483647` error: casting `i32` to `u32` may lose the sign of the value - --> tests/ui/cast.rs:521:9 + --> tests/ui/cast.rs:519:9 | LL | a.saturating_pow(3) as u32; | ^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operant may contain values in the range `-2147483648..=2147483647` + = note: the cast operand may contain values in the range `-2147483648..=2147483647` error: casting `i32` to `u32` may lose the sign of the value - --> tests/ui/cast.rs:524:9 + --> tests/ui/cast.rs:522:9 | LL | (a.abs() * b.pow(2) / c.abs()) as u32 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operant may contain values in the range `-2147483648..=2147483647` + = note: the cast operand may contain values in the range `-2147483648..=2147483647` error: casting `i32` to `u32` may lose the sign of the value - --> tests/ui/cast.rs:532:21 + --> tests/ui/cast.rs:530:21 | LL | let _ = i32::MIN as u32; // cast_sign_loss | ^^^^^^^^^^^^^^^ @@ -777,11 +737,11 @@ LL | let _ = i32::MIN as u32; // cast_sign_loss LL | m!(); | ---- in this macro invocation | - = note: the cast operant may assume the value `-2147483648` + = note: the cast operand is `-2147483648` = note: this error originates in the macro `m` (in Nightly builds, run with -Z macro-backtrace for more info) error: casting `u32` to `u8` may truncate the value - --> tests/ui/cast.rs:535:21 + --> tests/ui/cast.rs:533:21 | LL | let _ = u32::MAX as u8; // cast_possible_truncation | ^^^^^^^^^^^^^^ @@ -789,7 +749,7 @@ LL | let _ = u32::MAX as u8; // cast_possible_truncation LL | m!(); | ---- in this macro invocation | - = note: the cast operant may assume the value `4294967295` + = note: the cast operand is `4294967295` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... = note: this error originates in the macro `m` (in Nightly builds, run with -Z macro-backtrace for more info) help: ... or use `try_from` and handle the error accordingly @@ -799,7 +759,7 @@ LL + let _ = u8::try_from(u32::MAX); // cast_possible_truncation | error: casting `f64` to `f32` may truncate the value - --> tests/ui/cast.rs:538:21 + --> tests/ui/cast.rs:536:21 | LL | let _ = std::f64::consts::PI as f32; // cast_possible_truncation | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -811,12 +771,12 @@ LL | m!(); = note: this error originates in the macro `m` (in Nightly builds, run with -Z macro-backtrace for more info) error: casting `i64` to `usize` may truncate the value on targets with 32-bit wide pointers - --> tests/ui/cast.rs:549:5 + --> tests/ui/cast.rs:547:5 | LL | bar.unwrap().unwrap() as usize | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operant may contain values in the range `-9223372036854775808..=9223372036854775807` + = note: the cast operand may contain values in the range `-9223372036854775808..=9223372036854775807` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | @@ -825,20 +785,20 @@ LL + usize::try_from(bar.unwrap().unwrap()) | error: casting `i64` to `usize` may lose the sign of the value - --> tests/ui/cast.rs:549:5 + --> tests/ui/cast.rs:547:5 | LL | bar.unwrap().unwrap() as usize | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operant may contain values in the range `-9223372036854775808..=9223372036854775807` + = note: the cast operand may contain values in the range `-9223372036854775808..=9223372036854775807` error: casting `u64` to `u8` may truncate the value - --> tests/ui/cast.rs:566:5 + --> tests/ui/cast.rs:564:5 | LL | (256 & 999999u64) as u8; | ^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operant may assume the value `0` + = note: the cast operand is `0` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | @@ -847,12 +807,12 @@ LL + u8::try_from(256 & 999999u64); | error: casting `u64` to `u8` may truncate the value - --> tests/ui/cast.rs:569:5 + --> tests/ui/cast.rs:567:5 | LL | (255 % 999999u64) as u8; | ^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operant may assume the value `255` + = note: the cast operand is `255` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | @@ -861,12 +821,12 @@ LL + u8::try_from(255 % 999999u64); | error: casting `u64` to `u32` may truncate the value - --> tests/ui/cast.rs:580:5 + --> tests/ui/cast.rs:578:5 | LL | result as u32 | ^^^^^^^^^^^^^ | - = note: the cast operant may contain values in the range `0..=4294967294` + = note: the cast operand may contain values in the range `0..=4294967294` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | @@ -875,28 +835,20 @@ LL + u32::try_from(result) | error: casting `u16` to `i16` may wrap around the value - --> tests/ui/cast.rs:587:13 + --> tests/ui/cast.rs:585:13 | LL | let x = x as i16 - 0x180; | ^^^^^^^^ | - = note: the cast operant may contain values in the range `0..=65535` - -error: casting `i16` to `u16` may lose the sign of the value - --> tests/ui/cast.rs:590:13 - | -LL | let x = x.clamp(0, 510) as u16; - | ^^^^^^^^^^^^^^^^^^^^^^ - | - = note: the cast operant may contain values in the range `0..=510` + = note: the cast operand may contain values in the range `0..=65535` error: casting `u16` to `u8` may truncate the value - --> tests/ui/cast.rs:593:5 + --> tests/ui/cast.rs:590:5 | LL | ((x + 1) >> 1) as u8 | ^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operant may contain values in the range `0..=255` + = note: the cast operand may contain values in the range `0..=255` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | @@ -905,28 +857,20 @@ LL + u8::try_from((x + 1) >> 1) | error: casting `u16` to `i16` may wrap around the value - --> tests/ui/cast.rs:599:13 + --> tests/ui/cast.rs:596:13 | LL | let x = x as i16 - 0x180; | ^^^^^^^^ | - = note: the cast operant may contain values in the range `0..=65535` - -error: casting `i16` to `u16` may lose the sign of the value - --> tests/ui/cast.rs:602:13 - | -LL | let x = x.clamp(0, 510) as u16; - | ^^^^^^^^^^^^^^^^^^^^^^ - | - = note: the cast operant may contain values in the range `0..=510` + = note: the cast operand may contain values in the range `0..=65535` error: casting `u32` to `u8` may truncate the value - --> tests/ui/cast.rs:608:5 + --> tests/ui/cast.rs:604:5 | LL | ((x as u32 * 255 + 32895) >> 16) as u8 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operant may contain values in the range `0..=255` + = note: the cast operand may contain values in the range `0..=255` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | @@ -935,12 +879,12 @@ LL + u8::try_from((x as u32 * 255 + 32895) >> 16) | error: casting `u32` to `u16` may truncate the value - --> tests/ui/cast.rs:629:16 + --> tests/ui/cast.rs:625:16 | LL | return ((sign >> 16) | 0x7C00u32 | nan_bit | (man >> 13)) as u16; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operant may contain values in the range `0..=65535` + = note: the cast operand may contain values in the range `0..=65535` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | @@ -949,20 +893,20 @@ LL + return u16::try_from((sign >> 16) | 0x7C00u32 | nan_bit | (man >> 1 | error: casting `u32` to `i32` may wrap around the value - --> tests/ui/cast.rs:636:24 + --> tests/ui/cast.rs:632:24 | LL | let unbiased_exp = ((exp >> 23) as i32) - 127; | ^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operant may contain values in the range `0..=255` + = note: the cast operand may contain values in the range `0..=255` error: casting `u32` to `u16` may truncate the value - --> tests/ui/cast.rs:642:16 + --> tests/ui/cast.rs:638:16 | LL | return (half_sign | 0x7C00u32) as u16; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operant may contain values in the range `31744..=65535` + = note: the cast operand may contain values in the range `31744..=65535` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | @@ -971,12 +915,12 @@ LL + return u16::try_from(half_sign | 0x7C00u32); | error: casting `u32` to `u16` may truncate the value - --> tests/ui/cast.rs:662:16 + --> tests/ui/cast.rs:658:16 | LL | return (half_sign | half_man) as u16; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operant may contain values in the range `0..=4294967295` + = note: the cast operand may contain values in the range `0..=4294967295` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | @@ -985,20 +929,20 @@ LL + return u16::try_from(half_sign | half_man); | error: casting `i32` to `u32` may lose the sign of the value - --> tests/ui/cast.rs:667:20 + --> tests/ui/cast.rs:663:20 | LL | let half_exp = (half_exp as u32) << 10; | ^^^^^^^^^^^^^^^^^ | - = note: the cast operant may contain values in the range `-112..=143` + = note: the cast operand may contain values in the range `-112..=143` error: casting `u32` to `u16` may truncate the value - --> tests/ui/cast.rs:674:9 + --> tests/ui/cast.rs:670:9 | LL | ((half_sign | half_exp | half_man) + 1) as u16 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operant may contain values in the range `0..=4294967295` + = note: the cast operand may contain values in the range `0..=4294967295` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | @@ -1007,12 +951,12 @@ LL + u16::try_from((half_sign | half_exp | half_man) + 1) | error: casting `u32` to `u16` may truncate the value - --> tests/ui/cast.rs:677:9 + --> tests/ui/cast.rs:673:9 | LL | (half_sign | half_exp | half_man) as u16 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operant may contain values in the range `0..=4294967295` + = note: the cast operand may contain values in the range `0..=4294967295` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | @@ -1020,5 +964,5 @@ LL - (half_sign | half_exp | half_man) as u16 LL + u16::try_from(half_sign | half_exp | half_man) | -error: aborting due to 106 previous errors +error: aborting due to 99 previous errors diff --git a/tests/ui/cast_size.64bit.stderr b/tests/ui/cast_size.64bit.stderr index 9fa20e997f73..180d62f072a9 100644 --- a/tests/ui/cast_size.64bit.stderr +++ b/tests/ui/cast_size.64bit.stderr @@ -4,7 +4,7 @@ error: casting `isize` to `i8` may truncate the value LL | 1isize as i8; | ^^^^^^^^^^^^ | - = note: the cast operant may assume the value `1` + = note: the cast operand is `1` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... = note: `-D clippy::cast-possible-truncation` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::cast_possible_truncation)]` @@ -47,7 +47,7 @@ error: casting `isize` to `i32` may truncate the value on targets with 64-bit wi LL | 1isize as i32; | ^^^^^^^^^^^^^ | - = note: the cast operant may assume the value `1` + = note: the cast operand is `1` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | @@ -61,7 +61,7 @@ error: casting `isize` to `u32` may truncate the value on targets with 64-bit wi LL | 1isize as u32; | ^^^^^^^^^^^^^ | - = note: the cast operant may assume the value `1` + = note: the cast operand is `1` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | @@ -75,7 +75,7 @@ error: casting `usize` to `u32` may truncate the value on targets with 64-bit wi LL | 1usize as u32; | ^^^^^^^^^^^^^ | - = note: the cast operant may assume the value `1` + = note: the cast operand is `1` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | @@ -89,7 +89,7 @@ error: casting `usize` to `i32` may truncate the value on targets with 64-bit wi LL | 1usize as i32; | ^^^^^^^^^^^^^ | - = note: the cast operant may assume the value `1` + = note: the cast operand is `1` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | @@ -103,7 +103,7 @@ error: casting `usize` to `i32` may wrap around the value on targets with 32-bit LL | 1usize as i32; | ^^^^^^^^^^^^^ | - = note: the cast operant may assume the value `1` + = note: the cast operand is `1` = note: `-D clippy::cast-possible-wrap` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::cast_possible_wrap)]` @@ -113,7 +113,7 @@ error: casting `i64` to `isize` may truncate the value on targets with 32-bit wi LL | 1i64 as isize; | ^^^^^^^^^^^^^ | - = note: the cast operant may assume the value `1` + = note: the cast operand is `1` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | @@ -127,7 +127,7 @@ error: casting `i64` to `usize` may truncate the value on targets with 32-bit wi LL | 1i64 as usize; | ^^^^^^^^^^^^^ | - = note: the cast operant may assume the value `1` + = note: the cast operand is `1` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | @@ -141,7 +141,7 @@ error: casting `u64` to `isize` may truncate the value on targets with 32-bit wi LL | 1u64 as isize; | ^^^^^^^^^^^^^ | - = note: the cast operant may assume the value `1` + = note: the cast operand is `1` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | @@ -155,7 +155,7 @@ error: casting `u64` to `isize` may wrap around the value on targets with 64-bit LL | 1u64 as isize; | ^^^^^^^^^^^^^ | - = note: the cast operant may assume the value `1` + = note: the cast operand is `1` error: casting `u64` to `usize` may truncate the value on targets with 32-bit wide pointers --> tests/ui/cast_size.rs:51:5 @@ -163,7 +163,7 @@ error: casting `u64` to `usize` may truncate the value on targets with 32-bit wi LL | 1u64 as usize; | ^^^^^^^^^^^^^ | - = note: the cast operant may assume the value `1` + = note: the cast operand is `1` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | @@ -177,7 +177,7 @@ error: casting `u32` to `isize` may wrap around the value on targets with 32-bit LL | 1u32 as isize; | ^^^^^^^^^^^^^ | - = note: the cast operant may assume the value `1` + = note: the cast operand is `1` error: casting `i32` to `f32` causes a loss of precision (`i32` is 32 bits wide, but `f32`'s mantissa is only 23 bits wide) --> tests/ui/cast_size.rs:61:5 @@ -197,7 +197,7 @@ error: casting `usize` to `u16` may truncate the value LL | const N: u16 = M as u16; | ^^^^^^^^ | - = note: the cast operant may assume the value `100` + = note: the cast operand is `100` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... error: aborting due to 19 previous errors From 1652bc46c3fe3cf55fdbe0992be305313a62844a Mon Sep 17 00:00:00 2001 From: RunDevelopment Date: Sat, 2 Aug 2025 17:03:24 +0200 Subject: [PATCH 05/13] Improved evaluation for the pattern `x * x` --- clippy_utils/src/rinterval/mod.rs | 22 +++++++ tests/ui/cast.rs | 1 - tests/ui/cast.stderr | 102 ++++++++++++++---------------- 3 files changed, 69 insertions(+), 56 deletions(-) diff --git a/clippy_utils/src/rinterval/mod.rs b/clippy_utils/src/rinterval/mod.rs index 0e023892be74..8ab15b609cd0 100644 --- a/clippy_utils/src/rinterval/mod.rs +++ b/clippy_utils/src/rinterval/mod.rs @@ -12,6 +12,7 @@ pub use arithmetic::*; pub use iinterval::*; use rustc_ast::LitKind; +use rustc_hir::def::Res; use rustc_hir::{BinOpKind, Expr, ExprKind, PathSegment, UnOp}; use rustc_lint::LateContext; use rustc_middle::ty::{IntTy, Ty, TyKind, TypeckResults, UintTy}; @@ -127,6 +128,13 @@ impl<'c, 'cx> IntervalCtxt<'c, 'cx> { fn binary_op(&self, op: BinOpKind, l_expr: &Expr<'cx>, r_expr: &Expr<'cx>) -> Option { let lhs = &self.eval(l_expr)?; + // The pattern `x * x` is quite common and will always result in a + // positive value (absent overflow). To support this, special handling + // is required. + if matches!(op, BinOpKind::Mul) && self.is_same_variable(l_expr, r_expr) { + return Arithmetic::wrapping_pow(lhs, &IInterval::single_unsigned(IntType::U32, 2)).ok(); + } + // shl and shr have weird issues with type inference, so we need to // explicitly type the right-hand side as u32 let rhs = if matches!(op, BinOpKind::Shl | BinOpKind::Shr) { @@ -362,6 +370,20 @@ impl<'c, 'cx> IntervalCtxt<'c, 'cx> { None } + fn is_same_variable(&self, expr: &Expr<'cx>, other: &Expr<'cx>) -> bool { + // Check if the two expressions are the same variable + if let ExprKind::Path(ref path) = expr.kind { + if let ExprKind::Path(ref other_path) = other.kind { + let res = self.cx.qpath_res(path, expr.hir_id); + let other_res = self.cx.qpath_res(other_path, other.hir_id); + return match (res, other_res) { + (Res::Local(lhs_id), Res::Local(rhs_id)) => lhs_id == rhs_id, + _ => false, + }; + } + } + false + } fn u128_repr_to_interval(n: u128, ty: IntType) -> Option { match ty.info() { IntTypeInfo::Signed(_, _) => { diff --git a/tests/ui/cast.rs b/tests/ui/cast.rs index 88f66492f3e1..db22573d1f22 100644 --- a/tests/ui/cast.rs +++ b/tests/ui/cast.rs @@ -435,7 +435,6 @@ fn issue11642() { fn square(x: i16) -> u32 { let x = x as i32; (x * x) as u32; - //~^ cast_sign_loss x.pow(2) as u32; (-2_i32).saturating_pow(2) as u32 } diff --git a/tests/ui/cast.stderr b/tests/ui/cast.stderr index e263efa5984e..93abfe4db0e9 100644 --- a/tests/ui/cast.stderr +++ b/tests/ui/cast.stderr @@ -505,15 +505,7 @@ LL + let c = u8::try_from(q / 1000); | error: casting `i32` to `u32` may lose the sign of the value - --> tests/ui/cast.rs:437:9 - | -LL | (x * x) as u32; - | ^^^^^^^^^^^^^^ - | - = note: the cast operand may contain values in the range `-1073709056..=1073741824` - -error: casting `i32` to `u32` may lose the sign of the value - --> tests/ui/cast.rs:443:32 + --> tests/ui/cast.rs:442:32 | LL | let _a = |x: i32| -> u32 { (x * x * x * x) as u32 }; | ^^^^^^^^^^^^^^^^^^^^^^ @@ -521,7 +513,7 @@ LL | let _a = |x: i32| -> u32 { (x * x * x * x) as u32 }; = note: the cast operand may contain values in the range `-2147483648..=2147483647` error: casting `i32` to `u32` may lose the sign of the value - --> tests/ui/cast.rs:447:5 + --> tests/ui/cast.rs:446:5 | LL | (-2_i32).pow(3) as u32; | ^^^^^^^^^^^^^^^^^^^^^^ @@ -529,7 +521,7 @@ LL | (-2_i32).pow(3) as u32; = note: the cast operand is `-8` error: casting `i32` to `u32` may lose the sign of the value - --> tests/ui/cast.rs:452:5 + --> tests/ui/cast.rs:451:5 | LL | (-5_i32 % 2) as u32; | ^^^^^^^^^^^^^^^^^^^ @@ -537,7 +529,7 @@ LL | (-5_i32 % 2) as u32; = note: the cast operand is `-1` error: casting `i32` to `u32` may lose the sign of the value - --> tests/ui/cast.rs:455:5 + --> tests/ui/cast.rs:454:5 | LL | (-5_i32 % -2) as u32; | ^^^^^^^^^^^^^^^^^^^^ @@ -545,7 +537,7 @@ LL | (-5_i32 % -2) as u32; = note: the cast operand is `-1` error: casting `i32` to `u32` may lose the sign of the value - --> tests/ui/cast.rs:459:5 + --> tests/ui/cast.rs:458:5 | LL | (-2_i32 >> 1) as u32; | ^^^^^^^^^^^^^^^^^^^^ @@ -553,7 +545,7 @@ LL | (-2_i32 >> 1) as u32; = note: the cast operand is `-1` error: casting `i32` to `u32` may lose the sign of the value - --> tests/ui/cast.rs:463:5 + --> tests/ui/cast.rs:462:5 | LL | (x * x) as u32; | ^^^^^^^^^^^^^^ @@ -561,7 +553,7 @@ LL | (x * x) as u32; = note: the cast operand may contain values in the range `-2147483648..=2147483647` error: casting `i32` to `u32` may lose the sign of the value - --> tests/ui/cast.rs:465:5 + --> tests/ui/cast.rs:464:5 | LL | (x * x * x) as u32; | ^^^^^^^^^^^^^^^^^^ @@ -569,7 +561,7 @@ LL | (x * x * x) as u32; = note: the cast operand may contain values in the range `-2147483648..=2147483647` error: casting `i16` to `u16` may lose the sign of the value - --> tests/ui/cast.rs:469:5 + --> tests/ui/cast.rs:468:5 | LL | (y * y * y * y * -2) as u16; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -577,7 +569,7 @@ LL | (y * y * y * y * -2) as u16; = note: the cast operand may contain values in the range `-32768..=32767` error: casting `i16` to `u16` may lose the sign of the value - --> tests/ui/cast.rs:472:5 + --> tests/ui/cast.rs:471:5 | LL | (y * y * y / y * 2) as u16; | ^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -585,7 +577,7 @@ LL | (y * y * y / y * 2) as u16; = note: the cast operand may contain values in the range `-32768..=32767` error: casting `i16` to `u16` may lose the sign of the value - --> tests/ui/cast.rs:474:5 + --> tests/ui/cast.rs:473:5 | LL | (y * y / y * 2) as u16; | ^^^^^^^^^^^^^^^^^^^^^^ @@ -593,7 +585,7 @@ LL | (y * y / y * 2) as u16; = note: the cast operand may contain values in the range `-32768..=32767` error: casting `i16` to `u16` may lose the sign of the value - --> tests/ui/cast.rs:477:5 + --> tests/ui/cast.rs:476:5 | LL | (y / y * y * -2) as u16; | ^^^^^^^^^^^^^^^^^^^^^^^ @@ -601,7 +593,7 @@ LL | (y / y * y * -2) as u16; = note: the cast operand may contain values in the range `-32768..=32767` error: equal expressions as operands to `/` - --> tests/ui/cast.rs:477:6 + --> tests/ui/cast.rs:476:6 | LL | (y / y * y * -2) as u16; | ^^^^^ @@ -609,7 +601,7 @@ LL | (y / y * y * -2) as u16; = note: `#[deny(clippy::eq_op)]` on by default error: casting `i16` to `u16` may lose the sign of the value - --> tests/ui/cast.rs:481:5 + --> tests/ui/cast.rs:480:5 | LL | (y + y + y + -2) as u16; | ^^^^^^^^^^^^^^^^^^^^^^^ @@ -617,7 +609,7 @@ LL | (y + y + y + -2) as u16; = note: the cast operand may contain values in the range `-32768..=32767` error: casting `i16` to `u16` may lose the sign of the value - --> tests/ui/cast.rs:484:5 + --> tests/ui/cast.rs:483:5 | LL | (y + y + y + 2) as u16; | ^^^^^^^^^^^^^^^^^^^^^^ @@ -625,7 +617,7 @@ LL | (y + y + y + 2) as u16; = note: the cast operand may contain values in the range `-32768..=32767` error: casting `i16` to `u16` may lose the sign of the value - --> tests/ui/cast.rs:488:5 + --> tests/ui/cast.rs:487:5 | LL | (z + -2) as u16; | ^^^^^^^^^^^^^^^ @@ -633,7 +625,7 @@ LL | (z + -2) as u16; = note: the cast operand may contain values in the range `-32768..=32767` error: casting `i16` to `u16` may lose the sign of the value - --> tests/ui/cast.rs:491:5 + --> tests/ui/cast.rs:490:5 | LL | (z + z + 2) as u16; | ^^^^^^^^^^^^^^^^^^ @@ -641,7 +633,7 @@ LL | (z + z + 2) as u16; = note: the cast operand may contain values in the range `-32768..=32767` error: casting `i32` to `u32` may lose the sign of the value - --> tests/ui/cast.rs:495:9 + --> tests/ui/cast.rs:494:9 | LL | (a * a * b * b * c * c) as u32; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -649,7 +641,7 @@ LL | (a * a * b * b * c * c) as u32; = note: the cast operand may contain values in the range `-2147483648..=2147483647` error: casting `i32` to `u32` may lose the sign of the value - --> tests/ui/cast.rs:497:9 + --> tests/ui/cast.rs:496:9 | LL | (a * b * c) as u32; | ^^^^^^^^^^^^^^^^^^ @@ -657,7 +649,7 @@ LL | (a * b * c) as u32; = note: the cast operand may contain values in the range `-2147483648..=2147483647` error: casting `i32` to `u32` may lose the sign of the value - --> tests/ui/cast.rs:500:9 + --> tests/ui/cast.rs:499:9 | LL | (a * -b * c) as u32; | ^^^^^^^^^^^^^^^^^^^ @@ -665,7 +657,7 @@ LL | (a * -b * c) as u32; = note: the cast operand may contain values in the range `-2147483648..=2147483647` error: casting `i32` to `u32` may lose the sign of the value - --> tests/ui/cast.rs:503:9 + --> tests/ui/cast.rs:502:9 | LL | (a * b * c * c) as u32; | ^^^^^^^^^^^^^^^^^^^^^^ @@ -673,7 +665,7 @@ LL | (a * b * c * c) as u32; = note: the cast operand may contain values in the range `-2147483648..=2147483647` error: casting `i32` to `u32` may lose the sign of the value - --> tests/ui/cast.rs:505:9 + --> tests/ui/cast.rs:504:9 | LL | (a * -2) as u32; | ^^^^^^^^^^^^^^^ @@ -681,7 +673,7 @@ LL | (a * -2) as u32; = note: the cast operand may contain values in the range `-2147483648..=2147483647` error: casting `i32` to `u32` may lose the sign of the value - --> tests/ui/cast.rs:508:9 + --> tests/ui/cast.rs:507:9 | LL | (a * b * c * -2) as u32; | ^^^^^^^^^^^^^^^^^^^^^^^ @@ -689,7 +681,7 @@ LL | (a * b * c * -2) as u32; = note: the cast operand may contain values in the range `-2147483648..=2147483647` error: casting `i32` to `u32` may lose the sign of the value - --> tests/ui/cast.rs:511:9 + --> tests/ui/cast.rs:510:9 | LL | (a / b) as u32; | ^^^^^^^^^^^^^^ @@ -697,7 +689,7 @@ LL | (a / b) as u32; = note: the cast operand may contain values in the range `-2147483648..=2147483647` error: casting `i32` to `u32` may lose the sign of the value - --> tests/ui/cast.rs:513:9 + --> tests/ui/cast.rs:512:9 | LL | (a / b * c) as u32; | ^^^^^^^^^^^^^^^^^^ @@ -705,7 +697,7 @@ LL | (a / b * c) as u32; = note: the cast operand may contain values in the range `-2147483648..=2147483647` error: casting `i32` to `u32` may lose the sign of the value - --> tests/ui/cast.rs:516:9 + --> tests/ui/cast.rs:515:9 | LL | (a / b + b * c) as u32; | ^^^^^^^^^^^^^^^^^^^^^^ @@ -713,7 +705,7 @@ LL | (a / b + b * c) as u32; = note: the cast operand may contain values in the range `-2147483648..=2147483647` error: casting `i32` to `u32` may lose the sign of the value - --> tests/ui/cast.rs:519:9 + --> tests/ui/cast.rs:518:9 | LL | a.saturating_pow(3) as u32; | ^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -721,7 +713,7 @@ LL | a.saturating_pow(3) as u32; = note: the cast operand may contain values in the range `-2147483648..=2147483647` error: casting `i32` to `u32` may lose the sign of the value - --> tests/ui/cast.rs:522:9 + --> tests/ui/cast.rs:521:9 | LL | (a.abs() * b.pow(2) / c.abs()) as u32 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -729,7 +721,7 @@ LL | (a.abs() * b.pow(2) / c.abs()) as u32 = note: the cast operand may contain values in the range `-2147483648..=2147483647` error: casting `i32` to `u32` may lose the sign of the value - --> tests/ui/cast.rs:530:21 + --> tests/ui/cast.rs:529:21 | LL | let _ = i32::MIN as u32; // cast_sign_loss | ^^^^^^^^^^^^^^^ @@ -741,7 +733,7 @@ LL | m!(); = note: this error originates in the macro `m` (in Nightly builds, run with -Z macro-backtrace for more info) error: casting `u32` to `u8` may truncate the value - --> tests/ui/cast.rs:533:21 + --> tests/ui/cast.rs:532:21 | LL | let _ = u32::MAX as u8; // cast_possible_truncation | ^^^^^^^^^^^^^^ @@ -759,7 +751,7 @@ LL + let _ = u8::try_from(u32::MAX); // cast_possible_truncation | error: casting `f64` to `f32` may truncate the value - --> tests/ui/cast.rs:536:21 + --> tests/ui/cast.rs:535:21 | LL | let _ = std::f64::consts::PI as f32; // cast_possible_truncation | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -771,7 +763,7 @@ LL | m!(); = note: this error originates in the macro `m` (in Nightly builds, run with -Z macro-backtrace for more info) error: casting `i64` to `usize` may truncate the value on targets with 32-bit wide pointers - --> tests/ui/cast.rs:547:5 + --> tests/ui/cast.rs:546:5 | LL | bar.unwrap().unwrap() as usize | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -785,7 +777,7 @@ LL + usize::try_from(bar.unwrap().unwrap()) | error: casting `i64` to `usize` may lose the sign of the value - --> tests/ui/cast.rs:547:5 + --> tests/ui/cast.rs:546:5 | LL | bar.unwrap().unwrap() as usize | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -793,7 +785,7 @@ LL | bar.unwrap().unwrap() as usize = note: the cast operand may contain values in the range `-9223372036854775808..=9223372036854775807` error: casting `u64` to `u8` may truncate the value - --> tests/ui/cast.rs:564:5 + --> tests/ui/cast.rs:563:5 | LL | (256 & 999999u64) as u8; | ^^^^^^^^^^^^^^^^^^^^^^^ @@ -807,7 +799,7 @@ LL + u8::try_from(256 & 999999u64); | error: casting `u64` to `u8` may truncate the value - --> tests/ui/cast.rs:567:5 + --> tests/ui/cast.rs:566:5 | LL | (255 % 999999u64) as u8; | ^^^^^^^^^^^^^^^^^^^^^^^ @@ -821,7 +813,7 @@ LL + u8::try_from(255 % 999999u64); | error: casting `u64` to `u32` may truncate the value - --> tests/ui/cast.rs:578:5 + --> tests/ui/cast.rs:577:5 | LL | result as u32 | ^^^^^^^^^^^^^ @@ -835,7 +827,7 @@ LL + u32::try_from(result) | error: casting `u16` to `i16` may wrap around the value - --> tests/ui/cast.rs:585:13 + --> tests/ui/cast.rs:584:13 | LL | let x = x as i16 - 0x180; | ^^^^^^^^ @@ -843,7 +835,7 @@ LL | let x = x as i16 - 0x180; = note: the cast operand may contain values in the range `0..=65535` error: casting `u16` to `u8` may truncate the value - --> tests/ui/cast.rs:590:5 + --> tests/ui/cast.rs:589:5 | LL | ((x + 1) >> 1) as u8 | ^^^^^^^^^^^^^^^^^^^^ @@ -857,7 +849,7 @@ LL + u8::try_from((x + 1) >> 1) | error: casting `u16` to `i16` may wrap around the value - --> tests/ui/cast.rs:596:13 + --> tests/ui/cast.rs:595:13 | LL | let x = x as i16 - 0x180; | ^^^^^^^^ @@ -865,7 +857,7 @@ LL | let x = x as i16 - 0x180; = note: the cast operand may contain values in the range `0..=65535` error: casting `u32` to `u8` may truncate the value - --> tests/ui/cast.rs:604:5 + --> tests/ui/cast.rs:603:5 | LL | ((x as u32 * 255 + 32895) >> 16) as u8 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -879,7 +871,7 @@ LL + u8::try_from((x as u32 * 255 + 32895) >> 16) | error: casting `u32` to `u16` may truncate the value - --> tests/ui/cast.rs:625:16 + --> tests/ui/cast.rs:624:16 | LL | return ((sign >> 16) | 0x7C00u32 | nan_bit | (man >> 13)) as u16; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -893,7 +885,7 @@ LL + return u16::try_from((sign >> 16) | 0x7C00u32 | nan_bit | (man >> 1 | error: casting `u32` to `i32` may wrap around the value - --> tests/ui/cast.rs:632:24 + --> tests/ui/cast.rs:631:24 | LL | let unbiased_exp = ((exp >> 23) as i32) - 127; | ^^^^^^^^^^^^^^^^^^^^ @@ -901,7 +893,7 @@ LL | let unbiased_exp = ((exp >> 23) as i32) - 127; = note: the cast operand may contain values in the range `0..=255` error: casting `u32` to `u16` may truncate the value - --> tests/ui/cast.rs:638:16 + --> tests/ui/cast.rs:637:16 | LL | return (half_sign | 0x7C00u32) as u16; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -915,7 +907,7 @@ LL + return u16::try_from(half_sign | 0x7C00u32); | error: casting `u32` to `u16` may truncate the value - --> tests/ui/cast.rs:658:16 + --> tests/ui/cast.rs:657:16 | LL | return (half_sign | half_man) as u16; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -929,7 +921,7 @@ LL + return u16::try_from(half_sign | half_man); | error: casting `i32` to `u32` may lose the sign of the value - --> tests/ui/cast.rs:663:20 + --> tests/ui/cast.rs:662:20 | LL | let half_exp = (half_exp as u32) << 10; | ^^^^^^^^^^^^^^^^^ @@ -937,7 +929,7 @@ LL | let half_exp = (half_exp as u32) << 10; = note: the cast operand may contain values in the range `-112..=143` error: casting `u32` to `u16` may truncate the value - --> tests/ui/cast.rs:670:9 + --> tests/ui/cast.rs:669:9 | LL | ((half_sign | half_exp | half_man) + 1) as u16 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -951,7 +943,7 @@ LL + u16::try_from((half_sign | half_exp | half_man) + 1) | error: casting `u32` to `u16` may truncate the value - --> tests/ui/cast.rs:673:9 + --> tests/ui/cast.rs:672:9 | LL | (half_sign | half_exp | half_man) as u16 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -964,5 +956,5 @@ LL - (half_sign | half_exp | half_man) as u16 LL + u16::try_from(half_sign | half_exp | half_man) | -error: aborting due to 99 previous errors +error: aborting due to 98 previous errors From 4870d0f0faa5fc13d6cbb5bdfb5fd78159b57717 Mon Sep 17 00:00:00 2001 From: RunDevelopment Date: Sat, 2 Aug 2025 17:05:05 +0200 Subject: [PATCH 06/13] Removed unused imports --- clippy_lints/src/casts/cast_sign_loss.rs | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/clippy_lints/src/casts/cast_sign_loss.rs b/clippy_lints/src/casts/cast_sign_loss.rs index eccb1045c018..776e302a5880 100644 --- a/clippy_lints/src/casts/cast_sign_loss.rs +++ b/clippy_lints/src/casts/cast_sign_loss.rs @@ -1,14 +1,8 @@ -use std::convert::Infallible; -use std::ops::ControlFlow; - -use clippy_utils::consts::{ConstEvalCtxt, Constant}; use clippy_utils::diagnostics::{span_lint, span_lint_and_note}; -use clippy_utils::visitors::{Descend, for_each_expr_without_closures}; -use clippy_utils::{method_chain_args, rinterval, sext, sym}; -use rustc_hir::{BinOpKind, Expr, ExprKind}; +use clippy_utils::rinterval; +use rustc_hir::Expr; use rustc_lint::LateContext; -use rustc_middle::ty::{self, Ty}; -use rustc_span::Symbol; +use rustc_middle::ty::Ty; use super::{CAST_SIGN_LOSS, utils}; From 4a888fabf86f57a7c3c972da0c50c7cfd2695aed Mon Sep 17 00:00:00 2001 From: RunDevelopment Date: Sat, 2 Aug 2025 22:17:43 +0200 Subject: [PATCH 07/13] Use range analysis in `cast_possible_wrap` --- clippy_lints/src/casts/cast_possible_wrap.rs | 13 +- clippy_utils/src/rinterval/iinterval.rs | 18 ++ tests/ui/cast.rs | 34 +-- tests/ui/cast.stderr | 302 +++++++++---------- tests/ui/cast_size.64bit.stderr | 164 +++++----- tests/ui/cast_size.rs | 35 ++- 6 files changed, 312 insertions(+), 254 deletions(-) diff --git a/clippy_lints/src/casts/cast_possible_wrap.rs b/clippy_lints/src/casts/cast_possible_wrap.rs index a4552dea9d37..19875bd00e06 100644 --- a/clippy_lints/src/casts/cast_possible_wrap.rs +++ b/clippy_lints/src/casts/cast_possible_wrap.rs @@ -45,9 +45,6 @@ pub(super) fn check<'cx>( return; } - let interval_ctx = rinterval::IntervalCtxt::new(cx); - let from_interval = interval_ctx.eval(cast_op); - let should_lint = match (cast_from.is_ptr_sized_integral(), cast_to.is_ptr_sized_integral()) { (true, true) => { // casts between two ptr sized integers are trivially always the same size @@ -89,6 +86,16 @@ pub(super) fn check<'cx>( ), }; + let interval_ctx = rinterval::IntervalCtxt::new(cx); + let from_interval = interval_ctx.eval(cast_op); + + if let Some(from_interval) = &from_interval + && from_interval.fits_into(from_interval.ty.to_signed()) + { + // if the values always fit into the signed type, do not emit a warning + return; + } + span_lint_and_then(cx, CAST_POSSIBLE_WRAP, expr.span, message, |diag| { if let Some(from_interval) = from_interval { diag.note(utils::format_cast_operand(from_interval)); diff --git a/clippy_utils/src/rinterval/iinterval.rs b/clippy_utils/src/rinterval/iinterval.rs index 10907208032c..0e5a9ecada26 100644 --- a/clippy_utils/src/rinterval/iinterval.rs +++ b/clippy_utils/src/rinterval/iinterval.rs @@ -77,6 +77,24 @@ impl IntType { IntType::I128 => IntType::U128, } } + pub const fn to_signed(self) -> IntType { + match self { + IntType::U8 | IntType::I8 => IntType::I8, + IntType::U16 | IntType::I16 => IntType::I16, + IntType::U32 | IntType::I32 => IntType::I32, + IntType::U64 | IntType::I64 => IntType::I64, + IntType::U128 | IntType::I128 => IntType::I128, + } + } + pub const fn to_unsigned(self) -> IntType { + match self { + IntType::U8 | IntType::I8 => IntType::U8, + IntType::U16 | IntType::I16 => IntType::U16, + IntType::U32 | IntType::I32 => IntType::U32, + IntType::U64 | IntType::I64 => IntType::U64, + IntType::U128 | IntType::I128 => IntType::U128, + } + } } pub(crate) enum IntTypeInfo { diff --git a/tests/ui/cast.rs b/tests/ui/cast.rs index db22573d1f22..eaf41e19d546 100644 --- a/tests/ui/cast.rs +++ b/tests/ui/cast.rs @@ -15,11 +15,11 @@ clippy::identity_op )] -// FIXME(f16_f128): add tests once const casting is available for these types fn get_value() -> T { todo!() } +// FIXME(f16_f128): add tests once const casting is available for these types fn main() { // Test clippy::cast_precision_loss let x0: i32 = get_value(); @@ -55,11 +55,12 @@ fn main() { 1f64 as f32; //~^ cast_possible_truncation - 1i32 as i8; + get_value::() as i8; //~^ cast_possible_truncation - 1i32 as u8; + get_value::() as u8; //~^ cast_possible_truncation + //~| cast_sign_loss 1f64 as isize; //~^ cast_possible_truncation @@ -74,7 +75,7 @@ fn main() { //~| cast_sign_loss { - let _x: i8 = 1i32 as _; + let _x: i8 = get_value::() as _; //~^ cast_possible_truncation 1f32 as i32; @@ -88,19 +89,19 @@ fn main() { //~| cast_sign_loss } // Test clippy::cast_possible_wrap - 1u8 as i8; + get_value::() as i8; //~^ cast_possible_wrap - 1u16 as i16; + get_value::() as i16; //~^ cast_possible_wrap - 1u32 as i32; + get_value::() as i32; //~^ cast_possible_wrap - 1u64 as i64; + get_value::() as i64; //~^ cast_possible_wrap - 1usize as isize; + get_value::() as isize; //~^ cast_possible_wrap // should not wrap, usize is never 8 bits @@ -108,31 +109,31 @@ fn main() { //~^ cast_possible_truncation // wraps on 16 bit ptr size - 1usize as i16; + get_value::() as i16; //~^ cast_possible_truncation //~| cast_possible_wrap // wraps on 32 bit ptr size - 1usize as i32; + get_value::() as i32; //~^ cast_possible_truncation //~| cast_possible_wrap // wraps on 64 bit ptr size - 1usize as i64; + get_value::() as i64; //~^ cast_possible_wrap // should not wrap, isize is never 8 bits - 1u8 as isize; + get_value::() as isize; // wraps on 16 bit ptr size - 1u16 as isize; + get_value::() as isize; //~^ cast_possible_wrap // wraps on 32 bit ptr size - 1u32 as isize; + get_value::() as isize; //~^ cast_possible_wrap // wraps on 64 bit ptr size - 1u64 as isize; + get_value::() as isize; //~^ cast_possible_truncation //~| cast_possible_wrap @@ -629,7 +630,6 @@ fn f32_to_f16u(value: f32) -> u16 { let half_sign = sign >> 16; // Unbias the exponent, then bias for half precision let unbiased_exp = ((exp >> 23) as i32) - 127; - //~^ cast_possible_wrap let half_exp = unbiased_exp + 15; // Check for exponent overflow, return +infinity diff --git a/tests/ui/cast.stderr b/tests/ui/cast.stderr index 93abfe4db0e9..ab93449ca6d8 100644 --- a/tests/ui/cast.stderr +++ b/tests/ui/cast.stderr @@ -75,33 +75,41 @@ LL | 1f64 as f32; error: casting `i32` to `i8` may truncate the value --> tests/ui/cast.rs:58:5 | -LL | 1i32 as i8; - | ^^^^^^^^^^ +LL | get_value::() as i8; + | ^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operand is `1` + = note: the cast operand may contain values in the range `-2147483648..=2147483647` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | -LL - 1i32 as i8; -LL + i8::try_from(1i32); +LL - get_value::() as i8; +LL + i8::try_from(get_value::()); | error: casting `i32` to `u8` may truncate the value --> tests/ui/cast.rs:61:5 | -LL | 1i32 as u8; - | ^^^^^^^^^^ +LL | get_value::() as u8; + | ^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operand is `1` + = note: the cast operand may contain values in the range `-2147483648..=2147483647` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | -LL - 1i32 as u8; -LL + u8::try_from(1i32); +LL - get_value::() as u8; +LL + u8::try_from(get_value::()); | +error: casting `i32` to `u8` may lose the sign of the value + --> tests/ui/cast.rs:61:5 + | +LL | get_value::() as u8; + | ^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: the cast operand may contain values in the range `-2147483648..=2147483647` + error: casting `f64` to `isize` may truncate the value - --> tests/ui/cast.rs:64:5 + --> tests/ui/cast.rs:65:5 | LL | 1f64 as isize; | ^^^^^^^^^^^^^ @@ -109,7 +117,7 @@ LL | 1f64 as isize; = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... error: casting `f64` to `usize` may truncate the value - --> tests/ui/cast.rs:67:5 + --> tests/ui/cast.rs:68:5 | LL | 1f64 as usize; | ^^^^^^^^^^^^^ @@ -117,13 +125,13 @@ LL | 1f64 as usize; = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... error: casting `f64` to `usize` may lose the sign of the value - --> tests/ui/cast.rs:67:5 + --> tests/ui/cast.rs:68:5 | LL | 1f64 as usize; | ^^^^^^^^^^^^^ error: casting `u32` to `u16` may truncate the value - --> tests/ui/cast.rs:71:5 + --> tests/ui/cast.rs:72:5 | LL | 1f32 as u32 as u16; | ^^^^^^^^^^^^^^^^^^ @@ -137,7 +145,7 @@ LL + u16::try_from(1f32 as u32); | error: casting `f32` to `u32` may truncate the value - --> tests/ui/cast.rs:71:5 + --> tests/ui/cast.rs:72:5 | LL | 1f32 as u32 as u16; | ^^^^^^^^^^^ @@ -145,27 +153,27 @@ LL | 1f32 as u32 as u16; = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... error: casting `f32` to `u32` may lose the sign of the value - --> tests/ui/cast.rs:71:5 + --> tests/ui/cast.rs:72:5 | LL | 1f32 as u32 as u16; | ^^^^^^^^^^^ error: casting `i32` to `i8` may truncate the value - --> tests/ui/cast.rs:77:22 + --> tests/ui/cast.rs:78:22 | -LL | let _x: i8 = 1i32 as _; - | ^^^^^^^^^ +LL | let _x: i8 = get_value::() as _; + | ^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operand is `1` + = note: the cast operand may contain values in the range `-2147483648..=2147483647` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | -LL - let _x: i8 = 1i32 as _; -LL + let _x: i8 = 1i32.try_into(); +LL - let _x: i8 = get_value::() as _; +LL + let _x: i8 = get_value::().try_into(); | error: casting `f32` to `i32` may truncate the value - --> tests/ui/cast.rs:80:9 + --> tests/ui/cast.rs:81:9 | LL | 1f32 as i32; | ^^^^^^^^^^^ @@ -173,7 +181,7 @@ LL | 1f32 as i32; = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... error: casting `f64` to `i32` may truncate the value - --> tests/ui/cast.rs:83:9 + --> tests/ui/cast.rs:84:9 | LL | 1f64 as i32; | ^^^^^^^^^^^ @@ -181,7 +189,7 @@ LL | 1f64 as i32; = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... error: casting `f32` to `u8` may truncate the value - --> tests/ui/cast.rs:86:9 + --> tests/ui/cast.rs:87:9 | LL | 1f32 as u8; | ^^^^^^^^^^ @@ -189,55 +197,55 @@ LL | 1f32 as u8; = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... error: casting `f32` to `u8` may lose the sign of the value - --> tests/ui/cast.rs:86:9 + --> tests/ui/cast.rs:87:9 | LL | 1f32 as u8; | ^^^^^^^^^^ error: casting `u8` to `i8` may wrap around the value - --> tests/ui/cast.rs:91:5 + --> tests/ui/cast.rs:92:5 | -LL | 1u8 as i8; - | ^^^^^^^^^ +LL | get_value::() as i8; + | ^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operand is `1` + = note: the cast operand may contain values in the range `0..=255` = note: `-D clippy::cast-possible-wrap` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::cast_possible_wrap)]` error: casting `u16` to `i16` may wrap around the value - --> tests/ui/cast.rs:94:5 + --> tests/ui/cast.rs:95:5 | -LL | 1u16 as i16; - | ^^^^^^^^^^^ +LL | get_value::() as i16; + | ^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operand is `1` + = note: the cast operand may contain values in the range `0..=65535` error: casting `u32` to `i32` may wrap around the value - --> tests/ui/cast.rs:97:5 + --> tests/ui/cast.rs:98:5 | -LL | 1u32 as i32; - | ^^^^^^^^^^^ +LL | get_value::() as i32; + | ^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operand is `1` + = note: the cast operand may contain values in the range `0..=4294967295` error: casting `u64` to `i64` may wrap around the value - --> tests/ui/cast.rs:100:5 + --> tests/ui/cast.rs:101:5 | -LL | 1u64 as i64; - | ^^^^^^^^^^^ +LL | get_value::() as i64; + | ^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operand is `1` + = note: the cast operand may contain values in the range `0..=18446744073709551615` error: casting `usize` to `isize` may wrap around the value - --> tests/ui/cast.rs:103:5 + --> tests/ui/cast.rs:104:5 | -LL | 1usize as isize; - | ^^^^^^^^^^^^^^^ +LL | get_value::() as isize; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operand is `1` + = note: the cast operand may contain values in the range `0..=18446744073709551615` error: casting `usize` to `i8` may truncate the value - --> tests/ui/cast.rs:107:5 + --> tests/ui/cast.rs:108:5 | LL | 1usize as i8; | ^^^^^^^^^^^^ @@ -251,101 +259,101 @@ LL + i8::try_from(1usize); | error: casting `usize` to `i16` may truncate the value - --> tests/ui/cast.rs:111:5 + --> tests/ui/cast.rs:112:5 | -LL | 1usize as i16; - | ^^^^^^^^^^^^^ +LL | get_value::() as i16; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operand is `1` + = note: the cast operand may contain values in the range `0..=18446744073709551615` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | -LL - 1usize as i16; -LL + i16::try_from(1usize); +LL - get_value::() as i16; +LL + i16::try_from(get_value::()); | error: casting `usize` to `i16` may wrap around the value on targets with 16-bit wide pointers - --> tests/ui/cast.rs:111:5 + --> tests/ui/cast.rs:112:5 | -LL | 1usize as i16; - | ^^^^^^^^^^^^^ +LL | get_value::() as i16; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operand is `1` + = note: the cast operand may contain values in the range `0..=18446744073709551615` = note: `usize` and `isize` may be as small as 16 bits on some platforms = note: for more information see https://doc.rust-lang.org/reference/types/numeric.html#machine-dependent-integer-types error: casting `usize` to `i32` may truncate the value on targets with 64-bit wide pointers - --> tests/ui/cast.rs:116:5 + --> tests/ui/cast.rs:117:5 | -LL | 1usize as i32; - | ^^^^^^^^^^^^^ +LL | get_value::() as i32; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operand is `1` + = note: the cast operand may contain values in the range `0..=18446744073709551615` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | -LL - 1usize as i32; -LL + i32::try_from(1usize); +LL - get_value::() as i32; +LL + i32::try_from(get_value::()); | error: casting `usize` to `i32` may wrap around the value on targets with 32-bit wide pointers - --> tests/ui/cast.rs:116:5 + --> tests/ui/cast.rs:117:5 | -LL | 1usize as i32; - | ^^^^^^^^^^^^^ +LL | get_value::() as i32; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operand is `1` + = note: the cast operand may contain values in the range `0..=18446744073709551615` error: casting `usize` to `i64` may wrap around the value on targets with 64-bit wide pointers - --> tests/ui/cast.rs:121:5 + --> tests/ui/cast.rs:122:5 | -LL | 1usize as i64; - | ^^^^^^^^^^^^^ +LL | get_value::() as i64; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operand is `1` + = note: the cast operand may contain values in the range `0..=18446744073709551615` error: casting `u16` to `isize` may wrap around the value on targets with 16-bit wide pointers - --> tests/ui/cast.rs:127:5 + --> tests/ui/cast.rs:128:5 | -LL | 1u16 as isize; - | ^^^^^^^^^^^^^ +LL | get_value::() as isize; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operand is `1` + = note: the cast operand may contain values in the range `0..=65535` = note: `usize` and `isize` may be as small as 16 bits on some platforms = note: for more information see https://doc.rust-lang.org/reference/types/numeric.html#machine-dependent-integer-types error: casting `u32` to `isize` may wrap around the value on targets with 32-bit wide pointers - --> tests/ui/cast.rs:131:5 + --> tests/ui/cast.rs:132:5 | -LL | 1u32 as isize; - | ^^^^^^^^^^^^^ +LL | get_value::() as isize; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operand is `1` + = note: the cast operand may contain values in the range `0..=4294967295` error: casting `u64` to `isize` may truncate the value on targets with 32-bit wide pointers - --> tests/ui/cast.rs:135:5 + --> tests/ui/cast.rs:136:5 | -LL | 1u64 as isize; - | ^^^^^^^^^^^^^ +LL | get_value::() as isize; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operand is `1` + = note: the cast operand may contain values in the range `0..=18446744073709551615` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | -LL - 1u64 as isize; -LL + isize::try_from(1u64); +LL - get_value::() as isize; +LL + isize::try_from(get_value::()); | error: casting `u64` to `isize` may wrap around the value on targets with 64-bit wide pointers - --> tests/ui/cast.rs:135:5 + --> tests/ui/cast.rs:136:5 | -LL | 1u64 as isize; - | ^^^^^^^^^^^^^ +LL | get_value::() as isize; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operand is `1` + = note: the cast operand may contain values in the range `0..=18446744073709551615` error: casting `i32` to `u32` may lose the sign of the value - --> tests/ui/cast.rs:141:5 + --> tests/ui/cast.rs:142:5 | LL | -1i32 as u32; | ^^^^^^^^^^^^ @@ -353,7 +361,7 @@ LL | -1i32 as u32; = note: the cast operand is `-1` error: casting `isize` to `usize` may lose the sign of the value - --> tests/ui/cast.rs:145:5 + --> tests/ui/cast.rs:146:5 | LL | -1isize as usize; | ^^^^^^^^^^^^^^^^ @@ -361,7 +369,7 @@ LL | -1isize as usize; = note: the cast operand is `-1` error: casting `i8` to `u8` may lose the sign of the value - --> tests/ui/cast.rs:157:5 + --> tests/ui/cast.rs:158:5 | LL | (i8::MIN).abs() as u8; | ^^^^^^^^^^^^^^^^^^^^^ @@ -369,7 +377,7 @@ LL | (i8::MIN).abs() as u8; = note: the cast operand is `-128` error: casting `i64` to `i8` may truncate the value - --> tests/ui/cast.rs:236:5 + --> tests/ui/cast.rs:237:5 | LL | (-99999999999i64).min(1) as i8; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -383,7 +391,7 @@ LL + i8::try_from((-99999999999i64).min(1)); | error: casting `u64` to `u8` may truncate the value - --> tests/ui/cast.rs:250:5 + --> tests/ui/cast.rs:251:5 | LL | 999999u64.clamp(0, 256) as u8; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -397,7 +405,7 @@ LL + u8::try_from(999999u64.clamp(0, 256)); | error: casting `main::E2` to `u8` may truncate the value - --> tests/ui/cast.rs:273:21 + --> tests/ui/cast.rs:274:21 | LL | let _ = self as u8; | ^^^^^^^^^^ @@ -410,7 +418,7 @@ LL + let _ = u8::try_from(self); | error: casting `main::E2::B` to `u8` will truncate the value - --> tests/ui/cast.rs:276:21 + --> tests/ui/cast.rs:277:21 | LL | let _ = Self::B as u8; | ^^^^^^^^^^^^^ @@ -419,7 +427,7 @@ LL | let _ = Self::B as u8; = help: to override `-D warnings` add `#[allow(clippy::cast_enum_truncation)]` error: casting `main::E5` to `i8` may truncate the value - --> tests/ui/cast.rs:318:21 + --> tests/ui/cast.rs:319:21 | LL | let _ = self as i8; | ^^^^^^^^^^ @@ -432,13 +440,13 @@ LL + let _ = i8::try_from(self); | error: casting `main::E5::A` to `i8` will truncate the value - --> tests/ui/cast.rs:321:21 + --> tests/ui/cast.rs:322:21 | LL | let _ = Self::A as i8; | ^^^^^^^^^^^^^ error: casting `main::E6` to `i16` may truncate the value - --> tests/ui/cast.rs:339:21 + --> tests/ui/cast.rs:340:21 | LL | let _ = self as i16; | ^^^^^^^^^^^ @@ -451,7 +459,7 @@ LL + let _ = i16::try_from(self); | error: casting `main::E7` to `usize` may truncate the value on targets with 32-bit wide pointers - --> tests/ui/cast.rs:359:21 + --> tests/ui/cast.rs:360:21 | LL | let _ = self as usize; | ^^^^^^^^^^^^^ @@ -464,7 +472,7 @@ LL + let _ = usize::try_from(self); | error: casting `main::E10` to `u16` may truncate the value - --> tests/ui/cast.rs:407:21 + --> tests/ui/cast.rs:408:21 | LL | let _ = self as u16; | ^^^^^^^^^^^ @@ -477,7 +485,7 @@ LL + let _ = u16::try_from(self); | error: casting `u32` to `u8` may truncate the value - --> tests/ui/cast.rs:419:13 + --> tests/ui/cast.rs:420:13 | LL | let c = (q >> 16) as u8; | ^^^^^^^^^^^^^^^ @@ -491,7 +499,7 @@ LL + let c = u8::try_from(q >> 16); | error: casting `u32` to `u8` may truncate the value - --> tests/ui/cast.rs:424:13 + --> tests/ui/cast.rs:425:13 | LL | let c = (q / 1000) as u8; | ^^^^^^^^^^^^^^^^ @@ -505,7 +513,7 @@ LL + let c = u8::try_from(q / 1000); | error: casting `i32` to `u32` may lose the sign of the value - --> tests/ui/cast.rs:442:32 + --> tests/ui/cast.rs:443:32 | LL | let _a = |x: i32| -> u32 { (x * x * x * x) as u32 }; | ^^^^^^^^^^^^^^^^^^^^^^ @@ -513,7 +521,7 @@ LL | let _a = |x: i32| -> u32 { (x * x * x * x) as u32 }; = note: the cast operand may contain values in the range `-2147483648..=2147483647` error: casting `i32` to `u32` may lose the sign of the value - --> tests/ui/cast.rs:446:5 + --> tests/ui/cast.rs:447:5 | LL | (-2_i32).pow(3) as u32; | ^^^^^^^^^^^^^^^^^^^^^^ @@ -521,7 +529,7 @@ LL | (-2_i32).pow(3) as u32; = note: the cast operand is `-8` error: casting `i32` to `u32` may lose the sign of the value - --> tests/ui/cast.rs:451:5 + --> tests/ui/cast.rs:452:5 | LL | (-5_i32 % 2) as u32; | ^^^^^^^^^^^^^^^^^^^ @@ -529,7 +537,7 @@ LL | (-5_i32 % 2) as u32; = note: the cast operand is `-1` error: casting `i32` to `u32` may lose the sign of the value - --> tests/ui/cast.rs:454:5 + --> tests/ui/cast.rs:455:5 | LL | (-5_i32 % -2) as u32; | ^^^^^^^^^^^^^^^^^^^^ @@ -537,7 +545,7 @@ LL | (-5_i32 % -2) as u32; = note: the cast operand is `-1` error: casting `i32` to `u32` may lose the sign of the value - --> tests/ui/cast.rs:458:5 + --> tests/ui/cast.rs:459:5 | LL | (-2_i32 >> 1) as u32; | ^^^^^^^^^^^^^^^^^^^^ @@ -545,7 +553,7 @@ LL | (-2_i32 >> 1) as u32; = note: the cast operand is `-1` error: casting `i32` to `u32` may lose the sign of the value - --> tests/ui/cast.rs:462:5 + --> tests/ui/cast.rs:463:5 | LL | (x * x) as u32; | ^^^^^^^^^^^^^^ @@ -553,7 +561,7 @@ LL | (x * x) as u32; = note: the cast operand may contain values in the range `-2147483648..=2147483647` error: casting `i32` to `u32` may lose the sign of the value - --> tests/ui/cast.rs:464:5 + --> tests/ui/cast.rs:465:5 | LL | (x * x * x) as u32; | ^^^^^^^^^^^^^^^^^^ @@ -561,7 +569,7 @@ LL | (x * x * x) as u32; = note: the cast operand may contain values in the range `-2147483648..=2147483647` error: casting `i16` to `u16` may lose the sign of the value - --> tests/ui/cast.rs:468:5 + --> tests/ui/cast.rs:469:5 | LL | (y * y * y * y * -2) as u16; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -569,7 +577,7 @@ LL | (y * y * y * y * -2) as u16; = note: the cast operand may contain values in the range `-32768..=32767` error: casting `i16` to `u16` may lose the sign of the value - --> tests/ui/cast.rs:471:5 + --> tests/ui/cast.rs:472:5 | LL | (y * y * y / y * 2) as u16; | ^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -577,7 +585,7 @@ LL | (y * y * y / y * 2) as u16; = note: the cast operand may contain values in the range `-32768..=32767` error: casting `i16` to `u16` may lose the sign of the value - --> tests/ui/cast.rs:473:5 + --> tests/ui/cast.rs:474:5 | LL | (y * y / y * 2) as u16; | ^^^^^^^^^^^^^^^^^^^^^^ @@ -585,7 +593,7 @@ LL | (y * y / y * 2) as u16; = note: the cast operand may contain values in the range `-32768..=32767` error: casting `i16` to `u16` may lose the sign of the value - --> tests/ui/cast.rs:476:5 + --> tests/ui/cast.rs:477:5 | LL | (y / y * y * -2) as u16; | ^^^^^^^^^^^^^^^^^^^^^^^ @@ -593,7 +601,7 @@ LL | (y / y * y * -2) as u16; = note: the cast operand may contain values in the range `-32768..=32767` error: equal expressions as operands to `/` - --> tests/ui/cast.rs:476:6 + --> tests/ui/cast.rs:477:6 | LL | (y / y * y * -2) as u16; | ^^^^^ @@ -601,7 +609,7 @@ LL | (y / y * y * -2) as u16; = note: `#[deny(clippy::eq_op)]` on by default error: casting `i16` to `u16` may lose the sign of the value - --> tests/ui/cast.rs:480:5 + --> tests/ui/cast.rs:481:5 | LL | (y + y + y + -2) as u16; | ^^^^^^^^^^^^^^^^^^^^^^^ @@ -609,7 +617,7 @@ LL | (y + y + y + -2) as u16; = note: the cast operand may contain values in the range `-32768..=32767` error: casting `i16` to `u16` may lose the sign of the value - --> tests/ui/cast.rs:483:5 + --> tests/ui/cast.rs:484:5 | LL | (y + y + y + 2) as u16; | ^^^^^^^^^^^^^^^^^^^^^^ @@ -617,7 +625,7 @@ LL | (y + y + y + 2) as u16; = note: the cast operand may contain values in the range `-32768..=32767` error: casting `i16` to `u16` may lose the sign of the value - --> tests/ui/cast.rs:487:5 + --> tests/ui/cast.rs:488:5 | LL | (z + -2) as u16; | ^^^^^^^^^^^^^^^ @@ -625,7 +633,7 @@ LL | (z + -2) as u16; = note: the cast operand may contain values in the range `-32768..=32767` error: casting `i16` to `u16` may lose the sign of the value - --> tests/ui/cast.rs:490:5 + --> tests/ui/cast.rs:491:5 | LL | (z + z + 2) as u16; | ^^^^^^^^^^^^^^^^^^ @@ -633,7 +641,7 @@ LL | (z + z + 2) as u16; = note: the cast operand may contain values in the range `-32768..=32767` error: casting `i32` to `u32` may lose the sign of the value - --> tests/ui/cast.rs:494:9 + --> tests/ui/cast.rs:495:9 | LL | (a * a * b * b * c * c) as u32; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -641,7 +649,7 @@ LL | (a * a * b * b * c * c) as u32; = note: the cast operand may contain values in the range `-2147483648..=2147483647` error: casting `i32` to `u32` may lose the sign of the value - --> tests/ui/cast.rs:496:9 + --> tests/ui/cast.rs:497:9 | LL | (a * b * c) as u32; | ^^^^^^^^^^^^^^^^^^ @@ -649,7 +657,7 @@ LL | (a * b * c) as u32; = note: the cast operand may contain values in the range `-2147483648..=2147483647` error: casting `i32` to `u32` may lose the sign of the value - --> tests/ui/cast.rs:499:9 + --> tests/ui/cast.rs:500:9 | LL | (a * -b * c) as u32; | ^^^^^^^^^^^^^^^^^^^ @@ -657,7 +665,7 @@ LL | (a * -b * c) as u32; = note: the cast operand may contain values in the range `-2147483648..=2147483647` error: casting `i32` to `u32` may lose the sign of the value - --> tests/ui/cast.rs:502:9 + --> tests/ui/cast.rs:503:9 | LL | (a * b * c * c) as u32; | ^^^^^^^^^^^^^^^^^^^^^^ @@ -665,7 +673,7 @@ LL | (a * b * c * c) as u32; = note: the cast operand may contain values in the range `-2147483648..=2147483647` error: casting `i32` to `u32` may lose the sign of the value - --> tests/ui/cast.rs:504:9 + --> tests/ui/cast.rs:505:9 | LL | (a * -2) as u32; | ^^^^^^^^^^^^^^^ @@ -673,7 +681,7 @@ LL | (a * -2) as u32; = note: the cast operand may contain values in the range `-2147483648..=2147483647` error: casting `i32` to `u32` may lose the sign of the value - --> tests/ui/cast.rs:507:9 + --> tests/ui/cast.rs:508:9 | LL | (a * b * c * -2) as u32; | ^^^^^^^^^^^^^^^^^^^^^^^ @@ -681,7 +689,7 @@ LL | (a * b * c * -2) as u32; = note: the cast operand may contain values in the range `-2147483648..=2147483647` error: casting `i32` to `u32` may lose the sign of the value - --> tests/ui/cast.rs:510:9 + --> tests/ui/cast.rs:511:9 | LL | (a / b) as u32; | ^^^^^^^^^^^^^^ @@ -689,7 +697,7 @@ LL | (a / b) as u32; = note: the cast operand may contain values in the range `-2147483648..=2147483647` error: casting `i32` to `u32` may lose the sign of the value - --> tests/ui/cast.rs:512:9 + --> tests/ui/cast.rs:513:9 | LL | (a / b * c) as u32; | ^^^^^^^^^^^^^^^^^^ @@ -697,7 +705,7 @@ LL | (a / b * c) as u32; = note: the cast operand may contain values in the range `-2147483648..=2147483647` error: casting `i32` to `u32` may lose the sign of the value - --> tests/ui/cast.rs:515:9 + --> tests/ui/cast.rs:516:9 | LL | (a / b + b * c) as u32; | ^^^^^^^^^^^^^^^^^^^^^^ @@ -705,7 +713,7 @@ LL | (a / b + b * c) as u32; = note: the cast operand may contain values in the range `-2147483648..=2147483647` error: casting `i32` to `u32` may lose the sign of the value - --> tests/ui/cast.rs:518:9 + --> tests/ui/cast.rs:519:9 | LL | a.saturating_pow(3) as u32; | ^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -713,7 +721,7 @@ LL | a.saturating_pow(3) as u32; = note: the cast operand may contain values in the range `-2147483648..=2147483647` error: casting `i32` to `u32` may lose the sign of the value - --> tests/ui/cast.rs:521:9 + --> tests/ui/cast.rs:522:9 | LL | (a.abs() * b.pow(2) / c.abs()) as u32 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -721,7 +729,7 @@ LL | (a.abs() * b.pow(2) / c.abs()) as u32 = note: the cast operand may contain values in the range `-2147483648..=2147483647` error: casting `i32` to `u32` may lose the sign of the value - --> tests/ui/cast.rs:529:21 + --> tests/ui/cast.rs:530:21 | LL | let _ = i32::MIN as u32; // cast_sign_loss | ^^^^^^^^^^^^^^^ @@ -733,7 +741,7 @@ LL | m!(); = note: this error originates in the macro `m` (in Nightly builds, run with -Z macro-backtrace for more info) error: casting `u32` to `u8` may truncate the value - --> tests/ui/cast.rs:532:21 + --> tests/ui/cast.rs:533:21 | LL | let _ = u32::MAX as u8; // cast_possible_truncation | ^^^^^^^^^^^^^^ @@ -751,7 +759,7 @@ LL + let _ = u8::try_from(u32::MAX); // cast_possible_truncation | error: casting `f64` to `f32` may truncate the value - --> tests/ui/cast.rs:535:21 + --> tests/ui/cast.rs:536:21 | LL | let _ = std::f64::consts::PI as f32; // cast_possible_truncation | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -763,7 +771,7 @@ LL | m!(); = note: this error originates in the macro `m` (in Nightly builds, run with -Z macro-backtrace for more info) error: casting `i64` to `usize` may truncate the value on targets with 32-bit wide pointers - --> tests/ui/cast.rs:546:5 + --> tests/ui/cast.rs:547:5 | LL | bar.unwrap().unwrap() as usize | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -777,7 +785,7 @@ LL + usize::try_from(bar.unwrap().unwrap()) | error: casting `i64` to `usize` may lose the sign of the value - --> tests/ui/cast.rs:546:5 + --> tests/ui/cast.rs:547:5 | LL | bar.unwrap().unwrap() as usize | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -785,7 +793,7 @@ LL | bar.unwrap().unwrap() as usize = note: the cast operand may contain values in the range `-9223372036854775808..=9223372036854775807` error: casting `u64` to `u8` may truncate the value - --> tests/ui/cast.rs:563:5 + --> tests/ui/cast.rs:564:5 | LL | (256 & 999999u64) as u8; | ^^^^^^^^^^^^^^^^^^^^^^^ @@ -799,7 +807,7 @@ LL + u8::try_from(256 & 999999u64); | error: casting `u64` to `u8` may truncate the value - --> tests/ui/cast.rs:566:5 + --> tests/ui/cast.rs:567:5 | LL | (255 % 999999u64) as u8; | ^^^^^^^^^^^^^^^^^^^^^^^ @@ -813,7 +821,7 @@ LL + u8::try_from(255 % 999999u64); | error: casting `u64` to `u32` may truncate the value - --> tests/ui/cast.rs:577:5 + --> tests/ui/cast.rs:578:5 | LL | result as u32 | ^^^^^^^^^^^^^ @@ -827,7 +835,7 @@ LL + u32::try_from(result) | error: casting `u16` to `i16` may wrap around the value - --> tests/ui/cast.rs:584:13 + --> tests/ui/cast.rs:585:13 | LL | let x = x as i16 - 0x180; | ^^^^^^^^ @@ -835,7 +843,7 @@ LL | let x = x as i16 - 0x180; = note: the cast operand may contain values in the range `0..=65535` error: casting `u16` to `u8` may truncate the value - --> tests/ui/cast.rs:589:5 + --> tests/ui/cast.rs:590:5 | LL | ((x + 1) >> 1) as u8 | ^^^^^^^^^^^^^^^^^^^^ @@ -849,7 +857,7 @@ LL + u8::try_from((x + 1) >> 1) | error: casting `u16` to `i16` may wrap around the value - --> tests/ui/cast.rs:595:13 + --> tests/ui/cast.rs:596:13 | LL | let x = x as i16 - 0x180; | ^^^^^^^^ @@ -857,7 +865,7 @@ LL | let x = x as i16 - 0x180; = note: the cast operand may contain values in the range `0..=65535` error: casting `u32` to `u8` may truncate the value - --> tests/ui/cast.rs:603:5 + --> tests/ui/cast.rs:604:5 | LL | ((x as u32 * 255 + 32895) >> 16) as u8 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -871,7 +879,7 @@ LL + u8::try_from((x as u32 * 255 + 32895) >> 16) | error: casting `u32` to `u16` may truncate the value - --> tests/ui/cast.rs:624:16 + --> tests/ui/cast.rs:625:16 | LL | return ((sign >> 16) | 0x7C00u32 | nan_bit | (man >> 13)) as u16; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -884,14 +892,6 @@ LL - return ((sign >> 16) | 0x7C00u32 | nan_bit | (man >> 13)) as u16; LL + return u16::try_from((sign >> 16) | 0x7C00u32 | nan_bit | (man >> 13)); | -error: casting `u32` to `i32` may wrap around the value - --> tests/ui/cast.rs:631:24 - | -LL | let unbiased_exp = ((exp >> 23) as i32) - 127; - | ^^^^^^^^^^^^^^^^^^^^ - | - = note: the cast operand may contain values in the range `0..=255` - error: casting `u32` to `u16` may truncate the value --> tests/ui/cast.rs:637:16 | diff --git a/tests/ui/cast_size.64bit.stderr b/tests/ui/cast_size.64bit.stderr index 180d62f072a9..93acf3b7c25e 100644 --- a/tests/ui/cast_size.64bit.stderr +++ b/tests/ui/cast_size.64bit.stderr @@ -1,5 +1,5 @@ error: casting `isize` to `i8` may truncate the value - --> tests/ui/cast_size.rs:17:5 + --> tests/ui/cast_size.rs:21:5 | LL | 1isize as i8; | ^^^^^^^^^^^^ @@ -15,7 +15,7 @@ LL + i8::try_from(1isize); | error: casting `isize` to `f32` causes a loss of precision (`isize` is 32 or 64 bits wide, but `f32`'s mantissa is only 23 bits wide) - --> tests/ui/cast_size.rs:24:5 + --> tests/ui/cast_size.rs:28:5 | LL | x0 as f32; | ^^^^^^^^^ @@ -24,175 +24,201 @@ LL | x0 as f32; = help: to override `-D warnings` add `#[allow(clippy::cast_precision_loss)]` error: casting `usize` to `f32` causes a loss of precision (`usize` is 32 or 64 bits wide, but `f32`'s mantissa is only 23 bits wide) - --> tests/ui/cast_size.rs:26:5 + --> tests/ui/cast_size.rs:30:5 | LL | x1 as f32; | ^^^^^^^^^ error: casting `isize` to `f64` causes a loss of precision on targets with 64-bit wide pointers (`isize` is 64 bits wide, but `f64`'s mantissa is only 52 bits wide) - --> tests/ui/cast_size.rs:28:5 + --> tests/ui/cast_size.rs:32:5 | LL | x0 as f64; | ^^^^^^^^^ error: casting `usize` to `f64` causes a loss of precision on targets with 64-bit wide pointers (`usize` is 64 bits wide, but `f64`'s mantissa is only 52 bits wide) - --> tests/ui/cast_size.rs:30:5 + --> tests/ui/cast_size.rs:34:5 | LL | x1 as f64; | ^^^^^^^^^ error: casting `isize` to `i32` may truncate the value on targets with 64-bit wide pointers - --> tests/ui/cast_size.rs:35:5 + --> tests/ui/cast_size.rs:39:5 | -LL | 1isize as i32; - | ^^^^^^^^^^^^^ +LL | get_value::() as i32; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operand is `1` + = note: the cast operand may contain values in the range `-9223372036854775808..=9223372036854775807` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | -LL - 1isize as i32; -LL + i32::try_from(1isize); +LL - get_value::() as i32; +LL + i32::try_from(get_value::()); | error: casting `isize` to `u32` may truncate the value on targets with 64-bit wide pointers - --> tests/ui/cast_size.rs:37:5 + --> tests/ui/cast_size.rs:41:5 | -LL | 1isize as u32; - | ^^^^^^^^^^^^^ +LL | get_value::() as u32; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operand is `1` + = note: the cast operand may contain values in the range `-9223372036854775808..=9223372036854775807` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | -LL - 1isize as u32; -LL + u32::try_from(1isize); +LL - get_value::() as u32; +LL + u32::try_from(get_value::()); + | + +error: casting `isize` to `u32` may lose the sign of the value + --> tests/ui/cast_size.rs:41:5 + | +LL | get_value::() as u32; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | + = note: the cast operand may contain values in the range `-9223372036854775808..=9223372036854775807` + = note: `-D clippy::cast-sign-loss` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::cast_sign_loss)]` error: casting `usize` to `u32` may truncate the value on targets with 64-bit wide pointers - --> tests/ui/cast_size.rs:39:5 + --> tests/ui/cast_size.rs:44:5 | -LL | 1usize as u32; - | ^^^^^^^^^^^^^ +LL | get_value::() as u32; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operand is `1` + = note: the cast operand may contain values in the range `0..=18446744073709551615` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | -LL - 1usize as u32; -LL + u32::try_from(1usize); +LL - get_value::() as u32; +LL + u32::try_from(get_value::()); | error: casting `usize` to `i32` may truncate the value on targets with 64-bit wide pointers - --> tests/ui/cast_size.rs:41:5 + --> tests/ui/cast_size.rs:46:5 | -LL | 1usize as i32; - | ^^^^^^^^^^^^^ +LL | get_value::() as i32; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operand is `1` + = note: the cast operand may contain values in the range `0..=18446744073709551615` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | -LL - 1usize as i32; -LL + i32::try_from(1usize); +LL - get_value::() as i32; +LL + i32::try_from(get_value::()); | error: casting `usize` to `i32` may wrap around the value on targets with 32-bit wide pointers - --> tests/ui/cast_size.rs:41:5 + --> tests/ui/cast_size.rs:46:5 | -LL | 1usize as i32; - | ^^^^^^^^^^^^^ +LL | get_value::() as i32; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operand is `1` + = note: the cast operand may contain values in the range `0..=18446744073709551615` = note: `-D clippy::cast-possible-wrap` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::cast_possible_wrap)]` error: casting `i64` to `isize` may truncate the value on targets with 32-bit wide pointers - --> tests/ui/cast_size.rs:44:5 + --> tests/ui/cast_size.rs:49:5 | -LL | 1i64 as isize; - | ^^^^^^^^^^^^^ +LL | get_value::() as isize; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operand is `1` + = note: the cast operand may contain values in the range `-9223372036854775808..=9223372036854775807` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | -LL - 1i64 as isize; -LL + isize::try_from(1i64); +LL - get_value::() as isize; +LL + isize::try_from(get_value::()); | error: casting `i64` to `usize` may truncate the value on targets with 32-bit wide pointers - --> tests/ui/cast_size.rs:46:5 + --> tests/ui/cast_size.rs:51:5 | -LL | 1i64 as usize; - | ^^^^^^^^^^^^^ +LL | get_value::() as usize; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operand is `1` + = note: the cast operand may contain values in the range `-9223372036854775808..=9223372036854775807` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | -LL - 1i64 as usize; -LL + usize::try_from(1i64); +LL - get_value::() as usize; +LL + usize::try_from(get_value::()); + | + +error: casting `i64` to `usize` may lose the sign of the value + --> tests/ui/cast_size.rs:51:5 + | +LL | get_value::() as usize; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | + = note: the cast operand may contain values in the range `-9223372036854775808..=9223372036854775807` error: casting `u64` to `isize` may truncate the value on targets with 32-bit wide pointers - --> tests/ui/cast_size.rs:48:5 + --> tests/ui/cast_size.rs:54:5 | -LL | 1u64 as isize; - | ^^^^^^^^^^^^^ +LL | get_value::() as isize; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operand is `1` + = note: the cast operand may contain values in the range `0..=18446744073709551615` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | -LL - 1u64 as isize; -LL + isize::try_from(1u64); +LL - get_value::() as isize; +LL + isize::try_from(get_value::()); | error: casting `u64` to `isize` may wrap around the value on targets with 64-bit wide pointers - --> tests/ui/cast_size.rs:48:5 + --> tests/ui/cast_size.rs:54:5 | -LL | 1u64 as isize; - | ^^^^^^^^^^^^^ +LL | get_value::() as isize; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operand is `1` + = note: the cast operand may contain values in the range `0..=18446744073709551615` error: casting `u64` to `usize` may truncate the value on targets with 32-bit wide pointers - --> tests/ui/cast_size.rs:51:5 + --> tests/ui/cast_size.rs:57:5 | -LL | 1u64 as usize; - | ^^^^^^^^^^^^^ +LL | get_value::() as usize; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operand is `1` + = note: the cast operand may contain values in the range `0..=18446744073709551615` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | -LL - 1u64 as usize; -LL + usize::try_from(1u64); +LL - get_value::() as usize; +LL + usize::try_from(get_value::()); | error: casting `u32` to `isize` may wrap around the value on targets with 32-bit wide pointers - --> tests/ui/cast_size.rs:53:5 + --> tests/ui/cast_size.rs:59:5 | -LL | 1u32 as isize; - | ^^^^^^^^^^^^^ +LL | get_value::() as isize; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operand is `1` + = note: the cast operand may contain values in the range `0..=4294967295` + +error: casting `i32` to `usize` may lose the sign of the value + --> tests/ui/cast_size.rs:63:5 + | +LL | get_value::() as usize; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: the cast operand may contain values in the range `-2147483648..=2147483647` error: casting `i32` to `f32` causes a loss of precision (`i32` is 32 bits wide, but `f32`'s mantissa is only 23 bits wide) - --> tests/ui/cast_size.rs:61:5 + --> tests/ui/cast_size.rs:68:5 | LL | 999_999_999 as f32; | ^^^^^^^^^^^^^^^^^^ error: casting `usize` to `f64` causes a loss of precision on targets with 64-bit wide pointers (`usize` is 64 bits wide, but `f64`'s mantissa is only 52 bits wide) - --> tests/ui/cast_size.rs:63:5 + --> tests/ui/cast_size.rs:70:5 | LL | 9_999_999_999_999_999usize as f64; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: casting `usize` to `u16` may truncate the value - --> tests/ui/cast_size.rs:71:20 + --> tests/ui/cast_size.rs:78:20 | LL | const N: u16 = M as u16; | ^^^^^^^^ @@ -200,5 +226,5 @@ LL | const N: u16 = M as u16; = note: the cast operand is `100` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... -error: aborting due to 19 previous errors +error: aborting due to 22 previous errors diff --git a/tests/ui/cast_size.rs b/tests/ui/cast_size.rs index ecc586694191..9ecedfbe5fac 100644 --- a/tests/ui/cast_size.rs +++ b/tests/ui/cast_size.rs @@ -12,12 +12,16 @@ )] #![allow(clippy::no_effect, clippy::unnecessary_operation)] +fn get_value() -> T { + todo!() +} + fn main() { // Casting from *size 1isize as i8; //~^ cast_possible_truncation - let x0 = 1isize; - let x1 = 1usize; + let x0: isize = get_value(); + let x1: usize = get_value(); // FIXME(f16_f128): enable f16 and f128 conversions once const eval supports them // x0 as f16; // x1 as f16; @@ -32,29 +36,32 @@ fn main() { // x0 as f128; // x1 as f128; - 1isize as i32; + get_value::() as i32; //~^ cast_possible_truncation - 1isize as u32; + get_value::() as u32; //~^ cast_possible_truncation - 1usize as u32; + //~| cast_sign_loss + get_value::() as u32; //~^ cast_possible_truncation - 1usize as i32; + get_value::() as i32; //~^ cast_possible_truncation //~| cast_possible_wrap - 1i64 as isize; + get_value::() as isize; //~^ cast_possible_truncation - 1i64 as usize; + get_value::() as usize; //~^ cast_possible_truncation - 1u64 as isize; + //~| cast_sign_loss + get_value::() as isize; //~^ cast_possible_truncation //~| cast_possible_wrap - 1u64 as usize; + get_value::() as usize; //~^ cast_possible_truncation - 1u32 as isize; + get_value::() as isize; //~^ cast_possible_wrap - 1u32 as usize; // Should not trigger any lint - 1i32 as isize; // Neither should this - 1i32 as usize; + get_value::() as usize; // Should not trigger any lint + get_value::() as isize; // Neither should this + get_value::() as usize; + //~^ cast_sign_loss // Big integer literal to float // 999_999 as f16; From 96b8dbe6ecde2546947591fbc4a691f47794f436 Mon Sep 17 00:00:00 2001 From: RunDevelopment Date: Mon, 4 Aug 2025 14:48:44 +0200 Subject: [PATCH 08/13] Support more methods and improve interval arithmetic --- clippy_utils/src/rinterval/arithmetic.rs | 318 ++++++++++++++++++++++- clippy_utils/src/rinterval/mod.rs | 20 +- clippy_utils/src/sym.rs | 12 + 3 files changed, 338 insertions(+), 12 deletions(-) diff --git a/clippy_utils/src/rinterval/arithmetic.rs b/clippy_utils/src/rinterval/arithmetic.rs index 9e5713237acf..c3fc7a1c2aea 100644 --- a/clippy_utils/src/rinterval/arithmetic.rs +++ b/clippy_utils/src/rinterval/arithmetic.rs @@ -186,6 +186,13 @@ impl Arithmetic { Self::wrapping_shr(value, shift) } } + pub fn next_power_of_two(&self, value: &IInterval) -> ArithResult { + if self.checked { + Self::strict_next_power_of_two(value) + } else { + Self::wrapping_next_power_of_two(value) + } + } /// Addition which saturates on overflow. pub fn saturating_add(lhs: &IInterval, rhs: &IInterval) -> ArithResult { @@ -1313,6 +1320,130 @@ impl Arithmetic { } } + /// Log2, which panics for values <= 0. + pub fn ilog(x: &IInterval, base: &IInterval) -> ArithResult { + let ty = check_same_ty(x, base)?; + + if x.is_empty() || base.is_empty() { + return Ok(IInterval::empty(IntType::U32)); + } + + let (min, max) = match ty.info() { + IntTypeInfo::Signed(_, _) => { + let (mut x_min, x_max) = x.as_signed(); + let (mut base_min, base_max) = base.as_signed(); + + if x_max <= 0 { + return Ok(IInterval::empty(IntType::U32)); + } + if x_min <= 0 { + x_min = 1; // ignore non-positive values + } + + if base_max < 2 { + return Ok(IInterval::empty(IntType::U32)); + } + if base_min < 2 { + base_min = 2; + } + + (x_min.ilog(base_max), x_max.ilog(base_min)) + }, + IntTypeInfo::Unsigned(_) => { + let (mut x_min, x_max) = x.as_unsigned(); + let (mut base_min, base_max) = base.as_unsigned(); + + if x_max == 0 { + return Ok(IInterval::empty(IntType::U32)); + } + if x_min == 0 { + x_min = 1; // ignore non-positive values + } + + if base_max < 2 { + return Ok(IInterval::empty(IntType::U32)); + } + if base_min < 2 { + base_min = 2; + } + + (x_min.ilog(base_max), x_max.ilog(base_min)) + }, + }; + + Ok(IInterval::new_unsigned(IntType::U32, min as u128, max as u128)) + } + /// Log2, which panics for values <= 0. + pub fn ilog2(x: &IInterval) -> ArithResult { + if x.is_empty() { + return Ok(IInterval::empty(IntType::U32)); + } + + let ty = x.ty; + + let (min, max) = match ty.info() { + IntTypeInfo::Signed(_, _) => { + let (mut x_min, x_max) = x.as_signed(); + if x_max <= 0 { + return Ok(IInterval::empty(IntType::U32)); + } + if x_min <= 0 { + x_min = 1; // ignore non-positive values + } + + (x_min.ilog2(), x_max.ilog2()) + }, + IntTypeInfo::Unsigned(_) => { + let (mut x_min, x_max) = x.as_unsigned(); + if x_max == 0 { + return Ok(IInterval::empty(IntType::U32)); + } + if x_min == 0 { + x_min = 1; // ignore non-positive values + } + + (x_min.ilog2(), x_max.ilog2()) + }, + }; + + Ok(IInterval::new_unsigned(IntType::U32, min as u128, max as u128)) + } + /// Log10, which panics for values <= 0. + pub fn ilog10(x: &IInterval) -> ArithResult { + if x.is_empty() { + return Ok(IInterval::empty(IntType::U32)); + } + + let ty = x.ty; + + let (min, max) = match ty.info() { + IntTypeInfo::Signed(_, _) => { + let (mut x_min, x_max) = x.as_signed(); + if x_max <= 0 { + return Ok(IInterval::empty(IntType::U32)); + } + if x_min <= 0 { + x_min = 1; // ignore non-positive values + } + + (x_min.ilog10(), x_max.ilog10()) + }, + IntTypeInfo::Unsigned(_) => { + let (mut x_min, x_max) = x.as_unsigned(); + if x_max == 0 { + return Ok(IInterval::empty(IntType::U32)); + } + if x_min == 0 { + x_min = 1; // ignore non-positive values + } + + (x_min.ilog10(), x_max.ilog10()) + }, + }; + + Ok(IInterval::new_unsigned(IntType::U32, min as u128, max as u128)) + } + /// Power which saturates on overflow. pub fn saturating_pow(lhs: &IInterval, rhs: &IInterval) -> ArithResult { if rhs.ty != IntType::U32 { @@ -1722,6 +1853,47 @@ impl Arithmetic { } } + /// Absolute difference. + pub fn abs_diff(lhs: &IInterval, rhs: &IInterval) -> ArithResult { + let ty = check_same_ty(lhs, rhs)?; + let ret_ty = ty.to_unsigned(); + + if lhs.is_empty() || rhs.is_empty() { + return Ok(IInterval::empty(ret_ty)); + } + + match ty.info() { + IntTypeInfo::Signed(_, _) => { + let (l_min, l_max) = lhs.as_signed(); + let (r_min, r_max) = rhs.as_signed(); + + let (min, max) = if l_max < r_min { + (r_min.abs_diff(l_max), r_max.abs_diff(l_min)) + } else if r_max < l_min { + (l_min.abs_diff(r_max), l_max.abs_diff(r_min)) + } else { + (0, u128::max(r_min.abs_diff(l_max), l_min.abs_diff(r_max))) + }; + + Ok(IInterval::new_unsigned(ret_ty, min, max)) + }, + IntTypeInfo::Unsigned(_) => { + let (l_min, l_max) = lhs.as_unsigned(); + let (r_min, r_max) = rhs.as_unsigned(); + + let (min, max) = if l_max < r_min { + (r_min - l_max, r_max - l_min) + } else if r_max < l_min { + (l_min - r_max, l_max - r_min) + } else { + (0, u128::max(r_min.abs_diff(l_max), l_min.abs_diff(r_max))) + }; + + Ok(IInterval::new_unsigned(ret_ty, min, max)) + }, + } + } + /// Minimum. pub fn min(lhs: &IInterval, rhs: &IInterval) -> ArithResult { let ty = check_same_ty(lhs, rhs)?; @@ -1838,13 +2010,30 @@ impl Arithmetic { let ty = check_same_ty(lhs, rhs)?; check_non_empty!(lhs, rhs); - let l_bits = Bits::from_non_empty(lhs); - let r_bits = Bits::from_non_empty(rhs); + if ty.is_signed() { + Self::not(&Self::and(&Self::not(lhs)?, &Self::not(rhs)?)?) + } else { + let l_bits = Bits::from_non_empty(lhs); + let r_bits = Bits::from_non_empty(rhs); - let zero = l_bits.zero | r_bits.zero; - let one = l_bits.one | r_bits.one; + let zero = l_bits.zero | r_bits.zero; + let one = l_bits.one | r_bits.one; - Ok(Bits::new(zero, one).to_interval(ty)) + let (mut min, mut max) = (zero, one); + debug_assert_eq!( + IInterval::new_unsigned(ty, min, max), + Bits::new(zero, one).to_interval(ty) + ); + + // This narrows the range using: + // max(a,b) <= a|b <= a + b + let (l_min, l_max) = lhs.as_unsigned(); + let (r_min, r_max) = rhs.as_unsigned(); + max = max.min(l_max.saturating_add(r_max)); + min = min.max(l_min).max(r_min); + + Ok(IInterval::new_unsigned(ty, min, max)) + } } /// Bitwise XOR. pub fn xor(lhs: &IInterval, rhs: &IInterval) -> ArithResult { @@ -2040,10 +2229,7 @@ impl Arithmetic { &IInterval::new_unsigned(IntType::U32, r_min as u128, r_max as u128), ) } else { - Self::strict_shr( - lhs, - &IInterval::new_unsigned(IntType::U32, 0, (bit_width - 1) as u128), - ) + Self::strict_shr(lhs, &IInterval::new_unsigned(IntType::U32, 0, (bit_width - 1) as u128)) } } pub fn unbounded_shr(lhs: &IInterval, rhs: &IInterval) -> ArithResult { @@ -2170,6 +2356,120 @@ impl Arithmetic { Self::count_ones(&Self::not(x)?) } + pub fn signum(x: &IInterval) -> ArithResult { + check_non_empty!(x); + + let ty = x.ty; + + match ty.info() { + IntTypeInfo::Signed(_, _) => { + let (min, max) = x.as_signed(); + + if min > 0 { + Ok(IInterval::single_signed(ty, 1)) + } else if max < 0 { + Ok(IInterval::single_signed(ty, -1)) + } else { + let min = if min < 0 { -1 } else { 0 }; + let max = if max > 0 { 1 } else { 0 }; + Ok(IInterval::new_signed(ty, min, max)) + } + }, + IntTypeInfo::Unsigned(_) => Err(ArithError::Unsupported), + } + } + + /// Next power of two which panics on overflow. + pub fn strict_next_power_of_two(x: &IInterval) -> ArithResult { + check_non_empty!(x); + + let ty = x.ty; + + match ty.info() { + IntTypeInfo::Signed(_, _) => Err(ArithError::Unsupported), + IntTypeInfo::Unsigned(t_max) => { + let (x_min, x_max) = x.as_unsigned(); + + let min = x_min.checked_next_power_of_two().filter(|i| i <= &t_max); + let max = x_max.checked_next_power_of_two().filter(|i| i <= &t_max); + + let result = match (min, max) { + (Some(min), Some(max)) => IInterval::new_unsigned(ty, min, max), + (Some(min), None) => IInterval::new_unsigned(ty, min, t_max ^ (t_max >> 1)), + (None, _) => IInterval::empty(ty), + }; + + Ok(result) + }, + } + } + /// Next power of two which wraps on overflow. + pub fn wrapping_next_power_of_two(x: &IInterval) -> ArithResult { + check_non_empty!(x); + + let ty = x.ty; + + match ty.info() { + IntTypeInfo::Signed(_, _) => Err(ArithError::Unsupported), + IntTypeInfo::Unsigned(t_max) => { + let (x_min, x_max) = x.as_unsigned(); + + let min = x_min.checked_next_power_of_two().filter(|i| i <= &t_max); + let max = x_max.checked_next_power_of_two().filter(|i| i <= &t_max); + + let result = match (min, max) { + (Some(min), Some(max)) => IInterval::new_unsigned(ty, min, max), + (Some(_), None) => IInterval::new_unsigned(ty, 0, t_max ^ (t_max >> 1)), + (None, _) => IInterval::single_unsigned(ty, 0), + }; + + Ok(result) + }, + } + } + + /// Next multiple of which panics on overflow. + pub fn strict_next_multiple_of(lhs: &IInterval, rhs: &IInterval) -> ArithResult { + let ty = check_same_ty(lhs, rhs)?; + check_non_empty!(lhs, rhs); + + match ty.info() { + IntTypeInfo::Signed(_, _) => Err(ArithError::Unsupported), + IntTypeInfo::Unsigned(t_max) => { + let (l_min, l_max) = lhs.as_unsigned(); + let (mut r_min, r_max) = rhs.as_unsigned(); + + if r_max == 0 { + // x % 0 panics + return Ok(IInterval::empty(ty)); + } + if r_min == 0 { + r_min = 1; + } + + if r_min == r_max { + let r = r_min; + + // This is a lot easier if rhs is a constant. + let Some(min) = l_min.checked_next_multiple_of(r).filter(|i| i <= &t_max) else { + return Ok(IInterval::empty(ty)); + }; + let max = l_max + .checked_next_multiple_of(r) + .map(|i| if i > t_max { i - r } else { i }) + .unwrap_or(t_max); + + return Ok(IInterval::new_unsigned(ty, min, max)); + } + + let min = l_min; + let max = l_max.saturating_add(r_max - 1).min(t_max); + + Ok(IInterval::new_unsigned(ty, min, max)) + }, + } + } + /// Casts unsigned to signed. pub fn cast_signed(x: &IInterval) -> ArithResult { if x.ty.is_signed() { diff --git a/clippy_utils/src/rinterval/mod.rs b/clippy_utils/src/rinterval/mod.rs index 8ab15b609cd0..47f0cc6d373b 100644 --- a/clippy_utils/src/rinterval/mod.rs +++ b/clippy_utils/src/rinterval/mod.rs @@ -192,8 +192,9 @@ impl<'c, 'cx> IntervalCtxt<'c, 'cx> { sym::strict_neg => Some(|_, x| Arithmetic::strict_neg(x)), sym::wrapping_neg => Some(|_, x| Arithmetic::wrapping_neg(x)), - sym::isqrt => Some(|_, x| Arithmetic::isqrt(x)), - sym::checked_isqrt => Some(|_, x| Arithmetic::isqrt(x)), + sym::isqrt | sym::checked_isqrt => Some(|_, x| Arithmetic::isqrt(x)), + sym::ilog2 | sym::checked_ilog2 => Some(|_, x| Arithmetic::ilog2(x)), + sym::ilog10 | sym::checked_ilog10 => Some(|_, x| Arithmetic::ilog10(x)), sym::abs => Some(Arithmetic::abs), sym::checked_abs => Some(|_, x| Arithmetic::strict_abs(x)), @@ -204,6 +205,12 @@ impl<'c, 'cx> IntervalCtxt<'c, 'cx> { sym::not => Some(|_, x| Arithmetic::not(x)), + sym::signum => Some(|_, x| Arithmetic::signum(x)), + + sym::next_power_of_two => Some(Arithmetic::next_power_of_two), + sym::checked_next_power_of_two => Some(|_, x| Arithmetic::strict_next_power_of_two(x)), + sym::wrapping_next_power_of_two => Some(|_, x| Arithmetic::wrapping_next_power_of_two(x)), + sym::cast_signed => Some(|_, x| Arithmetic::cast_signed(x)), sym::cast_unsigned => Some(|_, x| Arithmetic::cast_unsigned(x)), @@ -266,12 +273,19 @@ impl<'c, 'cx> IntervalCtxt<'c, 'cx> { sym::midpoint => Some(|_, l, r| Arithmetic::midpoint(l, r)), + sym::abs_diff => Some(|_, l, r| Arithmetic::abs_diff(l, r)), + + sym::next_multiple_of => Some(|_, l, r| Arithmetic::strict_next_multiple_of(l, r)), + sym::checked_next_multiple_of => Some(|_, l, r| Arithmetic::strict_next_multiple_of(l, r)), + sym::pow => Some(|_, l, r| Arithmetic::strict_pow(l, r)), sym::checked_pow => Some(|_, l, r| Arithmetic::strict_pow(l, r)), sym::saturating_pow => Some(|_, l, r| Arithmetic::saturating_pow(l, r)), sym::strict_pow => Some(|_, l, r| Arithmetic::strict_pow(l, r)), sym::wrapping_pow => Some(|_, l, r| Arithmetic::wrapping_pow(l, r)), + sym::ilog | sym::checked_ilog => Some(|_, l, r| Arithmetic::ilog(l, r)), + sym::min => Some(|_, l, r| Arithmetic::min(l, r)), sym::max => Some(|_, l, r| Arithmetic::max(l, r)), @@ -402,7 +416,7 @@ impl<'c, 'cx> IntervalCtxt<'c, 'cx> { }, } } - fn to_int_type(&self, ty: Ty<'_>) -> Option { + pub fn to_int_type(&self, ty: Ty<'_>) -> Option { match ty.kind() { TyKind::Int(IntTy::Isize) => Some(self.isize_ty), TyKind::Int(IntTy::I8) => Some(IntType::I8), diff --git a/clippy_utils/src/sym.rs b/clippy_utils/src/sym.rs index 5090d9c9bdcf..a5293092e8d3 100644 --- a/clippy_utils/src/sym.rs +++ b/clippy_utils/src/sym.rs @@ -74,6 +74,7 @@ generate! { Visitor, Weak, abs, + abs_diff, ambiguous_glob_reexports, append, arg, @@ -105,9 +106,14 @@ generate! { checked_add, checked_div, checked_div_euclid, + checked_ilog, + checked_ilog10, + checked_ilog2, checked_isqrt, checked_mul, checked_neg, + checked_next_multiple_of, + checked_next_power_of_two, checked_pow, checked_rem, checked_rem_euclid, @@ -201,6 +207,9 @@ generate! { is_ok, is_some, is_some_and, + ilog, + ilog10, + ilog2, isqrt, itertools, join, @@ -244,6 +253,8 @@ generate! { next_back, next_if, next_if_eq, + next_multiple_of, + next_power_of_two, next_tuple, nth, ok, @@ -402,6 +413,7 @@ generate! { wrapping_abs, wrapping_div_euclid, wrapping_neg, + wrapping_next_power_of_two, wrapping_offset, wrapping_pow, wrapping_shl, From bf98325e88b5f4d1b04f511d17c04447db69c694 Mon Sep 17 00:00:00 2001 From: RunDevelopment Date: Mon, 4 Aug 2025 14:50:54 +0200 Subject: [PATCH 09/13] Use range analysis in `cast_possible_truncation` --- .../src/casts/cast_possible_truncation.rs | 119 +++++------------ tests/ui/cast.rs | 25 ++-- tests/ui/cast.stderr | 120 +++--------------- tests/ui/cast_size.64bit.stderr | 18 +-- tests/ui/cast_size.rs | 3 +- 5 files changed, 74 insertions(+), 211 deletions(-) diff --git a/clippy_lints/src/casts/cast_possible_truncation.rs b/clippy_lints/src/casts/cast_possible_truncation.rs index 5a9c97cfe9af..a82af1e63e51 100644 --- a/clippy_lints/src/casts/cast_possible_truncation.rs +++ b/clippy_lints/src/casts/cast_possible_truncation.rs @@ -1,88 +1,18 @@ -use clippy_utils::consts::{ConstEvalCtxt, Constant}; use clippy_utils::diagnostics::{span_lint, span_lint_and_then}; use clippy_utils::source::snippet; use clippy_utils::sugg::Sugg; use clippy_utils::ty::{get_discriminant_value, is_isize_or_usize}; -use clippy_utils::{expr_or_init, is_in_const_context, rinterval, sym}; +use clippy_utils::{is_in_const_context, rinterval}; use rustc_abi::IntegerType; use rustc_errors::{Applicability, Diag}; use rustc_hir::def::{DefKind, Res}; -use rustc_hir::{BinOpKind, Expr, ExprKind}; +use rustc_hir::{Expr, ExprKind}; use rustc_lint::LateContext; use rustc_middle::ty::{self, FloatTy, Ty}; use rustc_span::Span; use super::{CAST_ENUM_TRUNCATION, CAST_POSSIBLE_TRUNCATION, utils}; -fn constant_int(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option { - if let Some(Constant::Int(c)) = ConstEvalCtxt::new(cx).eval(expr) { - Some(c) - } else { - None - } -} - -fn get_constant_bits(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option { - constant_int(cx, expr).map(|c| u64::from(128 - c.leading_zeros())) -} - -fn apply_reductions(cx: &LateContext<'_>, nbits: u64, expr: &Expr<'_>, signed: bool) -> u64 { - match expr_or_init(cx, expr).kind { - ExprKind::Cast(inner, _) => apply_reductions(cx, nbits, inner, signed), - ExprKind::Block(block, _) => block.expr.map_or(nbits, |e| apply_reductions(cx, nbits, e, signed)), - ExprKind::Binary(op, left, right) => match op.node { - BinOpKind::Div => { - apply_reductions(cx, nbits, left, signed).saturating_sub(if signed { - // let's be conservative here - 0 - } else { - // by dividing by 1, we remove 0 bits, etc. - get_constant_bits(cx, right).map_or(0, |b| b.saturating_sub(1)) - }) - }, - BinOpKind::Rem => get_constant_bits(cx, right) - .unwrap_or(u64::MAX) - .min(apply_reductions(cx, nbits, left, signed)), - BinOpKind::BitAnd => get_constant_bits(cx, right) - .unwrap_or(u64::MAX) - .min(get_constant_bits(cx, left).unwrap_or(u64::MAX)) - .min(apply_reductions(cx, nbits, right, signed)) - .min(apply_reductions(cx, nbits, left, signed)), - BinOpKind::Shr => apply_reductions(cx, nbits, left, signed) - .saturating_sub(constant_int(cx, right).map_or(0, |s| u64::try_from(s).unwrap_or_default())), - _ => nbits, - }, - ExprKind::MethodCall(method, left, [right], _) => { - if signed { - return nbits; - } - let max_bits = if method.ident.name == sym::min { - get_constant_bits(cx, right) - } else { - None - }; - apply_reductions(cx, nbits, left, signed).min(max_bits.unwrap_or(u64::MAX)) - }, - ExprKind::MethodCall(method, _, [lo, hi], _) => { - if method.ident.name == sym::clamp - //FIXME: make this a diagnostic item - && let (Some(lo_bits), Some(hi_bits)) = (get_constant_bits(cx, lo), get_constant_bits(cx, hi)) - { - return lo_bits.max(hi_bits); - } - nbits - }, - ExprKind::MethodCall(method, _value, [], _) => { - if method.ident.name == sym::signum { - 0 // do not lint if cast comes from a `signum` function - } else { - nbits - } - }, - _ => nbits, - } -} - pub(super) fn check<'cx>( cx: &LateContext<'cx>, expr: &Expr<'_>, @@ -91,16 +21,40 @@ pub(super) fn check<'cx>( cast_to: Ty<'_>, cast_to_span: Span, ) { + let mut from_interval = None; + + let from_is_size = is_isize_or_usize(cast_from); + let to_is_size = is_isize_or_usize(cast_to); + let msg = match (cast_from.kind(), utils::int_ty_to_nbits(cx.tcx, cast_to)) { (ty::Int(_) | ty::Uint(_), Some(to_nbits)) => { - let from_nbits = apply_reductions( - cx, - utils::int_ty_to_nbits(cx.tcx, cast_from).unwrap(), - cast_expr, - cast_from.is_signed(), - ); - - let (should_lint, suffix) = match (is_isize_or_usize(cast_from), is_isize_or_usize(cast_to)) { + let interval_ctx = rinterval::IntervalCtxt::new(cx); + from_interval = interval_ctx.eval(cast_expr); + + let to_ty = if !from_is_size && to_is_size { + // if we cast from a fixed-size integer to a pointer-sized integer, + // we want assume the worst case of usize being 32-bit + if cast_to.is_signed() { + rinterval::IntType::I32 + } else { + rinterval::IntType::U32 + } + } else { + interval_ctx + .to_int_type(cast_to) + .expect("the to cast type should be an integral type") + }; + + if let Some(from_interval) = &from_interval + && from_interval.fits_into(to_ty) + { + // No truncation possible. + return; + } + + let from_nbits = utils::int_ty_to_nbits(cx.tcx, cast_from).unwrap(); + + let (should_lint, suffix) = match (from_is_size, to_is_size) { (true, true) | (false, false) => (to_nbits < from_nbits, ""), (true, false) => ( to_nbits <= 32, @@ -133,7 +87,7 @@ pub(super) fn check<'cx>( }; let cast_from_ptr_size = def.repr().int.is_none_or(|ty| matches!(ty, IntegerType::Pointer(_),)); - let suffix = match (cast_from_ptr_size, is_isize_or_usize(cast_to)) { + let suffix = match (cast_from_ptr_size, to_is_size) { (_, false) if from_nbits > to_nbits => "", (false, true) if from_nbits > 64 => "", (false, true) if from_nbits > 32 => " on targets with 32-bit wide pointers", @@ -166,9 +120,6 @@ pub(super) fn check<'cx>( _ => return, }; - let interval_ctx = rinterval::IntervalCtxt::new(cx); - let from_interval = interval_ctx.eval(cast_expr); - span_lint_and_then(cx, CAST_POSSIBLE_TRUNCATION, expr.span, msg, |diag| { if let Some(from_interval) = from_interval { diag.note(utils::format_cast_operand(from_interval)); diff --git a/tests/ui/cast.rs b/tests/ui/cast.rs index eaf41e19d546..a31cccae294a 100644 --- a/tests/ui/cast.rs +++ b/tests/ui/cast.rs @@ -105,7 +105,7 @@ fn main() { //~^ cast_possible_wrap // should not wrap, usize is never 8 bits - 1usize as i8; + get_value::() as i8; //~^ cast_possible_truncation // wraps on 16 bit ptr size @@ -550,22 +550,22 @@ fn issue12506() -> usize { } fn issue12721() { - fn x() -> u64 { - u64::MAX - } + // Don't lint. + (get_value::() & 0xff) as u8; + ((get_value::() & 0xff00) >> 8) as u8; // Don't lint. - (255 & 999999u64) as u8; + (255 & get_value::()) as u8; // Don't lint. - let _ = ((x() & 255) & 999999) as u8; + let _ = ((get_value::() & 255) & 999999) as u8; // Don't lint. - let _ = (999999 & (x() & 255)) as u8; + let _ = (999999 & (get_value::() & 255)) as u8; - (256 & 999999u64) as u8; + (256 & get_value::()) as u8; //~^ cast_possible_truncation - (255 % 999999u64) as u8; - //~^ cast_possible_truncation + // Don't lint. + (get_value::() as u64 % get_value::()) as u8; } fn issue7486(number: u64, other: u32) -> u32 { @@ -576,7 +576,6 @@ fn issue7486(number: u64, other: u32) -> u32 { // which implies that result < u32::max_value() let result = number % other_u64; result as u32 - //~^ cast_possible_truncation } fn dxgi_xr10_to_unorm8(x: u16) -> u8 { @@ -588,7 +587,6 @@ fn dxgi_xr10_to_unorm8(x: u16) -> u8 { let x = x.clamp(0, 510) as u16; // this is round(x / 510 * 255), but faster ((x + 1) >> 1) as u8 - //~^ cast_possible_truncation } fn dxgi_xr10_to_unorm16(x: u16) -> u16 { debug_assert!(x <= 1023); @@ -602,7 +600,6 @@ fn dxgi_xr10_to_unorm16(x: u16) -> u16 { } fn unorm16_to_unorm8(x: u16) -> u8 { ((x as u32 * 255 + 32895) >> 16) as u8 - //~^ cast_possible_truncation } fn f32_to_f16u(value: f32) -> u16 { @@ -623,7 +620,6 @@ fn f32_to_f16u(value: f32) -> u16 { // Set mantissa MSB for NaN (and also keep shifted mantissa bits) let nan_bit = if man == 0 { 0 } else { 0x0200u32 }; return ((sign >> 16) | 0x7C00u32 | nan_bit | (man >> 13)) as u16; - //~^ cast_possible_truncation } // The number is normalized, start assembling half precision version @@ -635,7 +631,6 @@ fn f32_to_f16u(value: f32) -> u16 { // Check for exponent overflow, return +infinity if half_exp >= 0x1F { return (half_sign | 0x7C00u32) as u16; - //~^ cast_possible_truncation } // Check for underflow diff --git a/tests/ui/cast.stderr b/tests/ui/cast.stderr index ab93449ca6d8..2f47376b752a 100644 --- a/tests/ui/cast.stderr +++ b/tests/ui/cast.stderr @@ -244,18 +244,18 @@ LL | get_value::() as isize; | = note: the cast operand may contain values in the range `0..=18446744073709551615` -error: casting `usize` to `i8` may truncate the value +error: casting `isize` to `i8` may truncate the value --> tests/ui/cast.rs:108:5 | -LL | 1usize as i8; - | ^^^^^^^^^^^^ +LL | get_value::() as i8; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operand is `1` + = note: the cast operand may contain values in the range `-9223372036854775808..=9223372036854775807` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | -LL - 1usize as i8; -LL + i8::try_from(1usize); +LL - get_value::() as i8; +LL + i8::try_from(get_value::()); | error: casting `usize` to `i16` may truncate the value @@ -795,119 +795,35 @@ LL | bar.unwrap().unwrap() as usize error: casting `u64` to `u8` may truncate the value --> tests/ui/cast.rs:564:5 | -LL | (256 & 999999u64) as u8; - | ^^^^^^^^^^^^^^^^^^^^^^^ - | - = note: the cast operand is `0` - = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... -help: ... or use `try_from` and handle the error accordingly - | -LL - (256 & 999999u64) as u8; -LL + u8::try_from(256 & 999999u64); - | - -error: casting `u64` to `u8` may truncate the value - --> tests/ui/cast.rs:567:5 - | -LL | (255 % 999999u64) as u8; - | ^^^^^^^^^^^^^^^^^^^^^^^ - | - = note: the cast operand is `255` - = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... -help: ... or use `try_from` and handle the error accordingly - | -LL - (255 % 999999u64) as u8; -LL + u8::try_from(255 % 999999u64); - | - -error: casting `u64` to `u32` may truncate the value - --> tests/ui/cast.rs:578:5 - | -LL | result as u32 - | ^^^^^^^^^^^^^ +LL | (256 & get_value::()) as u8; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operand may contain values in the range `0..=4294967294` + = note: the cast operand may contain values in the range `0..=256` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | -LL - result as u32 -LL + u32::try_from(result) +LL - (256 & get_value::()) as u8; +LL + u8::try_from(256 & get_value::()); | error: casting `u16` to `i16` may wrap around the value - --> tests/ui/cast.rs:585:13 + --> tests/ui/cast.rs:584:13 | LL | let x = x as i16 - 0x180; | ^^^^^^^^ | = note: the cast operand may contain values in the range `0..=65535` -error: casting `u16` to `u8` may truncate the value - --> tests/ui/cast.rs:590:5 - | -LL | ((x + 1) >> 1) as u8 - | ^^^^^^^^^^^^^^^^^^^^ - | - = note: the cast operand may contain values in the range `0..=255` - = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... -help: ... or use `try_from` and handle the error accordingly - | -LL - ((x + 1) >> 1) as u8 -LL + u8::try_from((x + 1) >> 1) - | - error: casting `u16` to `i16` may wrap around the value - --> tests/ui/cast.rs:596:13 + --> tests/ui/cast.rs:594:13 | LL | let x = x as i16 - 0x180; | ^^^^^^^^ | = note: the cast operand may contain values in the range `0..=65535` -error: casting `u32` to `u8` may truncate the value - --> tests/ui/cast.rs:604:5 - | -LL | ((x as u32 * 255 + 32895) >> 16) as u8 - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = note: the cast operand may contain values in the range `0..=255` - = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... -help: ... or use `try_from` and handle the error accordingly - | -LL - ((x as u32 * 255 + 32895) >> 16) as u8 -LL + u8::try_from((x as u32 * 255 + 32895) >> 16) - | - -error: casting `u32` to `u16` may truncate the value - --> tests/ui/cast.rs:625:16 - | -LL | return ((sign >> 16) | 0x7C00u32 | nan_bit | (man >> 13)) as u16; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = note: the cast operand may contain values in the range `0..=65535` - = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... -help: ... or use `try_from` and handle the error accordingly - | -LL - return ((sign >> 16) | 0x7C00u32 | nan_bit | (man >> 13)) as u16; -LL + return u16::try_from((sign >> 16) | 0x7C00u32 | nan_bit | (man >> 13)); - | - -error: casting `u32` to `u16` may truncate the value - --> tests/ui/cast.rs:637:16 - | -LL | return (half_sign | 0x7C00u32) as u16; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = note: the cast operand may contain values in the range `31744..=65535` - = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... -help: ... or use `try_from` and handle the error accordingly - | -LL - return (half_sign | 0x7C00u32) as u16; -LL + return u16::try_from(half_sign | 0x7C00u32); - | - error: casting `u32` to `u16` may truncate the value - --> tests/ui/cast.rs:657:16 + --> tests/ui/cast.rs:652:16 | LL | return (half_sign | half_man) as u16; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -921,7 +837,7 @@ LL + return u16::try_from(half_sign | half_man); | error: casting `i32` to `u32` may lose the sign of the value - --> tests/ui/cast.rs:662:20 + --> tests/ui/cast.rs:657:20 | LL | let half_exp = (half_exp as u32) << 10; | ^^^^^^^^^^^^^^^^^ @@ -929,7 +845,7 @@ LL | let half_exp = (half_exp as u32) << 10; = note: the cast operand may contain values in the range `-112..=143` error: casting `u32` to `u16` may truncate the value - --> tests/ui/cast.rs:669:9 + --> tests/ui/cast.rs:664:9 | LL | ((half_sign | half_exp | half_man) + 1) as u16 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -943,7 +859,7 @@ LL + u16::try_from((half_sign | half_exp | half_man) + 1) | error: casting `u32` to `u16` may truncate the value - --> tests/ui/cast.rs:672:9 + --> tests/ui/cast.rs:667:9 | LL | (half_sign | half_exp | half_man) as u16 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -956,5 +872,5 @@ LL - (half_sign | half_exp | half_man) as u16 LL + u16::try_from(half_sign | half_exp | half_man) | -error: aborting due to 98 previous errors +error: aborting due to 92 previous errors diff --git a/tests/ui/cast_size.64bit.stderr b/tests/ui/cast_size.64bit.stderr index 93acf3b7c25e..cec10e7f9922 100644 --- a/tests/ui/cast_size.64bit.stderr +++ b/tests/ui/cast_size.64bit.stderr @@ -1,17 +1,17 @@ error: casting `isize` to `i8` may truncate the value --> tests/ui/cast_size.rs:21:5 | -LL | 1isize as i8; - | ^^^^^^^^^^^^ +LL | get_value::() as i8; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operand is `1` + = note: the cast operand may contain values in the range `-9223372036854775808..=9223372036854775807` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... = note: `-D clippy::cast-possible-truncation` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::cast_possible_truncation)]` help: ... or use `try_from` and handle the error accordingly | -LL - 1isize as i8; -LL + i8::try_from(1isize); +LL - get_value::() as i8; +LL + i8::try_from(get_value::()); | error: casting `isize` to `f32` causes a loss of precision (`isize` is 32 or 64 bits wide, but `f32`'s mantissa is only 23 bits wide) @@ -218,12 +218,12 @@ LL | 9_999_999_999_999_999usize as f64; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: casting `usize` to `u16` may truncate the value - --> tests/ui/cast_size.rs:78:20 + --> tests/ui/cast_size.rs:79:20 | -LL | const N: u16 = M as u16; - | ^^^^^^^^ +LL | const O: u16 = (M * 1000) as u16; + | ^^^^^^^^^^^^^^^^^ | - = note: the cast operand is `100` + = note: the cast operand is `100000` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... error: aborting due to 22 previous errors diff --git a/tests/ui/cast_size.rs b/tests/ui/cast_size.rs index 9ecedfbe5fac..0c7222a5d0df 100644 --- a/tests/ui/cast_size.rs +++ b/tests/ui/cast_size.rs @@ -18,7 +18,7 @@ fn get_value() -> T { fn main() { // Casting from *size - 1isize as i8; + get_value::() as i8; //~^ cast_possible_truncation let x0: isize = get_value(); let x1: usize = get_value(); @@ -76,5 +76,6 @@ fn main() { fn issue15163() { const M: usize = 100; const N: u16 = M as u16; + const O: u16 = (M * 1000) as u16; //~^ cast_possible_truncation } From 9174eb9799fd377f54f7b4a34502b713030a287b Mon Sep 17 00:00:00 2001 From: RunDevelopment Date: Mon, 4 Aug 2025 16:06:26 +0200 Subject: [PATCH 10/13] Cache evaluated expressions --- .../src/casts/cast_possible_truncation.rs | 7 ++- clippy_lints/src/casts/cast_possible_wrap.rs | 4 +- clippy_lints/src/casts/cast_sign_loss.rs | 4 +- clippy_lints/src/casts/mod.rs | 10 +++-- clippy_utils/src/rinterval/iinterval.rs | 2 +- clippy_utils/src/rinterval/mod.rs | 45 ++++++++++++++----- 6 files changed, 49 insertions(+), 23 deletions(-) diff --git a/clippy_lints/src/casts/cast_possible_truncation.rs b/clippy_lints/src/casts/cast_possible_truncation.rs index a82af1e63e51..75e03a3938f3 100644 --- a/clippy_lints/src/casts/cast_possible_truncation.rs +++ b/clippy_lints/src/casts/cast_possible_truncation.rs @@ -15,6 +15,7 @@ use super::{CAST_ENUM_TRUNCATION, CAST_POSSIBLE_TRUNCATION, utils}; pub(super) fn check<'cx>( cx: &LateContext<'cx>, + i_cx: &mut rinterval::IntervalCtxt<'_, 'cx>, expr: &Expr<'_>, cast_expr: &Expr<'cx>, cast_from: Ty<'_>, @@ -28,8 +29,7 @@ pub(super) fn check<'cx>( let msg = match (cast_from.kind(), utils::int_ty_to_nbits(cx.tcx, cast_to)) { (ty::Int(_) | ty::Uint(_), Some(to_nbits)) => { - let interval_ctx = rinterval::IntervalCtxt::new(cx); - from_interval = interval_ctx.eval(cast_expr); + from_interval = i_cx.eval(cast_expr); let to_ty = if !from_is_size && to_is_size { // if we cast from a fixed-size integer to a pointer-sized integer, @@ -40,8 +40,7 @@ pub(super) fn check<'cx>( rinterval::IntType::U32 } } else { - interval_ctx - .to_int_type(cast_to) + i_cx.to_int_type(cast_to) .expect("the to cast type should be an integral type") }; diff --git a/clippy_lints/src/casts/cast_possible_wrap.rs b/clippy_lints/src/casts/cast_possible_wrap.rs index 19875bd00e06..503716f8fc9f 100644 --- a/clippy_lints/src/casts/cast_possible_wrap.rs +++ b/clippy_lints/src/casts/cast_possible_wrap.rs @@ -19,6 +19,7 @@ enum EmitState { pub(super) fn check<'cx>( cx: &LateContext<'cx>, + i_cx: &mut rinterval::IntervalCtxt<'_, 'cx>, expr: &Expr<'_>, cast_op: &Expr<'cx>, cast_from: Ty<'_>, @@ -86,8 +87,7 @@ pub(super) fn check<'cx>( ), }; - let interval_ctx = rinterval::IntervalCtxt::new(cx); - let from_interval = interval_ctx.eval(cast_op); + let from_interval = i_cx.eval(cast_op); if let Some(from_interval) = &from_interval && from_interval.fits_into(from_interval.ty.to_signed()) diff --git a/clippy_lints/src/casts/cast_sign_loss.rs b/clippy_lints/src/casts/cast_sign_loss.rs index 776e302a5880..ffb37d070fa7 100644 --- a/clippy_lints/src/casts/cast_sign_loss.rs +++ b/clippy_lints/src/casts/cast_sign_loss.rs @@ -8,6 +8,7 @@ use super::{CAST_SIGN_LOSS, utils}; pub(super) fn check<'cx>( cx: &LateContext<'cx>, + i_cx: &mut rinterval::IntervalCtxt<'_, 'cx>, expr: &Expr<'cx>, cast_op: &Expr<'cx>, cast_from: Ty<'cx>, @@ -33,8 +34,7 @@ pub(super) fn check<'cx>( // reported if the signed integer expression can actually contain negative // values. if cast_from.is_integral() && cast_from.is_signed() { - let interval_ctx = rinterval::IntervalCtxt::new(cx); - if let Some(from_interval) = interval_ctx.eval(cast_op) + if let Some(from_interval) = i_cx.eval(cast_op) && from_interval.ty.is_signed() && from_interval.contains_negative() { diff --git a/clippy_lints/src/casts/mod.rs b/clippy_lints/src/casts/mod.rs index 540a7dcbeeae..7241a7851403 100644 --- a/clippy_lints/src/casts/mod.rs +++ b/clippy_lints/src/casts/mod.rs @@ -27,8 +27,8 @@ mod utils; mod zero_ptr; use clippy_config::Conf; -use clippy_utils::is_hir_ty_cfg_dependant; use clippy_utils::msrvs::{self, Msrv}; +use clippy_utils::{is_hir_ty_cfg_dependant, rinterval}; use rustc_hir::{Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_session::impl_lint_pass; @@ -885,11 +885,13 @@ impl<'tcx> LateLintPass<'tcx> for Casts { } if cast_to.is_numeric() { - cast_possible_truncation::check(cx, expr, cast_from_expr, cast_from, cast_to, cast_to_hir.span); + let i_cx = &mut rinterval::IntervalCtxt::new(cx); + + cast_possible_truncation::check(cx, i_cx, expr, cast_from_expr, cast_from, cast_to, cast_to_hir.span); if cast_from.is_numeric() { - cast_possible_wrap::check(cx, expr, cast_from_expr, cast_from, cast_to); + cast_possible_wrap::check(cx, i_cx, expr, cast_from_expr, cast_from, cast_to); cast_precision_loss::check(cx, expr, cast_from, cast_to); - cast_sign_loss::check(cx, expr, cast_from_expr, cast_from, cast_to); + cast_sign_loss::check(cx, i_cx, expr, cast_from_expr, cast_from, cast_to); cast_abs_to_unsigned::check(cx, expr, cast_from_expr, cast_from, cast_to, self.msrv); cast_nan_to_int::check(cx, expr, cast_from_expr, cast_from, cast_to); } diff --git a/clippy_utils/src/rinterval/iinterval.rs b/clippy_utils/src/rinterval/iinterval.rs index 0e5a9ecada26..34c530ad6bed 100644 --- a/clippy_utils/src/rinterval/iinterval.rs +++ b/clippy_utils/src/rinterval/iinterval.rs @@ -110,7 +110,7 @@ pub(crate) enum IntTypeInfo { /// possible to accurately represent the set of all possible values of an /// integer expression using only its minimum and maximum values. /// -/// As such, this type represents a **subset** of the actual set of values of +/// As such, this type represents a **superset** of the actual set of values of /// an expression. #[derive(Clone, Debug, Eq, PartialEq)] diff --git a/clippy_utils/src/rinterval/mod.rs b/clippy_utils/src/rinterval/mod.rs index 47f0cc6d373b..74cf4ecf1920 100644 --- a/clippy_utils/src/rinterval/mod.rs +++ b/clippy_utils/src/rinterval/mod.rs @@ -12,8 +12,9 @@ pub use arithmetic::*; pub use iinterval::*; use rustc_ast::LitKind; +use rustc_data_structures::fx::FxHashMap; use rustc_hir::def::Res; -use rustc_hir::{BinOpKind, Expr, ExprKind, PathSegment, UnOp}; +use rustc_hir::{BinOpKind, Expr, ExprKind, HirId, PathSegment, UnOp}; use rustc_lint::LateContext; use rustc_middle::ty::{IntTy, Ty, TyKind, TypeckResults, UintTy}; use rustc_span::Symbol; @@ -29,6 +30,8 @@ pub struct IntervalCtxt<'c, 'cx> { arth: Arithmetic, isize_ty: IntType, + + cache: FxHashMap>, } impl<'c, 'cx> IntervalCtxt<'c, 'cx> { @@ -49,6 +52,8 @@ impl<'c, 'cx> IntervalCtxt<'c, 'cx> { arth: Arithmetic { checked: false }, isize_ty, + + cache: FxHashMap::default(), } } @@ -56,7 +61,7 @@ impl<'c, 'cx> IntervalCtxt<'c, 'cx> { /// /// If the given expression is not of a supported integer type, None is /// returned. - pub fn eval(&self, expr: &Expr<'cx>) -> Option { + pub fn eval(&mut self, expr: &Expr<'cx>) -> Option { let ty = self.to_int_type(self.typeck.expr_ty(expr))?; let expr = self.cx.expr_or_init(expr.peel_borrows()); @@ -73,7 +78,21 @@ impl<'c, 'cx> IntervalCtxt<'c, 'cx> { /// /// If anything goes wrong or an expression cannot be evaluated, None is /// returned. - fn eval_ty(&self, expr: &Expr<'cx>, ty: IntType) -> Option { + fn eval_ty(&mut self, expr: &Expr<'cx>, ty: IntType) -> Option { + // Check the cache first. + if let Some(interval) = self.cache.get(&expr.hir_id) { + return interval.clone(); + } + + // Evaluate the expression. + let interval = self.eval_ty_uncached(expr, ty); + + // Cache the result. + self.cache.insert(expr.hir_id, interval.clone()); + + interval + } + fn eval_ty_uncached(&mut self, expr: &Expr<'cx>, ty: IntType) -> Option { match expr.kind { ExprKind::Lit(lit) => { return self.literal(&lit.node, ty); @@ -125,7 +144,7 @@ impl<'c, 'cx> IntervalCtxt<'c, 'cx> { _ => None, } } - fn binary_op(&self, op: BinOpKind, l_expr: &Expr<'cx>, r_expr: &Expr<'cx>) -> Option { + fn binary_op(&mut self, op: BinOpKind, l_expr: &Expr<'cx>, r_expr: &Expr<'cx>) -> Option { let lhs = &self.eval(l_expr)?; // The pattern `x * x` is quite common and will always result in a @@ -161,7 +180,7 @@ impl<'c, 'cx> IntervalCtxt<'c, 'cx> { _ => None, } } - fn unary_op(&self, op: UnOp, value: &IInterval) -> Option { + fn unary_op(&mut self, op: UnOp, value: &IInterval) -> Option { match op { UnOp::Neg => self.arth.neg(value).ok(), UnOp::Not => Arithmetic::not(value).ok(), @@ -177,7 +196,7 @@ impl<'c, 'cx> IntervalCtxt<'c, 'cx> { /// Calls to methods that returns an integer. fn method_call( - &self, + &mut self, path: &PathSegment<'_>, self_arg: &Expr<'cx>, args: &[Expr<'cx>], @@ -225,7 +244,8 @@ impl<'c, 'cx> IntervalCtxt<'c, 'cx> { }; if let Some(f) = f { - return f(&self.arth, &self.eval(self_arg)?).ok(); + let self_arg = self.eval(self_arg)?; + return f(&self.arth, &self_arg).ok(); } }, @@ -309,7 +329,9 @@ impl<'c, 'cx> IntervalCtxt<'c, 'cx> { }; if let Some(f) = f { - return f(&self.arth, &self.eval(self_arg)?, &self.eval(arg1)?).ok(); + let self_arg = self.eval(self_arg)?; + let arg1 = self.eval(arg1)?; + return f(&self.arth, &self_arg, &arg1).ok(); } }, @@ -322,7 +344,10 @@ impl<'c, 'cx> IntervalCtxt<'c, 'cx> { }; if let Some(f) = f { - return f(&self.arth, &self.eval(self_arg)?, &self.eval(arg1)?, &self.eval(arg2)?).ok(); + let self_arg = self.eval(self_arg)?; + let arg1 = self.eval(arg1)?; + let arg2 = self.eval(arg2)?; + return f(&self.arth, &self_arg, &arg1, &arg2).ok(); } }, _ => {}, @@ -384,7 +409,7 @@ impl<'c, 'cx> IntervalCtxt<'c, 'cx> { None } - fn is_same_variable(&self, expr: &Expr<'cx>, other: &Expr<'cx>) -> bool { + fn is_same_variable(&self, expr: &Expr<'_>, other: &Expr<'_>) -> bool { // Check if the two expressions are the same variable if let ExprKind::Path(ref path) = expr.kind { if let ExprKind::Path(ref other_path) = other.kind { From 42b1800c3a38352d8f84f5156a0f72535c16bb5a Mon Sep 17 00:00:00 2001 From: RunDevelopment Date: Mon, 4 Aug 2025 16:51:53 +0200 Subject: [PATCH 11/13] Improve formatting for numeric ranges --- clippy_lints/src/casts/utils.rs | 67 +++++++++++++++ tests/ui/cast.rs | 9 ++ tests/ui/cast.stderr | 144 +++++++++++++++++++++----------- tests/ui/cast_size.64bit.stderr | 30 +++---- 4 files changed, 185 insertions(+), 65 deletions(-) diff --git a/clippy_lints/src/casts/utils.rs b/clippy_lints/src/casts/utils.rs index 6e595b16bd18..052e7f0fac35 100644 --- a/clippy_lints/src/casts/utils.rs +++ b/clippy_lints/src/casts/utils.rs @@ -75,6 +75,8 @@ pub(super) fn format_cast_operand(range: IInterval) -> String { if min == max { format!("the cast operand is `{min}`") } else { + let min = PrettyNumber::from(min); + let max = PrettyNumber::from(max); format!("the cast operand may contain values in the range `{min}..={max}`") } } else { @@ -83,7 +85,72 @@ pub(super) fn format_cast_operand(range: IInterval) -> String { if min == max { format!("the cast operand is `{min}`") } else { + let min = PrettyNumber::from(min); + let max = PrettyNumber::from(max); format!("the cast operand may contain values in the range `{min}..={max}`") } } } +enum PrettyNumber { + Unsigned(u128), + Signed(i128), +} +impl PrettyNumber { + fn abs(&self) -> u128 { + match self { + PrettyNumber::Unsigned(value) => *value, + PrettyNumber::Signed(value) => value.unsigned_abs(), + } + } + fn is_negative(&self) -> bool { + match self { + PrettyNumber::Unsigned(_) => false, + PrettyNumber::Signed(value) => value.is_negative(), + } + } +} +impl From for PrettyNumber { + fn from(value: u128) -> Self { + PrettyNumber::Unsigned(value) + } +} +impl From for PrettyNumber { + fn from(value: i128) -> Self { + PrettyNumber::Signed(value) + } +} +impl std::fmt::Display for PrettyNumber { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let abs = self.abs(); + if abs > 4096 + 100 { + // This is the closest power of 2 minus 1. + // The minus 1 is necessary, because we can't represent 2^128. + let mut closest_power_of_two_m1 = abs.checked_next_power_of_two().unwrap_or(0).wrapping_sub(1); + if closest_power_of_two_m1.abs_diff(abs) > 100 { + closest_power_of_two_m1 /= 2; + } + if closest_power_of_two_m1.abs_diff(abs) < 100 { + let mut diff = abs.wrapping_sub(closest_power_of_two_m1.wrapping_add(1)).cast_signed() as i32; + if self.is_negative() { + write!(f, "-")?; + diff = -diff; + } + + let power = closest_power_of_two_m1.count_ones(); + write!(f, "2^{power}")?; + + if diff < 0 { + write!(f, "{diff}")?; + } else if diff > 0 { + write!(f, "+{diff}")?; + } + return Ok(()); + } + } + + match self { + PrettyNumber::Unsigned(value) => write!(f, "{value}"), + PrettyNumber::Signed(value) => write!(f, "{value}"), + } + } +} diff --git a/tests/ui/cast.rs b/tests/ui/cast.rs index a31cccae294a..6982a462cc7d 100644 --- a/tests/ui/cast.rs +++ b/tests/ui/cast.rs @@ -668,3 +668,12 @@ fn f32_to_f16u(value: f32) -> u16 { //~^ cast_possible_truncation } } + +fn test_range_formatting(value: i32) { + (get_value::() as i32 * 64 + 4) as u16; + //~^ cast_possible_truncation + //~| cast_sign_loss + (get_value::() as i32 * 64 - 4) as u16; + //~^ cast_possible_truncation + //~| cast_sign_loss +} diff --git a/tests/ui/cast.stderr b/tests/ui/cast.stderr index 2f47376b752a..7bc55d5a61bb 100644 --- a/tests/ui/cast.stderr +++ b/tests/ui/cast.stderr @@ -78,7 +78,7 @@ error: casting `i32` to `i8` may truncate the value LL | get_value::() as i8; | ^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operand may contain values in the range `-2147483648..=2147483647` + = note: the cast operand may contain values in the range `-2^31..=2^31-1` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | @@ -92,7 +92,7 @@ error: casting `i32` to `u8` may truncate the value LL | get_value::() as u8; | ^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operand may contain values in the range `-2147483648..=2147483647` + = note: the cast operand may contain values in the range `-2^31..=2^31-1` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | @@ -106,7 +106,7 @@ error: casting `i32` to `u8` may lose the sign of the value LL | get_value::() as u8; | ^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operand may contain values in the range `-2147483648..=2147483647` + = note: the cast operand may contain values in the range `-2^31..=2^31-1` error: casting `f64` to `isize` may truncate the value --> tests/ui/cast.rs:65:5 @@ -136,7 +136,7 @@ error: casting `u32` to `u16` may truncate the value LL | 1f32 as u32 as u16; | ^^^^^^^^^^^^^^^^^^ | - = note: the cast operand may contain values in the range `0..=4294967295` + = note: the cast operand may contain values in the range `0..=2^32-1` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | @@ -164,7 +164,7 @@ error: casting `i32` to `i8` may truncate the value LL | let _x: i8 = get_value::() as _; | ^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operand may contain values in the range `-2147483648..=2147483647` + = note: the cast operand may contain values in the range `-2^31..=2^31-1` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | @@ -218,7 +218,7 @@ error: casting `u16` to `i16` may wrap around the value LL | get_value::() as i16; | ^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operand may contain values in the range `0..=65535` + = note: the cast operand may contain values in the range `0..=2^16-1` error: casting `u32` to `i32` may wrap around the value --> tests/ui/cast.rs:98:5 @@ -226,7 +226,7 @@ error: casting `u32` to `i32` may wrap around the value LL | get_value::() as i32; | ^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operand may contain values in the range `0..=4294967295` + = note: the cast operand may contain values in the range `0..=2^32-1` error: casting `u64` to `i64` may wrap around the value --> tests/ui/cast.rs:101:5 @@ -234,7 +234,7 @@ error: casting `u64` to `i64` may wrap around the value LL | get_value::() as i64; | ^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operand may contain values in the range `0..=18446744073709551615` + = note: the cast operand may contain values in the range `0..=2^64-1` error: casting `usize` to `isize` may wrap around the value --> tests/ui/cast.rs:104:5 @@ -242,7 +242,7 @@ error: casting `usize` to `isize` may wrap around the value LL | get_value::() as isize; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operand may contain values in the range `0..=18446744073709551615` + = note: the cast operand may contain values in the range `0..=2^64-1` error: casting `isize` to `i8` may truncate the value --> tests/ui/cast.rs:108:5 @@ -250,7 +250,7 @@ error: casting `isize` to `i8` may truncate the value LL | get_value::() as i8; | ^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operand may contain values in the range `-9223372036854775808..=9223372036854775807` + = note: the cast operand may contain values in the range `-2^63..=2^63-1` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | @@ -264,7 +264,7 @@ error: casting `usize` to `i16` may truncate the value LL | get_value::() as i16; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operand may contain values in the range `0..=18446744073709551615` + = note: the cast operand may contain values in the range `0..=2^64-1` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | @@ -278,7 +278,7 @@ error: casting `usize` to `i16` may wrap around the value on targets with 16-bit LL | get_value::() as i16; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operand may contain values in the range `0..=18446744073709551615` + = note: the cast operand may contain values in the range `0..=2^64-1` = note: `usize` and `isize` may be as small as 16 bits on some platforms = note: for more information see https://doc.rust-lang.org/reference/types/numeric.html#machine-dependent-integer-types @@ -288,7 +288,7 @@ error: casting `usize` to `i32` may truncate the value on targets with 64-bit wi LL | get_value::() as i32; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operand may contain values in the range `0..=18446744073709551615` + = note: the cast operand may contain values in the range `0..=2^64-1` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | @@ -302,7 +302,7 @@ error: casting `usize` to `i32` may wrap around the value on targets with 32-bit LL | get_value::() as i32; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operand may contain values in the range `0..=18446744073709551615` + = note: the cast operand may contain values in the range `0..=2^64-1` error: casting `usize` to `i64` may wrap around the value on targets with 64-bit wide pointers --> tests/ui/cast.rs:122:5 @@ -310,7 +310,7 @@ error: casting `usize` to `i64` may wrap around the value on targets with 64-bit LL | get_value::() as i64; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operand may contain values in the range `0..=18446744073709551615` + = note: the cast operand may contain values in the range `0..=2^64-1` error: casting `u16` to `isize` may wrap around the value on targets with 16-bit wide pointers --> tests/ui/cast.rs:128:5 @@ -318,7 +318,7 @@ error: casting `u16` to `isize` may wrap around the value on targets with 16-bit LL | get_value::() as isize; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operand may contain values in the range `0..=65535` + = note: the cast operand may contain values in the range `0..=2^16-1` = note: `usize` and `isize` may be as small as 16 bits on some platforms = note: for more information see https://doc.rust-lang.org/reference/types/numeric.html#machine-dependent-integer-types @@ -328,7 +328,7 @@ error: casting `u32` to `isize` may wrap around the value on targets with 32-bit LL | get_value::() as isize; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operand may contain values in the range `0..=4294967295` + = note: the cast operand may contain values in the range `0..=2^32-1` error: casting `u64` to `isize` may truncate the value on targets with 32-bit wide pointers --> tests/ui/cast.rs:136:5 @@ -336,7 +336,7 @@ error: casting `u64` to `isize` may truncate the value on targets with 32-bit wi LL | get_value::() as isize; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operand may contain values in the range `0..=18446744073709551615` + = note: the cast operand may contain values in the range `0..=2^64-1` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | @@ -350,7 +350,7 @@ error: casting `u64` to `isize` may wrap around the value on targets with 64-bit LL | get_value::() as isize; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operand may contain values in the range `0..=18446744073709551615` + = note: the cast operand may contain values in the range `0..=2^64-1` error: casting `i32` to `u32` may lose the sign of the value --> tests/ui/cast.rs:142:5 @@ -490,7 +490,7 @@ error: casting `u32` to `u8` may truncate the value LL | let c = (q >> 16) as u8; | ^^^^^^^^^^^^^^^ | - = note: the cast operand may contain values in the range `0..=65535` + = note: the cast operand may contain values in the range `0..=2^16-1` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | @@ -518,7 +518,7 @@ error: casting `i32` to `u32` may lose the sign of the value LL | let _a = |x: i32| -> u32 { (x * x * x * x) as u32 }; | ^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operand may contain values in the range `-2147483648..=2147483647` + = note: the cast operand may contain values in the range `-2^31..=2^31-1` error: casting `i32` to `u32` may lose the sign of the value --> tests/ui/cast.rs:447:5 @@ -558,7 +558,7 @@ error: casting `i32` to `u32` may lose the sign of the value LL | (x * x) as u32; | ^^^^^^^^^^^^^^ | - = note: the cast operand may contain values in the range `-2147483648..=2147483647` + = note: the cast operand may contain values in the range `-2^31..=2^31-1` error: casting `i32` to `u32` may lose the sign of the value --> tests/ui/cast.rs:465:5 @@ -566,7 +566,7 @@ error: casting `i32` to `u32` may lose the sign of the value LL | (x * x * x) as u32; | ^^^^^^^^^^^^^^^^^^ | - = note: the cast operand may contain values in the range `-2147483648..=2147483647` + = note: the cast operand may contain values in the range `-2^31..=2^31-1` error: casting `i16` to `u16` may lose the sign of the value --> tests/ui/cast.rs:469:5 @@ -574,7 +574,7 @@ error: casting `i16` to `u16` may lose the sign of the value LL | (y * y * y * y * -2) as u16; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operand may contain values in the range `-32768..=32767` + = note: the cast operand may contain values in the range `-2^15..=2^15-1` error: casting `i16` to `u16` may lose the sign of the value --> tests/ui/cast.rs:472:5 @@ -582,7 +582,7 @@ error: casting `i16` to `u16` may lose the sign of the value LL | (y * y * y / y * 2) as u16; | ^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operand may contain values in the range `-32768..=32767` + = note: the cast operand may contain values in the range `-2^15..=2^15-1` error: casting `i16` to `u16` may lose the sign of the value --> tests/ui/cast.rs:474:5 @@ -590,7 +590,7 @@ error: casting `i16` to `u16` may lose the sign of the value LL | (y * y / y * 2) as u16; | ^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operand may contain values in the range `-32768..=32767` + = note: the cast operand may contain values in the range `-2^15..=2^15-1` error: casting `i16` to `u16` may lose the sign of the value --> tests/ui/cast.rs:477:5 @@ -598,7 +598,7 @@ error: casting `i16` to `u16` may lose the sign of the value LL | (y / y * y * -2) as u16; | ^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operand may contain values in the range `-32768..=32767` + = note: the cast operand may contain values in the range `-2^15..=2^15-1` error: equal expressions as operands to `/` --> tests/ui/cast.rs:477:6 @@ -614,7 +614,7 @@ error: casting `i16` to `u16` may lose the sign of the value LL | (y + y + y + -2) as u16; | ^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operand may contain values in the range `-32768..=32767` + = note: the cast operand may contain values in the range `-2^15..=2^15-1` error: casting `i16` to `u16` may lose the sign of the value --> tests/ui/cast.rs:484:5 @@ -622,7 +622,7 @@ error: casting `i16` to `u16` may lose the sign of the value LL | (y + y + y + 2) as u16; | ^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operand may contain values in the range `-32768..=32767` + = note: the cast operand may contain values in the range `-2^15..=2^15-1` error: casting `i16` to `u16` may lose the sign of the value --> tests/ui/cast.rs:488:5 @@ -630,7 +630,7 @@ error: casting `i16` to `u16` may lose the sign of the value LL | (z + -2) as u16; | ^^^^^^^^^^^^^^^ | - = note: the cast operand may contain values in the range `-32768..=32767` + = note: the cast operand may contain values in the range `-2^15..=2^15-1` error: casting `i16` to `u16` may lose the sign of the value --> tests/ui/cast.rs:491:5 @@ -638,7 +638,7 @@ error: casting `i16` to `u16` may lose the sign of the value LL | (z + z + 2) as u16; | ^^^^^^^^^^^^^^^^^^ | - = note: the cast operand may contain values in the range `-32768..=32767` + = note: the cast operand may contain values in the range `-2^15..=2^15-1` error: casting `i32` to `u32` may lose the sign of the value --> tests/ui/cast.rs:495:9 @@ -646,7 +646,7 @@ error: casting `i32` to `u32` may lose the sign of the value LL | (a * a * b * b * c * c) as u32; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operand may contain values in the range `-2147483648..=2147483647` + = note: the cast operand may contain values in the range `-2^31..=2^31-1` error: casting `i32` to `u32` may lose the sign of the value --> tests/ui/cast.rs:497:9 @@ -654,7 +654,7 @@ error: casting `i32` to `u32` may lose the sign of the value LL | (a * b * c) as u32; | ^^^^^^^^^^^^^^^^^^ | - = note: the cast operand may contain values in the range `-2147483648..=2147483647` + = note: the cast operand may contain values in the range `-2^31..=2^31-1` error: casting `i32` to `u32` may lose the sign of the value --> tests/ui/cast.rs:500:9 @@ -662,7 +662,7 @@ error: casting `i32` to `u32` may lose the sign of the value LL | (a * -b * c) as u32; | ^^^^^^^^^^^^^^^^^^^ | - = note: the cast operand may contain values in the range `-2147483648..=2147483647` + = note: the cast operand may contain values in the range `-2^31..=2^31-1` error: casting `i32` to `u32` may lose the sign of the value --> tests/ui/cast.rs:503:9 @@ -670,7 +670,7 @@ error: casting `i32` to `u32` may lose the sign of the value LL | (a * b * c * c) as u32; | ^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operand may contain values in the range `-2147483648..=2147483647` + = note: the cast operand may contain values in the range `-2^31..=2^31-1` error: casting `i32` to `u32` may lose the sign of the value --> tests/ui/cast.rs:505:9 @@ -678,7 +678,7 @@ error: casting `i32` to `u32` may lose the sign of the value LL | (a * -2) as u32; | ^^^^^^^^^^^^^^^ | - = note: the cast operand may contain values in the range `-2147483648..=2147483647` + = note: the cast operand may contain values in the range `-2^31..=2^31-1` error: casting `i32` to `u32` may lose the sign of the value --> tests/ui/cast.rs:508:9 @@ -686,7 +686,7 @@ error: casting `i32` to `u32` may lose the sign of the value LL | (a * b * c * -2) as u32; | ^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operand may contain values in the range `-2147483648..=2147483647` + = note: the cast operand may contain values in the range `-2^31..=2^31-1` error: casting `i32` to `u32` may lose the sign of the value --> tests/ui/cast.rs:511:9 @@ -694,7 +694,7 @@ error: casting `i32` to `u32` may lose the sign of the value LL | (a / b) as u32; | ^^^^^^^^^^^^^^ | - = note: the cast operand may contain values in the range `-2147483648..=2147483647` + = note: the cast operand may contain values in the range `-2^31..=2^31-1` error: casting `i32` to `u32` may lose the sign of the value --> tests/ui/cast.rs:513:9 @@ -702,7 +702,7 @@ error: casting `i32` to `u32` may lose the sign of the value LL | (a / b * c) as u32; | ^^^^^^^^^^^^^^^^^^ | - = note: the cast operand may contain values in the range `-2147483648..=2147483647` + = note: the cast operand may contain values in the range `-2^31..=2^31-1` error: casting `i32` to `u32` may lose the sign of the value --> tests/ui/cast.rs:516:9 @@ -710,7 +710,7 @@ error: casting `i32` to `u32` may lose the sign of the value LL | (a / b + b * c) as u32; | ^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operand may contain values in the range `-2147483648..=2147483647` + = note: the cast operand may contain values in the range `-2^31..=2^31-1` error: casting `i32` to `u32` may lose the sign of the value --> tests/ui/cast.rs:519:9 @@ -718,7 +718,7 @@ error: casting `i32` to `u32` may lose the sign of the value LL | a.saturating_pow(3) as u32; | ^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operand may contain values in the range `-2147483648..=2147483647` + = note: the cast operand may contain values in the range `-2^31..=2^31-1` error: casting `i32` to `u32` may lose the sign of the value --> tests/ui/cast.rs:522:9 @@ -726,7 +726,7 @@ error: casting `i32` to `u32` may lose the sign of the value LL | (a.abs() * b.pow(2) / c.abs()) as u32 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operand may contain values in the range `-2147483648..=2147483647` + = note: the cast operand may contain values in the range `-2^31..=2^31-1` error: casting `i32` to `u32` may lose the sign of the value --> tests/ui/cast.rs:530:21 @@ -776,7 +776,7 @@ error: casting `i64` to `usize` may truncate the value on targets with 32-bit wi LL | bar.unwrap().unwrap() as usize | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operand may contain values in the range `-9223372036854775808..=9223372036854775807` + = note: the cast operand may contain values in the range `-2^63..=2^63-1` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | @@ -790,7 +790,7 @@ error: casting `i64` to `usize` may lose the sign of the value LL | bar.unwrap().unwrap() as usize | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operand may contain values in the range `-9223372036854775808..=9223372036854775807` + = note: the cast operand may contain values in the range `-2^63..=2^63-1` error: casting `u64` to `u8` may truncate the value --> tests/ui/cast.rs:564:5 @@ -812,7 +812,7 @@ error: casting `u16` to `i16` may wrap around the value LL | let x = x as i16 - 0x180; | ^^^^^^^^ | - = note: the cast operand may contain values in the range `0..=65535` + = note: the cast operand may contain values in the range `0..=2^16-1` error: casting `u16` to `i16` may wrap around the value --> tests/ui/cast.rs:594:13 @@ -820,7 +820,7 @@ error: casting `u16` to `i16` may wrap around the value LL | let x = x as i16 - 0x180; | ^^^^^^^^ | - = note: the cast operand may contain values in the range `0..=65535` + = note: the cast operand may contain values in the range `0..=2^16-1` error: casting `u32` to `u16` may truncate the value --> tests/ui/cast.rs:652:16 @@ -828,7 +828,7 @@ error: casting `u32` to `u16` may truncate the value LL | return (half_sign | half_man) as u16; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operand may contain values in the range `0..=4294967295` + = note: the cast operand may contain values in the range `0..=2^32-1` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | @@ -850,7 +850,7 @@ error: casting `u32` to `u16` may truncate the value LL | ((half_sign | half_exp | half_man) + 1) as u16 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operand may contain values in the range `0..=4294967295` + = note: the cast operand may contain values in the range `0..=2^32-1` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | @@ -864,7 +864,7 @@ error: casting `u32` to `u16` may truncate the value LL | (half_sign | half_exp | half_man) as u16 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operand may contain values in the range `0..=4294967295` + = note: the cast operand may contain values in the range `0..=2^32-1` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | @@ -872,5 +872,49 @@ LL - (half_sign | half_exp | half_man) as u16 LL + u16::try_from(half_sign | half_exp | half_man) | -error: aborting due to 92 previous errors +error: casting `i32` to `u16` may truncate the value + --> tests/ui/cast.rs:673:5 + | +LL | (get_value::() as i32 * 64 + 4) as u16; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: the cast operand may contain values in the range `-2^13+4..=2^13-60` + = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... +help: ... or use `try_from` and handle the error accordingly + | +LL - (get_value::() as i32 * 64 + 4) as u16; +LL + u16::try_from(get_value::() as i32 * 64 + 4); + | + +error: casting `i32` to `u16` may lose the sign of the value + --> tests/ui/cast.rs:673:5 + | +LL | (get_value::() as i32 * 64 + 4) as u16; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: the cast operand may contain values in the range `-2^13+4..=2^13-60` + +error: casting `i32` to `u16` may truncate the value + --> tests/ui/cast.rs:676:5 + | +LL | (get_value::() as i32 * 64 - 4) as u16; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: the cast operand may contain values in the range `-2^13-4..=2^13-68` + = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... +help: ... or use `try_from` and handle the error accordingly + | +LL - (get_value::() as i32 * 64 - 4) as u16; +LL + u16::try_from(get_value::() as i32 * 64 - 4); + | + +error: casting `i32` to `u16` may lose the sign of the value + --> tests/ui/cast.rs:676:5 + | +LL | (get_value::() as i32 * 64 - 4) as u16; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: the cast operand may contain values in the range `-2^13-4..=2^13-68` + +error: aborting due to 96 previous errors diff --git a/tests/ui/cast_size.64bit.stderr b/tests/ui/cast_size.64bit.stderr index cec10e7f9922..7509b1a67872 100644 --- a/tests/ui/cast_size.64bit.stderr +++ b/tests/ui/cast_size.64bit.stderr @@ -4,7 +4,7 @@ error: casting `isize` to `i8` may truncate the value LL | get_value::() as i8; | ^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operand may contain values in the range `-9223372036854775808..=9223372036854775807` + = note: the cast operand may contain values in the range `-2^63..=2^63-1` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... = note: `-D clippy::cast-possible-truncation` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::cast_possible_truncation)]` @@ -47,7 +47,7 @@ error: casting `isize` to `i32` may truncate the value on targets with 64-bit wi LL | get_value::() as i32; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operand may contain values in the range `-9223372036854775808..=9223372036854775807` + = note: the cast operand may contain values in the range `-2^63..=2^63-1` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | @@ -61,7 +61,7 @@ error: casting `isize` to `u32` may truncate the value on targets with 64-bit wi LL | get_value::() as u32; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operand may contain values in the range `-9223372036854775808..=9223372036854775807` + = note: the cast operand may contain values in the range `-2^63..=2^63-1` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | @@ -75,7 +75,7 @@ error: casting `isize` to `u32` may lose the sign of the value LL | get_value::() as u32; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operand may contain values in the range `-9223372036854775808..=9223372036854775807` + = note: the cast operand may contain values in the range `-2^63..=2^63-1` = note: `-D clippy::cast-sign-loss` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::cast_sign_loss)]` @@ -85,7 +85,7 @@ error: casting `usize` to `u32` may truncate the value on targets with 64-bit wi LL | get_value::() as u32; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operand may contain values in the range `0..=18446744073709551615` + = note: the cast operand may contain values in the range `0..=2^64-1` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | @@ -99,7 +99,7 @@ error: casting `usize` to `i32` may truncate the value on targets with 64-bit wi LL | get_value::() as i32; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operand may contain values in the range `0..=18446744073709551615` + = note: the cast operand may contain values in the range `0..=2^64-1` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | @@ -113,7 +113,7 @@ error: casting `usize` to `i32` may wrap around the value on targets with 32-bit LL | get_value::() as i32; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operand may contain values in the range `0..=18446744073709551615` + = note: the cast operand may contain values in the range `0..=2^64-1` = note: `-D clippy::cast-possible-wrap` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::cast_possible_wrap)]` @@ -123,7 +123,7 @@ error: casting `i64` to `isize` may truncate the value on targets with 32-bit wi LL | get_value::() as isize; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operand may contain values in the range `-9223372036854775808..=9223372036854775807` + = note: the cast operand may contain values in the range `-2^63..=2^63-1` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | @@ -137,7 +137,7 @@ error: casting `i64` to `usize` may truncate the value on targets with 32-bit wi LL | get_value::() as usize; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operand may contain values in the range `-9223372036854775808..=9223372036854775807` + = note: the cast operand may contain values in the range `-2^63..=2^63-1` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | @@ -151,7 +151,7 @@ error: casting `i64` to `usize` may lose the sign of the value LL | get_value::() as usize; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operand may contain values in the range `-9223372036854775808..=9223372036854775807` + = note: the cast operand may contain values in the range `-2^63..=2^63-1` error: casting `u64` to `isize` may truncate the value on targets with 32-bit wide pointers --> tests/ui/cast_size.rs:54:5 @@ -159,7 +159,7 @@ error: casting `u64` to `isize` may truncate the value on targets with 32-bit wi LL | get_value::() as isize; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operand may contain values in the range `0..=18446744073709551615` + = note: the cast operand may contain values in the range `0..=2^64-1` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | @@ -173,7 +173,7 @@ error: casting `u64` to `isize` may wrap around the value on targets with 64-bit LL | get_value::() as isize; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operand may contain values in the range `0..=18446744073709551615` + = note: the cast operand may contain values in the range `0..=2^64-1` error: casting `u64` to `usize` may truncate the value on targets with 32-bit wide pointers --> tests/ui/cast_size.rs:57:5 @@ -181,7 +181,7 @@ error: casting `u64` to `usize` may truncate the value on targets with 32-bit wi LL | get_value::() as usize; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operand may contain values in the range `0..=18446744073709551615` + = note: the cast operand may contain values in the range `0..=2^64-1` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | @@ -195,7 +195,7 @@ error: casting `u32` to `isize` may wrap around the value on targets with 32-bit LL | get_value::() as isize; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operand may contain values in the range `0..=4294967295` + = note: the cast operand may contain values in the range `0..=2^32-1` error: casting `i32` to `usize` may lose the sign of the value --> tests/ui/cast_size.rs:63:5 @@ -203,7 +203,7 @@ error: casting `i32` to `usize` may lose the sign of the value LL | get_value::() as usize; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operand may contain values in the range `-2147483648..=2147483647` + = note: the cast operand may contain values in the range `-2^31..=2^31-1` error: casting `i32` to `f32` causes a loss of precision (`i32` is 32 bits wide, but `f32`'s mantissa is only 23 bits wide) --> tests/ui/cast_size.rs:68:5 From 1fb924edae1749a90a69e8345633cfc00b51cdeb Mon Sep 17 00:00:00 2001 From: RunDevelopment Date: Mon, 4 Aug 2025 16:57:12 +0200 Subject: [PATCH 12/13] Remove notes that don't add information --- .../src/casts/cast_possible_truncation.rs | 4 +- clippy_lints/src/casts/cast_possible_wrap.rs | 4 +- clippy_lints/src/casts/cast_sign_loss.rs | 11 ++- tests/ui/cast.rs | 10 ++ tests/ui/cast.stderr | 91 ++----------------- tests/ui/cast_size.64bit.stderr | 19 ---- 6 files changed, 30 insertions(+), 109 deletions(-) diff --git a/clippy_lints/src/casts/cast_possible_truncation.rs b/clippy_lints/src/casts/cast_possible_truncation.rs index 75e03a3938f3..9242d18ab044 100644 --- a/clippy_lints/src/casts/cast_possible_truncation.rs +++ b/clippy_lints/src/casts/cast_possible_truncation.rs @@ -120,7 +120,9 @@ pub(super) fn check<'cx>( }; span_lint_and_then(cx, CAST_POSSIBLE_TRUNCATION, expr.span, msg, |diag| { - if let Some(from_interval) = from_interval { + if let Some(from_interval) = from_interval + && !from_interval.is_full() + { diag.note(utils::format_cast_operand(from_interval)); } diff --git a/clippy_lints/src/casts/cast_possible_wrap.rs b/clippy_lints/src/casts/cast_possible_wrap.rs index 503716f8fc9f..d4a88d7c920a 100644 --- a/clippy_lints/src/casts/cast_possible_wrap.rs +++ b/clippy_lints/src/casts/cast_possible_wrap.rs @@ -97,7 +97,9 @@ pub(super) fn check<'cx>( } span_lint_and_then(cx, CAST_POSSIBLE_WRAP, expr.span, message, |diag| { - if let Some(from_interval) = from_interval { + if let Some(from_interval) = from_interval + && !from_interval.is_full() + { diag.note(utils::format_cast_operand(from_interval)); } diff --git a/clippy_lints/src/casts/cast_sign_loss.rs b/clippy_lints/src/casts/cast_sign_loss.rs index ffb37d070fa7..64bca356c737 100644 --- a/clippy_lints/src/casts/cast_sign_loss.rs +++ b/clippy_lints/src/casts/cast_sign_loss.rs @@ -1,4 +1,4 @@ -use clippy_utils::diagnostics::{span_lint, span_lint_and_note}; +use clippy_utils::diagnostics::{span_lint, span_lint_and_then}; use clippy_utils::rinterval; use rustc_hir::Expr; use rustc_lint::LateContext; @@ -38,13 +38,16 @@ pub(super) fn check<'cx>( && from_interval.ty.is_signed() && from_interval.contains_negative() { - span_lint_and_note( + span_lint_and_then( cx, CAST_SIGN_LOSS, expr.span, format!("casting `{cast_from}` to `{cast_to}` may lose the sign of the value"), - None, - utils::format_cast_operand(from_interval), + |diag| { + if !from_interval.is_full() { + diag.note(utils::format_cast_operand(from_interval)); + } + }, ); } } diff --git a/tests/ui/cast.rs b/tests/ui/cast.rs index 6982a462cc7d..be43cb40088c 100644 --- a/tests/ui/cast.rs +++ b/tests/ui/cast.rs @@ -677,3 +677,13 @@ fn test_range_formatting(value: i32) { //~^ cast_possible_truncation //~| cast_sign_loss } + +fn test_narrowing(value: i32) { + if value > 0 { + // value as u32; + (value - 1) as u32; + //~^ cast_sign_loss + } else { + panic!("value must be positive"); + } +} diff --git a/tests/ui/cast.stderr b/tests/ui/cast.stderr index 7bc55d5a61bb..f6442f798d8e 100644 --- a/tests/ui/cast.stderr +++ b/tests/ui/cast.stderr @@ -78,7 +78,6 @@ error: casting `i32` to `i8` may truncate the value LL | get_value::() as i8; | ^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operand may contain values in the range `-2^31..=2^31-1` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | @@ -92,7 +91,6 @@ error: casting `i32` to `u8` may truncate the value LL | get_value::() as u8; | ^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operand may contain values in the range `-2^31..=2^31-1` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | @@ -105,8 +103,6 @@ error: casting `i32` to `u8` may lose the sign of the value | LL | get_value::() as u8; | ^^^^^^^^^^^^^^^^^^^^^^^^ - | - = note: the cast operand may contain values in the range `-2^31..=2^31-1` error: casting `f64` to `isize` may truncate the value --> tests/ui/cast.rs:65:5 @@ -136,7 +132,6 @@ error: casting `u32` to `u16` may truncate the value LL | 1f32 as u32 as u16; | ^^^^^^^^^^^^^^^^^^ | - = note: the cast operand may contain values in the range `0..=2^32-1` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | @@ -164,7 +159,6 @@ error: casting `i32` to `i8` may truncate the value LL | let _x: i8 = get_value::() as _; | ^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operand may contain values in the range `-2^31..=2^31-1` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | @@ -208,7 +202,6 @@ error: casting `u8` to `i8` may wrap around the value LL | get_value::() as i8; | ^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operand may contain values in the range `0..=255` = note: `-D clippy::cast-possible-wrap` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::cast_possible_wrap)]` @@ -217,32 +210,24 @@ error: casting `u16` to `i16` may wrap around the value | LL | get_value::() as i16; | ^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = note: the cast operand may contain values in the range `0..=2^16-1` error: casting `u32` to `i32` may wrap around the value --> tests/ui/cast.rs:98:5 | LL | get_value::() as i32; | ^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = note: the cast operand may contain values in the range `0..=2^32-1` error: casting `u64` to `i64` may wrap around the value --> tests/ui/cast.rs:101:5 | LL | get_value::() as i64; | ^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = note: the cast operand may contain values in the range `0..=2^64-1` error: casting `usize` to `isize` may wrap around the value --> tests/ui/cast.rs:104:5 | LL | get_value::() as isize; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = note: the cast operand may contain values in the range `0..=2^64-1` error: casting `isize` to `i8` may truncate the value --> tests/ui/cast.rs:108:5 @@ -250,7 +235,6 @@ error: casting `isize` to `i8` may truncate the value LL | get_value::() as i8; | ^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operand may contain values in the range `-2^63..=2^63-1` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | @@ -264,7 +248,6 @@ error: casting `usize` to `i16` may truncate the value LL | get_value::() as i16; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operand may contain values in the range `0..=2^64-1` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | @@ -278,7 +261,6 @@ error: casting `usize` to `i16` may wrap around the value on targets with 16-bit LL | get_value::() as i16; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operand may contain values in the range `0..=2^64-1` = note: `usize` and `isize` may be as small as 16 bits on some platforms = note: for more information see https://doc.rust-lang.org/reference/types/numeric.html#machine-dependent-integer-types @@ -288,7 +270,6 @@ error: casting `usize` to `i32` may truncate the value on targets with 64-bit wi LL | get_value::() as i32; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operand may contain values in the range `0..=2^64-1` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | @@ -301,16 +282,12 @@ error: casting `usize` to `i32` may wrap around the value on targets with 32-bit | LL | get_value::() as i32; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = note: the cast operand may contain values in the range `0..=2^64-1` error: casting `usize` to `i64` may wrap around the value on targets with 64-bit wide pointers --> tests/ui/cast.rs:122:5 | LL | get_value::() as i64; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = note: the cast operand may contain values in the range `0..=2^64-1` error: casting `u16` to `isize` may wrap around the value on targets with 16-bit wide pointers --> tests/ui/cast.rs:128:5 @@ -318,7 +295,6 @@ error: casting `u16` to `isize` may wrap around the value on targets with 16-bit LL | get_value::() as isize; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operand may contain values in the range `0..=2^16-1` = note: `usize` and `isize` may be as small as 16 bits on some platforms = note: for more information see https://doc.rust-lang.org/reference/types/numeric.html#machine-dependent-integer-types @@ -327,8 +303,6 @@ error: casting `u32` to `isize` may wrap around the value on targets with 32-bit | LL | get_value::() as isize; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = note: the cast operand may contain values in the range `0..=2^32-1` error: casting `u64` to `isize` may truncate the value on targets with 32-bit wide pointers --> tests/ui/cast.rs:136:5 @@ -336,7 +310,6 @@ error: casting `u64` to `isize` may truncate the value on targets with 32-bit wi LL | get_value::() as isize; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operand may contain values in the range `0..=2^64-1` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | @@ -349,8 +322,6 @@ error: casting `u64` to `isize` may wrap around the value on targets with 64-bit | LL | get_value::() as isize; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = note: the cast operand may contain values in the range `0..=2^64-1` error: casting `i32` to `u32` may lose the sign of the value --> tests/ui/cast.rs:142:5 @@ -517,8 +488,6 @@ error: casting `i32` to `u32` may lose the sign of the value | LL | let _a = |x: i32| -> u32 { (x * x * x * x) as u32 }; | ^^^^^^^^^^^^^^^^^^^^^^ - | - = note: the cast operand may contain values in the range `-2^31..=2^31-1` error: casting `i32` to `u32` may lose the sign of the value --> tests/ui/cast.rs:447:5 @@ -557,48 +526,36 @@ error: casting `i32` to `u32` may lose the sign of the value | LL | (x * x) as u32; | ^^^^^^^^^^^^^^ - | - = note: the cast operand may contain values in the range `-2^31..=2^31-1` error: casting `i32` to `u32` may lose the sign of the value --> tests/ui/cast.rs:465:5 | LL | (x * x * x) as u32; | ^^^^^^^^^^^^^^^^^^ - | - = note: the cast operand may contain values in the range `-2^31..=2^31-1` error: casting `i16` to `u16` may lose the sign of the value --> tests/ui/cast.rs:469:5 | LL | (y * y * y * y * -2) as u16; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = note: the cast operand may contain values in the range `-2^15..=2^15-1` error: casting `i16` to `u16` may lose the sign of the value --> tests/ui/cast.rs:472:5 | LL | (y * y * y / y * 2) as u16; | ^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = note: the cast operand may contain values in the range `-2^15..=2^15-1` error: casting `i16` to `u16` may lose the sign of the value --> tests/ui/cast.rs:474:5 | LL | (y * y / y * 2) as u16; | ^^^^^^^^^^^^^^^^^^^^^^ - | - = note: the cast operand may contain values in the range `-2^15..=2^15-1` error: casting `i16` to `u16` may lose the sign of the value --> tests/ui/cast.rs:477:5 | LL | (y / y * y * -2) as u16; | ^^^^^^^^^^^^^^^^^^^^^^^ - | - = note: the cast operand may contain values in the range `-2^15..=2^15-1` error: equal expressions as operands to `/` --> tests/ui/cast.rs:477:6 @@ -613,120 +570,90 @@ error: casting `i16` to `u16` may lose the sign of the value | LL | (y + y + y + -2) as u16; | ^^^^^^^^^^^^^^^^^^^^^^^ - | - = note: the cast operand may contain values in the range `-2^15..=2^15-1` error: casting `i16` to `u16` may lose the sign of the value --> tests/ui/cast.rs:484:5 | LL | (y + y + y + 2) as u16; | ^^^^^^^^^^^^^^^^^^^^^^ - | - = note: the cast operand may contain values in the range `-2^15..=2^15-1` error: casting `i16` to `u16` may lose the sign of the value --> tests/ui/cast.rs:488:5 | LL | (z + -2) as u16; | ^^^^^^^^^^^^^^^ - | - = note: the cast operand may contain values in the range `-2^15..=2^15-1` error: casting `i16` to `u16` may lose the sign of the value --> tests/ui/cast.rs:491:5 | LL | (z + z + 2) as u16; | ^^^^^^^^^^^^^^^^^^ - | - = note: the cast operand may contain values in the range `-2^15..=2^15-1` error: casting `i32` to `u32` may lose the sign of the value --> tests/ui/cast.rs:495:9 | LL | (a * a * b * b * c * c) as u32; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = note: the cast operand may contain values in the range `-2^31..=2^31-1` error: casting `i32` to `u32` may lose the sign of the value --> tests/ui/cast.rs:497:9 | LL | (a * b * c) as u32; | ^^^^^^^^^^^^^^^^^^ - | - = note: the cast operand may contain values in the range `-2^31..=2^31-1` error: casting `i32` to `u32` may lose the sign of the value --> tests/ui/cast.rs:500:9 | LL | (a * -b * c) as u32; | ^^^^^^^^^^^^^^^^^^^ - | - = note: the cast operand may contain values in the range `-2^31..=2^31-1` error: casting `i32` to `u32` may lose the sign of the value --> tests/ui/cast.rs:503:9 | LL | (a * b * c * c) as u32; | ^^^^^^^^^^^^^^^^^^^^^^ - | - = note: the cast operand may contain values in the range `-2^31..=2^31-1` error: casting `i32` to `u32` may lose the sign of the value --> tests/ui/cast.rs:505:9 | LL | (a * -2) as u32; | ^^^^^^^^^^^^^^^ - | - = note: the cast operand may contain values in the range `-2^31..=2^31-1` error: casting `i32` to `u32` may lose the sign of the value --> tests/ui/cast.rs:508:9 | LL | (a * b * c * -2) as u32; | ^^^^^^^^^^^^^^^^^^^^^^^ - | - = note: the cast operand may contain values in the range `-2^31..=2^31-1` error: casting `i32` to `u32` may lose the sign of the value --> tests/ui/cast.rs:511:9 | LL | (a / b) as u32; | ^^^^^^^^^^^^^^ - | - = note: the cast operand may contain values in the range `-2^31..=2^31-1` error: casting `i32` to `u32` may lose the sign of the value --> tests/ui/cast.rs:513:9 | LL | (a / b * c) as u32; | ^^^^^^^^^^^^^^^^^^ - | - = note: the cast operand may contain values in the range `-2^31..=2^31-1` error: casting `i32` to `u32` may lose the sign of the value --> tests/ui/cast.rs:516:9 | LL | (a / b + b * c) as u32; | ^^^^^^^^^^^^^^^^^^^^^^ - | - = note: the cast operand may contain values in the range `-2^31..=2^31-1` error: casting `i32` to `u32` may lose the sign of the value --> tests/ui/cast.rs:519:9 | LL | a.saturating_pow(3) as u32; | ^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = note: the cast operand may contain values in the range `-2^31..=2^31-1` error: casting `i32` to `u32` may lose the sign of the value --> tests/ui/cast.rs:522:9 | LL | (a.abs() * b.pow(2) / c.abs()) as u32 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = note: the cast operand may contain values in the range `-2^31..=2^31-1` error: casting `i32` to `u32` may lose the sign of the value --> tests/ui/cast.rs:530:21 @@ -776,7 +703,6 @@ error: casting `i64` to `usize` may truncate the value on targets with 32-bit wi LL | bar.unwrap().unwrap() as usize | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operand may contain values in the range `-2^63..=2^63-1` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | @@ -789,8 +715,6 @@ error: casting `i64` to `usize` may lose the sign of the value | LL | bar.unwrap().unwrap() as usize | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = note: the cast operand may contain values in the range `-2^63..=2^63-1` error: casting `u64` to `u8` may truncate the value --> tests/ui/cast.rs:564:5 @@ -811,16 +735,12 @@ error: casting `u16` to `i16` may wrap around the value | LL | let x = x as i16 - 0x180; | ^^^^^^^^ - | - = note: the cast operand may contain values in the range `0..=2^16-1` error: casting `u16` to `i16` may wrap around the value --> tests/ui/cast.rs:594:13 | LL | let x = x as i16 - 0x180; | ^^^^^^^^ - | - = note: the cast operand may contain values in the range `0..=2^16-1` error: casting `u32` to `u16` may truncate the value --> tests/ui/cast.rs:652:16 @@ -828,7 +748,6 @@ error: casting `u32` to `u16` may truncate the value LL | return (half_sign | half_man) as u16; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operand may contain values in the range `0..=2^32-1` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | @@ -850,7 +769,6 @@ error: casting `u32` to `u16` may truncate the value LL | ((half_sign | half_exp | half_man) + 1) as u16 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operand may contain values in the range `0..=2^32-1` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | @@ -864,7 +782,6 @@ error: casting `u32` to `u16` may truncate the value LL | (half_sign | half_exp | half_man) as u16 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operand may contain values in the range `0..=2^32-1` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | @@ -916,5 +833,11 @@ LL | (get_value::() as i32 * 64 - 4) as u16; | = note: the cast operand may contain values in the range `-2^13-4..=2^13-68` -error: aborting due to 96 previous errors +error: casting `i32` to `u32` may lose the sign of the value + --> tests/ui/cast.rs:684:9 + | +LL | (value - 1) as u32; + | ^^^^^^^^^^^^^^^^^^ + +error: aborting due to 97 previous errors diff --git a/tests/ui/cast_size.64bit.stderr b/tests/ui/cast_size.64bit.stderr index 7509b1a67872..abbb86e6a311 100644 --- a/tests/ui/cast_size.64bit.stderr +++ b/tests/ui/cast_size.64bit.stderr @@ -4,7 +4,6 @@ error: casting `isize` to `i8` may truncate the value LL | get_value::() as i8; | ^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operand may contain values in the range `-2^63..=2^63-1` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... = note: `-D clippy::cast-possible-truncation` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::cast_possible_truncation)]` @@ -47,7 +46,6 @@ error: casting `isize` to `i32` may truncate the value on targets with 64-bit wi LL | get_value::() as i32; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operand may contain values in the range `-2^63..=2^63-1` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | @@ -61,7 +59,6 @@ error: casting `isize` to `u32` may truncate the value on targets with 64-bit wi LL | get_value::() as u32; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operand may contain values in the range `-2^63..=2^63-1` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | @@ -75,7 +72,6 @@ error: casting `isize` to `u32` may lose the sign of the value LL | get_value::() as u32; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operand may contain values in the range `-2^63..=2^63-1` = note: `-D clippy::cast-sign-loss` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::cast_sign_loss)]` @@ -85,7 +81,6 @@ error: casting `usize` to `u32` may truncate the value on targets with 64-bit wi LL | get_value::() as u32; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operand may contain values in the range `0..=2^64-1` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | @@ -99,7 +94,6 @@ error: casting `usize` to `i32` may truncate the value on targets with 64-bit wi LL | get_value::() as i32; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operand may contain values in the range `0..=2^64-1` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | @@ -113,7 +107,6 @@ error: casting `usize` to `i32` may wrap around the value on targets with 32-bit LL | get_value::() as i32; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operand may contain values in the range `0..=2^64-1` = note: `-D clippy::cast-possible-wrap` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::cast_possible_wrap)]` @@ -123,7 +116,6 @@ error: casting `i64` to `isize` may truncate the value on targets with 32-bit wi LL | get_value::() as isize; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operand may contain values in the range `-2^63..=2^63-1` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | @@ -137,7 +129,6 @@ error: casting `i64` to `usize` may truncate the value on targets with 32-bit wi LL | get_value::() as usize; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operand may contain values in the range `-2^63..=2^63-1` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | @@ -150,8 +141,6 @@ error: casting `i64` to `usize` may lose the sign of the value | LL | get_value::() as usize; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = note: the cast operand may contain values in the range `-2^63..=2^63-1` error: casting `u64` to `isize` may truncate the value on targets with 32-bit wide pointers --> tests/ui/cast_size.rs:54:5 @@ -159,7 +148,6 @@ error: casting `u64` to `isize` may truncate the value on targets with 32-bit wi LL | get_value::() as isize; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operand may contain values in the range `0..=2^64-1` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | @@ -172,8 +160,6 @@ error: casting `u64` to `isize` may wrap around the value on targets with 64-bit | LL | get_value::() as isize; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = note: the cast operand may contain values in the range `0..=2^64-1` error: casting `u64` to `usize` may truncate the value on targets with 32-bit wide pointers --> tests/ui/cast_size.rs:57:5 @@ -181,7 +167,6 @@ error: casting `u64` to `usize` may truncate the value on targets with 32-bit wi LL | get_value::() as usize; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the cast operand may contain values in the range `0..=2^64-1` = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | @@ -194,16 +179,12 @@ error: casting `u32` to `isize` may wrap around the value on targets with 32-bit | LL | get_value::() as isize; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = note: the cast operand may contain values in the range `0..=2^32-1` error: casting `i32` to `usize` may lose the sign of the value --> tests/ui/cast_size.rs:63:5 | LL | get_value::() as usize; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = note: the cast operand may contain values in the range `-2^31..=2^31-1` error: casting `i32` to `f32` causes a loss of precision (`i32` is 32 bits wide, but `f32`'s mantissa is only 23 bits wide) --> tests/ui/cast_size.rs:68:5 From fe13b7d81793885ce28895ab3e282bdf5351f6ce Mon Sep 17 00:00:00 2001 From: RunDevelopment Date: Mon, 4 Aug 2025 22:37:52 +0200 Subject: [PATCH 13/13] Improved branch behavior --- .../src/casts/cast_possible_truncation.rs | 2 +- clippy_lints/src/casts/cast_possible_wrap.rs | 2 +- clippy_lints/src/casts/cast_sign_loss.rs | 2 +- clippy_utils/src/rinterval/arithmetic.rs | 115 ++++--- clippy_utils/src/rinterval/mod.rs | 314 ++++++++++++------ tests/ui/cast.rs | 25 ++ tests/ui/cast.stderr | 2 +- 7 files changed, 309 insertions(+), 153 deletions(-) diff --git a/clippy_lints/src/casts/cast_possible_truncation.rs b/clippy_lints/src/casts/cast_possible_truncation.rs index 9242d18ab044..08cc873fa982 100644 --- a/clippy_lints/src/casts/cast_possible_truncation.rs +++ b/clippy_lints/src/casts/cast_possible_truncation.rs @@ -29,7 +29,7 @@ pub(super) fn check<'cx>( let msg = match (cast_from.kind(), utils::int_ty_to_nbits(cx.tcx, cast_to)) { (ty::Int(_) | ty::Uint(_), Some(to_nbits)) => { - from_interval = i_cx.eval(cast_expr); + from_interval = i_cx.eval_int(cast_expr); let to_ty = if !from_is_size && to_is_size { // if we cast from a fixed-size integer to a pointer-sized integer, diff --git a/clippy_lints/src/casts/cast_possible_wrap.rs b/clippy_lints/src/casts/cast_possible_wrap.rs index d4a88d7c920a..c61580696808 100644 --- a/clippy_lints/src/casts/cast_possible_wrap.rs +++ b/clippy_lints/src/casts/cast_possible_wrap.rs @@ -87,7 +87,7 @@ pub(super) fn check<'cx>( ), }; - let from_interval = i_cx.eval(cast_op); + let from_interval = i_cx.eval_int(cast_op); if let Some(from_interval) = &from_interval && from_interval.fits_into(from_interval.ty.to_signed()) diff --git a/clippy_lints/src/casts/cast_sign_loss.rs b/clippy_lints/src/casts/cast_sign_loss.rs index 64bca356c737..bb3b9841de90 100644 --- a/clippy_lints/src/casts/cast_sign_loss.rs +++ b/clippy_lints/src/casts/cast_sign_loss.rs @@ -34,7 +34,7 @@ pub(super) fn check<'cx>( // reported if the signed integer expression can actually contain negative // values. if cast_from.is_integral() && cast_from.is_signed() { - if let Some(from_interval) = i_cx.eval(cast_op) + if let Some(from_interval) = i_cx.eval_int(cast_op) && from_interval.ty.is_signed() && from_interval.contains_negative() { diff --git a/clippy_utils/src/rinterval/arithmetic.rs b/clippy_utils/src/rinterval/arithmetic.rs index c3fc7a1c2aea..3201f1a32739 100644 --- a/clippy_utils/src/rinterval/arithmetic.rs +++ b/clippy_utils/src/rinterval/arithmetic.rs @@ -96,6 +96,53 @@ fn split_by_sign_bit_signed(i: &IInterval, mut f: impl FnMut(i128, i128, SignBit } } +fn parse_shift_strict(shift: &IInterval, bit_width: u8) -> Option<(u8, u8)> { + if shift.is_empty() { + return None; + } + + if shift.ty.is_signed() { + let (min, max) = shift.as_signed(); + if max < 0 || min >= bit_width as i128 { + return None; + } + + Some((min.max(0) as u8, max.min((bit_width - 1) as i128) as u8)) + } else { + let (min, max) = shift.as_unsigned(); + if min >= bit_width as u128 { + return None; + } + + Some((min as u8, max.min((bit_width - 1) as u128) as u8)) + } +} +fn parse_shift_wrapping(shift: &IInterval, bit_width: u8) -> Option<(u8, u8)> { + if shift.is_empty() { + return None; + } + + // check for large ranges + if shift.ty.is_signed() { + let (min, max) = shift.as_signed(); + if max.abs_diff(min) >= (bit_width - 1) as u128 { + return Some((0, bit_width - 1)); + } + } else { + let (min, max) = shift.as_unsigned(); + if max - min >= (bit_width - 1) as u128 { + return Some((0, bit_width - 1)); + } + } + + // due to how the `% bit_width`, we can completely ignore the maybe sign of + // the values and just cast to u8. + let min = (shift.min as u8) % bit_width; + let max = (shift.max as u8) % bit_width; + + Some((min, max)) +} + pub struct Arithmetic { /// If `true`, checked arithmetic will be assumed. /// @@ -2076,23 +2123,16 @@ impl Arithmetic { } pub fn strict_shl(lhs: &IInterval, rhs: &IInterval) -> ArithResult { - if rhs.ty != IntType::U32 { - return Err(ArithError::TypeError); - } - check_non_empty!(lhs, rhs); let ty = lhs.ty; - let bit_width: u32 = ty.bits() as u32; + let bit_width = ty.bits(); - let (r_min, r_max) = rhs.as_unsigned(); - let r_min = r_min as u32; - let r_max = (r_max as u32).min(bit_width - 1); - if r_min >= bit_width { + let Some((r_min, r_max)) = parse_shift_strict(rhs, bit_width) else { return Ok(IInterval::empty(ty)); - } + }; - let mask = !u128::MAX.unbounded_shl(bit_width); + let mask = !u128::MAX.unbounded_shl(bit_width as u32); let mut bits = Bits::from_non_empty(lhs); bits.zero = (bits.zero << r_min) & mask; @@ -2108,26 +2148,14 @@ impl Arithmetic { Ok(result) } pub fn wrapping_shl(lhs: &IInterval, rhs: &IInterval) -> ArithResult { - if rhs.ty != IntType::U32 { - return Err(ArithError::TypeError); - } - check_non_empty!(lhs, rhs); let ty = lhs.ty; - let bit_width = ty.bits() as u32; + let bit_width = ty.bits(); - let (r_min, r_max) = rhs.as_unsigned(); - let mut r_min = r_min as u32; - let mut r_max = r_max as u32; - - if r_max - r_min >= bit_width - 1 { - r_min = 0; - r_max = bit_width - 1; - } else { - r_min %= bit_width; - r_max %= bit_width; - } + let Some((r_min, r_max)) = parse_shift_wrapping(rhs, bit_width) else { + return Ok(IInterval::empty(ty)); + }; let result = if r_min <= r_max { Self::strict_shl( @@ -2171,21 +2199,14 @@ impl Arithmetic { } pub fn strict_shr(lhs: &IInterval, rhs: &IInterval) -> ArithResult { - if rhs.ty != IntType::U32 { - return Err(ArithError::TypeError); - } - check_non_empty!(lhs, rhs); let ty = lhs.ty; - let bit_width: u32 = ty.bits() as u32; + let bit_width = ty.bits(); - let (r_min, r_max) = rhs.as_unsigned(); - let r_min = r_min as u32; - let r_max = (r_max as u32).min(bit_width - 1); - if r_min >= bit_width { + let Some((r_min, r_max)) = parse_shift_strict(rhs, bit_width) else { return Ok(IInterval::empty(ty)); - } + }; if ty.is_signed() { Ok(split_by_sign_bit_signed(lhs, |min, max, sign| { @@ -2202,26 +2223,14 @@ impl Arithmetic { } } pub fn wrapping_shr(lhs: &IInterval, rhs: &IInterval) -> ArithResult { - if rhs.ty != IntType::U32 { - return Err(ArithError::TypeError); - } - check_non_empty!(lhs, rhs); let ty = lhs.ty; - let bit_width = ty.bits() as u32; + let bit_width = ty.bits(); - let (r_min, r_max) = rhs.as_unsigned(); - let mut r_min = r_min as u32; - let mut r_max = r_max as u32; - - if r_max - r_min >= bit_width - 1 { - r_min = 0; - r_max = bit_width - 1; - } else { - r_min %= bit_width; - r_max %= bit_width; - } + let Some((r_min, r_max)) = parse_shift_wrapping(rhs, bit_width) else { + return Ok(IInterval::empty(ty)); + }; if r_min <= r_max { Self::strict_shr( diff --git a/clippy_utils/src/rinterval/mod.rs b/clippy_utils/src/rinterval/mod.rs index 74cf4ecf1920..866116b07b7e 100644 --- a/clippy_utils/src/rinterval/mod.rs +++ b/clippy_utils/src/rinterval/mod.rs @@ -31,7 +31,104 @@ pub struct IntervalCtxt<'c, 'cx> { arth: Arithmetic, isize_ty: IntType, - cache: FxHashMap>, + cache: FxHashMap, +} + +#[derive(Clone, Debug)] +enum Value { + /// The value is of the never type. + Never, + /// The value is an integer. The set of possible values is represented by an interval. + Int(IInterval), + + /// Any value of an unknown type. We truly know nothing about this value. + Unknown, +} +impl Value { + pub fn as_int(&self, ty: IntType) -> Option { + match self { + Value::Int(interval) if interval.ty == ty => Some(interval.clone()), + // coerce never to an empty interval + Value::Never => Some(IInterval::empty(ty)), + _ => None, + } + } + pub fn int_or_unknown(interval: Result) -> Self { + match interval { + Ok(interval) => Value::Int(interval), + Err(_) => Value::Unknown, + } + } + pub fn unknown_to(self, f: impl FnOnce() -> Self) -> Self { + match self { + Value::Unknown => f(), + _ => self, + } + } + pub fn unknown_to_full(self, ty: IntType) -> Self { + match self { + Value::Unknown => Value::Int(IInterval::full(ty)), + _ => self, + } + } + pub fn full(ty: IntType) -> Self { + Value::Int(IInterval::full(ty)) + } + + pub fn union(self, other: Self) -> Self { + match (self, other) { + (Value::Never, value) | (value, Value::Never) => value, + + (_, Value::Unknown) | (Value::Unknown, _) => Value::Unknown, + + (Value::Int(a), Value::Int(b)) => { + if let Some(interval) = a.hull(&b) { + Value::Int(interval) + } else { + // This really shouldn't happen, but just in case + Value::Unknown + } + }, + } + } +} +impl From for Value { + fn from(interval: IInterval) -> Self { + Value::Int(interval) + } +} + +trait Extensions { + fn or_full(self, ty: IntType) -> Value; + fn or_unknown(self) -> Value; +} +impl Extensions for Option { + fn or_full(self, ty: IntType) -> Value { + match self { + Some(interval) if interval.ty == ty => Value::Int(interval), + _ => Value::Int(IInterval::full(ty)), + } + } + fn or_unknown(self) -> Value { + match self { + Some(interval) => Value::Int(interval), + None => Value::Unknown, + } + } +} +impl Extensions for Result { + fn or_full(self, ty: IntType) -> Value { + match self { + Ok(interval) if interval.ty == ty => Value::Int(interval), + _ => Value::Int(IInterval::full(ty)), + } + } + fn or_unknown(self) -> Value { + match self { + Ok(interval) => Value::Int(interval), + Err(_) => Value::Unknown, + } + } } impl<'c, 'cx> IntervalCtxt<'c, 'cx> { @@ -61,38 +158,50 @@ impl<'c, 'cx> IntervalCtxt<'c, 'cx> { /// /// If the given expression is not of a supported integer type, None is /// returned. - pub fn eval(&mut self, expr: &Expr<'cx>) -> Option { - let ty = self.to_int_type(self.typeck.expr_ty(expr))?; - - let expr = self.cx.expr_or_init(expr.peel_borrows()); - - if let Some(evaluated) = self.eval_ty(expr, ty) { - Some(evaluated) + pub fn eval_int(&mut self, expr: &Expr<'cx>) -> Option { + if let Value::Int(interval) = self.eval(expr) { + Some(interval) } else { - // we couldn't evaluate the expression for some reason, so just - // return the full range of the integer type. - Some(IInterval::full(ty)) + None } } - /// Evaluates an expression to an integer interval of the given type. - /// - /// If anything goes wrong or an expression cannot be evaluated, None is - /// returned. - fn eval_ty(&mut self, expr: &Expr<'cx>, ty: IntType) -> Option { + + fn eval(&mut self, expr: &Expr<'cx>) -> Value { + let cache_key = expr.hir_id; // Check the cache first. - if let Some(interval) = self.cache.get(&expr.hir_id) { + if let Some(interval) = self.cache.get(&cache_key) { return interval.clone(); } - // Evaluate the expression. - let interval = self.eval_ty_uncached(expr, ty); + // we only care about values, so ignore borrows + let expr = expr.peel_borrows(); + + let expr_ty = self.typeck.expr_ty(expr); + let Some(ty) = self.to_int_type(expr_ty) else { + return if expr_ty.is_never() { + Value::Never + } else { + Value::Unknown + }; + }; + + let expr = self.cx.expr_or_init(expr); + let value = self.eval_int_ty(expr, ty).unknown_to_full(ty); // Cache the result. - self.cache.insert(expr.hir_id, interval.clone()); + self.cache.insert(expr.hir_id, value.clone()); + self.cache.insert(cache_key, value.clone()); - interval + value } - fn eval_ty_uncached(&mut self, expr: &Expr<'cx>, ty: IntType) -> Option { + /// Evaluates an expression to an integer interval of the given type. + fn eval_int_ty(&mut self, expr: &Expr<'cx>, ty: IntType) -> Value { + let expr_ty = self.typeck.expr_ty(expr); + if expr_ty.is_never() { + // If the expression is never, we can return an empty interval. + return IInterval::empty(ty).into(); + } + match expr.kind { ExprKind::Lit(lit) => { return self.literal(&lit.node, ty); @@ -101,12 +210,14 @@ impl<'c, 'cx> IntervalCtxt<'c, 'cx> { return self.binary_op(op.node, lhs, rhs); }, ExprKind::Unary(op, operand) => { - let operand_interval = self.eval(operand)?; - return self.unary_op(op, &operand_interval); + if let Some(operand_interval) = self.eval(operand).as_int(ty) { + return self.unary_op(op, &operand_interval); + } }, ExprKind::Cast(expr, _) => { - let expr_interval = self.eval(expr)?; - return Arithmetic::cast_as(&expr_interval, ty).ok(); + if let Value::Int(expr_interval) = self.eval(expr) { + return Arithmetic::cast_as(&expr_interval, ty).or_full(ty); + } }, // For conditional expressions, we evaluate all branches and @@ -115,17 +226,10 @@ impl<'c, 'cx> IntervalCtxt<'c, 'cx> { // No attempt is made at trimming down on branches. All branches // are assumed to be reachable. ExprKind::If(_cond, if_true, Some(if_false)) => { - let true_interval = self.eval(if_true)?; - let false_interval = self.eval(if_false)?; - return true_interval.hull(&false_interval); + return Self::branches(&[if_true, if_false], |e| self.eval(*e)); }, ExprKind::Match(_expr, arms, _) => { - let mut combined = IInterval::empty(ty); - for arm in arms { - let arm_interval = self.eval(&arm.body)?; - combined = combined.hull(&arm_interval)?; - } - return Some(combined); + return Self::branches(arms, |arm| self.eval(arm.body)); }, // Known methods and functions of integer types. @@ -135,61 +239,73 @@ impl<'c, 'cx> IntervalCtxt<'c, 'cx> { } // if all else fails, try to evaluate the expression using const eval - self.const_eval(expr, ty) + self.const_eval(expr, ty).unknown_to_full(ty) } - fn literal(&self, lit: &LitKind, ty: IntType) -> Option { + fn branches(branches: &[T], mut get_value: impl FnMut(&T) -> Value) -> Value { + let mut combined = Value::Never; + + for branch in branches { + let branch_value = get_value(branch); + + if matches!(branch_value, Value::Unknown) { + // once unknown, always unknown + return Value::Unknown; + } + + combined = combined.union(branch_value); + } + + combined + } + + fn literal(&self, lit: &LitKind, ty: IntType) -> Value { match lit { - LitKind::Int(n, _) => Self::u128_repr_to_interval(n.get(), ty), - _ => None, + LitKind::Int(n, _) => Self::u128_repr_to_interval(n.get(), ty).or_full(ty), + _ => Value::Unknown, } } - fn binary_op(&mut self, op: BinOpKind, l_expr: &Expr<'cx>, r_expr: &Expr<'cx>) -> Option { - let lhs = &self.eval(l_expr)?; + fn binary_op(&mut self, op: BinOpKind, l_expr: &Expr<'cx>, r_expr: &Expr<'cx>) -> Value { + let Value::Int(lhs) = &self.eval(l_expr) else { + return Value::Unknown; + }; // The pattern `x * x` is quite common and will always result in a // positive value (absent overflow). To support this, special handling // is required. if matches!(op, BinOpKind::Mul) && self.is_same_variable(l_expr, r_expr) { - return Arithmetic::wrapping_pow(lhs, &IInterval::single_unsigned(IntType::U32, 2)).ok(); + return Arithmetic::wrapping_pow(lhs, &IInterval::single_unsigned(IntType::U32, 2)).or_unknown(); } - // shl and shr have weird issues with type inference, so we need to - // explicitly type the right-hand side as u32 - let rhs = if matches!(op, BinOpKind::Shl | BinOpKind::Shr) { - &self.eval_ty(r_expr, IntType::U32).unwrap_or_else(|| { - // if we can't evaluate the right-hand side, just return the full - // range of the integer type. - IInterval::full(IntType::U32) - }) - } else { - &self.eval(r_expr)? + let Value::Int(rhs) = &self.eval(r_expr) else { + return Value::Unknown; }; match op { - BinOpKind::Add => self.arth.add(lhs, rhs).ok(), - BinOpKind::Sub => self.arth.sub(lhs, rhs).ok(), - BinOpKind::Mul => self.arth.mul(lhs, rhs).ok(), - BinOpKind::Div => self.arth.div(lhs, rhs).ok(), - BinOpKind::Rem => self.arth.rem(lhs, rhs).ok(), - BinOpKind::BitAnd => Arithmetic::and(lhs, rhs).ok(), - BinOpKind::BitOr => Arithmetic::or(lhs, rhs).ok(), - BinOpKind::BitXor => Arithmetic::xor(lhs, rhs).ok(), - BinOpKind::Shl => self.arth.shl(lhs, rhs).ok(), - BinOpKind::Shr => self.arth.shr(lhs, rhs).ok(), - _ => None, + BinOpKind::Add => self.arth.add(lhs, rhs), + BinOpKind::Sub => self.arth.sub(lhs, rhs), + BinOpKind::Mul => self.arth.mul(lhs, rhs), + BinOpKind::Div => self.arth.div(lhs, rhs), + BinOpKind::Rem => self.arth.rem(lhs, rhs), + BinOpKind::BitAnd => Arithmetic::and(lhs, rhs), + BinOpKind::BitOr => Arithmetic::or(lhs, rhs), + BinOpKind::BitXor => Arithmetic::xor(lhs, rhs), + BinOpKind::Shl => self.arth.shl(lhs, rhs), + BinOpKind::Shr => self.arth.shr(lhs, rhs), + _ => return Value::Unknown, } + .or_unknown() } - fn unary_op(&mut self, op: UnOp, value: &IInterval) -> Option { + fn unary_op(&mut self, op: UnOp, value: &IInterval) -> Value { match op { - UnOp::Neg => self.arth.neg(value).ok(), - UnOp::Not => Arithmetic::not(value).ok(), + UnOp::Neg => self.arth.neg(value).or_unknown(), + UnOp::Not => Arithmetic::not(value).or_unknown(), UnOp::Deref => { // Deref doesn't really make sense for numbers, but it does make // sense for references to numbers. Assuming that the value is // indeed a reference to a number, we can just return the value // of the number. - Some(value.clone()) + Value::Int(value.clone()) }, } } @@ -200,8 +316,8 @@ impl<'c, 'cx> IntervalCtxt<'c, 'cx> { path: &PathSegment<'_>, self_arg: &Expr<'cx>, args: &[Expr<'cx>], - ty: IntType, - ) -> Option { + ret_ty: IntType, + ) -> Value { match args { [] => { let f: Option ArithResult> = match path.ident.name { @@ -243,9 +359,10 @@ impl<'c, 'cx> IntervalCtxt<'c, 'cx> { _ => None, }; - if let Some(f) = f { - let self_arg = self.eval(self_arg)?; - return f(&self.arth, &self_arg).ok(); + if let Some(f) = f + && let Value::Int(self_arg) = self.eval(self_arg) + { + return f(&self.arth, &self_arg).or_full(ret_ty); } }, @@ -328,10 +445,11 @@ impl<'c, 'cx> IntervalCtxt<'c, 'cx> { _ => None, }; - if let Some(f) = f { - let self_arg = self.eval(self_arg)?; - let arg1 = self.eval(arg1)?; - return f(&self.arth, &self_arg, &arg1).ok(); + if let Some(f) = f + && let Value::Int(self_arg) = self.eval(self_arg) + && let Value::Int(arg1) = self.eval(arg1) + { + return f(&self.arth, &self_arg, &arg1).or_full(ret_ty); } }, @@ -343,11 +461,12 @@ impl<'c, 'cx> IntervalCtxt<'c, 'cx> { _ => None, }; - if let Some(f) = f { - let self_arg = self.eval(self_arg)?; - let arg1 = self.eval(arg1)?; - let arg2 = self.eval(arg2)?; - return f(&self.arth, &self_arg, &arg1, &arg2).ok(); + if let Some(f) = f + && let Value::Int(self_arg) = self.eval(self_arg) + && let Value::Int(arg1) = self.eval(arg1) + && let Value::Int(arg2) = self.eval(arg2) + { + return f(&self.arth, &self_arg, &arg1, &arg2).or_full(ret_ty); } }, _ => {}, @@ -369,44 +488,47 @@ impl<'c, 'cx> IntervalCtxt<'c, 'cx> { let is_option = is_type_diagnostic_item(self.cx, self_ty, sym::Option); if is_option || true { - let self_interval = self.eval_ty(self_arg, ty)?; + let self_value = self.eval_int_ty(self_arg, ret_ty); match path.ident.name { sym::unwrap | sym::unwrap_unchecked | sym::expect => { // these are all the same in that they return the Some value - return Some(self_interval); + return self_value; }, sym::unwrap_or_default => { // the default value of all integer types is 0, so we can // evaluate the Some value and add 0 to it. - let zero = if ty.is_signed() { - IInterval::single_signed(ty, 0) + let zero = if ret_ty.is_signed() { + IInterval::single_signed(ret_ty, 0) } else { - IInterval::single_unsigned(ty, 0) + IInterval::single_unsigned(ret_ty, 0) }; - return self_interval.hull(&zero); + return self_value.union(zero.into()); }, sym::unwrap_or => { // the default value is given as the second argument - let or_interval = self.eval(args.get(0)?)?; - return self_interval.hull(&or_interval); + let Some(arg0) = args.get(0) else { + // this really shouldn't happen, but just in case + return Value::Unknown; + }; + let or_interval = self.eval(arg0); + return self_value.union(or_interval); }, _ => {}, } } } - None + Value::full(ret_ty) } /// Uses the const eval machinery to evaluate an expression to a single /// integer value. - fn const_eval(&self, expr: &Expr<'_>, ty: IntType) -> Option { - let const_val = self.const_eval.eval(expr)?; - if let Constant::Int(n) = const_val { - return Self::u128_repr_to_interval(n, ty); + fn const_eval(&self, expr: &Expr<'_>, ty: IntType) -> Value { + if let Some(Constant::Int(n)) = self.const_eval.eval(expr) { + return Self::u128_repr_to_interval(n, ty).or_full(ty); } - None + Value::Unknown } fn is_same_variable(&self, expr: &Expr<'_>, other: &Expr<'_>) -> bool { @@ -449,7 +571,7 @@ impl<'c, 'cx> IntervalCtxt<'c, 'cx> { TyKind::Int(IntTy::I32) => Some(IntType::I32), TyKind::Int(IntTy::I64) => Some(IntType::I64), TyKind::Int(IntTy::I128) => Some(IntType::I128), - TyKind::Uint(UintTy::Usize) => Some(self.isize_ty.swap_signedness()), + TyKind::Uint(UintTy::Usize) => Some(self.isize_ty.to_unsigned()), TyKind::Uint(UintTy::U8) => Some(IntType::U8), TyKind::Uint(UintTy::U16) => Some(IntType::U16), TyKind::Uint(UintTy::U32) => Some(IntType::U32), diff --git a/tests/ui/cast.rs b/tests/ui/cast.rs index be43cb40088c..37945d6320f8 100644 --- a/tests/ui/cast.rs +++ b/tests/ui/cast.rs @@ -678,6 +678,31 @@ fn test_range_formatting(value: i32) { //~| cast_sign_loss } +fn test_branching(value: i32) { + let a: u64 = if get_value() { + 100 + } else { + panic!("value must be positive") + }; + a as u8; + + let b: u64 = match get_value::() { + 0 => 100, + 1 => return, + 2 => unreachable!(), + 3 => todo!(), + 4 => { + if get_value() { + return; + } else { + panic!("value must be positive") + } + }, + _ => panic!("value must be positive"), + }; + b as u8; +} + fn test_narrowing(value: i32) { if value > 0 { // value as u32; diff --git a/tests/ui/cast.stderr b/tests/ui/cast.stderr index f6442f798d8e..611967feebcb 100644 --- a/tests/ui/cast.stderr +++ b/tests/ui/cast.stderr @@ -834,7 +834,7 @@ LL | (get_value::() as i32 * 64 - 4) as u16; = note: the cast operand may contain values in the range `-2^13-4..=2^13-68` error: casting `i32` to `u32` may lose the sign of the value - --> tests/ui/cast.rs:684:9 + --> tests/ui/cast.rs:709:9 | LL | (value - 1) as u32; | ^^^^^^^^^^^^^^^^^^