diff --git a/clippy_lints/src/casts/cast_possible_truncation.rs b/clippy_lints/src/casts/cast_possible_truncation.rs index 2eebe8492327..08cc873fa982 100644 --- a/clippy_lints/src/casts/cast_possible_truncation.rs +++ b/clippy_lints/src/casts/cast_possible_truncation.rs @@ -1,106 +1,59 @@ -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, 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 - } -} +pub(super) fn check<'cx>( + cx: &LateContext<'cx>, + i_cx: &mut rinterval::IntervalCtxt<'_, 'cx>, + expr: &Expr<'_>, + cast_expr: &Expr<'cx>, + cast_from: Ty<'_>, + cast_to: Ty<'_>, + cast_to_span: Span, +) { + let mut from_interval = None; -fn get_constant_bits(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option { - constant_int(cx, expr).map(|c| u64::from(128 - c.leading_zeros())) -} + let from_is_size = is_isize_or_usize(cast_from); + let to_is_size = is_isize_or_usize(cast_to); -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 + 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_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, + // we want assume the worst case of usize being 32-bit + if cast_to.is_signed() { + rinterval::IntType::I32 } 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) + rinterval::IntType::U32 + } } else { - None + i_cx.to_int_type(cast_to) + .expect("the to cast type should be an integral type") }; - 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)) + + if let Some(from_interval) = &from_interval + && from_interval.fits_into(to_ty) { - 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 + // No truncation possible. + return; } - }, - _ => nbits, - } -} -pub(super) fn check( - cx: &LateContext<'_>, - expr: &Expr<'_>, - cast_expr: &Expr<'_>, - cast_from: Ty<'_>, - cast_to: Ty<'_>, - cast_to_span: Span, -) { - 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 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 +86,7 @@ pub(super) fn check( }; 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", @@ -167,6 +120,12 @@ pub(super) fn check( }; span_lint_and_then(cx, CAST_POSSIBLE_TRUNCATION, expr.span, msg, |diag| { + if let Some(from_interval) = from_interval + && !from_interval.is_full() + { + diag.note(utils::format_cast_operand(from_interval)); + } + 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..c61580696808 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,14 @@ 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>, + i_cx: &mut rinterval::IntervalCtxt<'_, '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), @@ -79,7 +87,22 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_from: Ty<'_>, ca ), }; + 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()) + { + // 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 + && !from_interval.is_full() + { + diag.note(utils::format_cast_operand(from_interval)); + } + 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..bb3b9841de90 100644 --- a/clippy_lints/src/casts/cast_sign_loss.rs +++ b/clippy_lints/src/casts/cast_sign_loss.rs @@ -1,340 +1,54 @@ -use std::convert::Infallible; -use std::ops::ControlFlow; - -use clippy_utils::consts::{ConstEvalCtxt, Constant}; -use clippy_utils::diagnostics::span_lint; -use clippy_utils::visitors::{Descend, for_each_expr_without_closures}; -use clippy_utils::{method_chain_args, sext, sym}; -use rustc_hir::{BinOpKind, Expr, ExprKind}; +use clippy_utils::diagnostics::{span_lint, span_lint_and_then}; +use clippy_utils::rinterval; +use rustc_hir::Expr; 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, -]; +use rustc_middle::ty::Ty; -/// 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>, - expr: &Expr<'_>, - cast_op: &Expr<'_>, + i_cx: &mut rinterval::IntervalCtxt<'_, 'cx>, + expr: &Expr<'cx>, + cast_op: &Expr<'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; } -} - -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.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.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 + // 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() { + if let Some(from_interval) = i_cx.eval_int(cast_op) + && from_interval.ty.is_signed() + && from_interval.contains_negative() { - 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, + span_lint_and_then( + cx, + CAST_SIGN_LOSS, + expr.span, + format!("casting `{cast_from}` to `{cast_to}` may lose the sign of the value"), + |diag| { + if !from_interval.is_full() { + diag.note(utils::format_cast_operand(from_interval)); + } + }, + ); } } - - // 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/mod.rs b/clippy_lints/src/casts/mod.rs index 37accff5eaa8..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, 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_lints/src/casts/utils.rs b/clippy_lints/src/casts/utils.rs index d846d78b9ee7..052e7f0fac35 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,96 @@ 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 { + let min = PrettyNumber::from(min); + let max = PrettyNumber::from(max); + 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 { + 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/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index a14ea72ba0c7..ebb685cb9e57 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..3201f1a32739 --- /dev/null +++ b/clippy_utils/src/rinterval/arithmetic.rs @@ -0,0 +1,2575 @@ +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) + } +} + +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. + /// + /// 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) + } + } + 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 { + 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)) + }, + } + } + + /// 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 { + 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), + } + } + + /// 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)?; + 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); + + 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 (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 { + 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 { + check_non_empty!(lhs, rhs); + + let ty = lhs.ty; + let bit_width = ty.bits(); + + 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 as u32); + + 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 { + check_non_empty!(lhs, rhs); + + let ty = lhs.ty; + let bit_width = ty.bits(); + + 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( + 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 { + check_non_empty!(lhs, rhs); + + let ty = lhs.ty; + let bit_width = ty.bits(); + + 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| { + 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 { + check_non_empty!(lhs, rhs); + + let ty = lhs.ty; + let bit_width = ty.bits(); + + 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( + 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)?) + } + + 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() { + 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..34c530ad6bed --- /dev/null +++ b/clippy_utils/src/rinterval/iinterval.rs @@ -0,0 +1,407 @@ +#[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 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 { + 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 **superset** 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..866116b07b7e --- /dev/null +++ b/clippy_utils/src/rinterval/mod.rs @@ -0,0 +1,583 @@ +//! 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_data_structures::fx::FxHashMap; +use rustc_hir::def::Res; +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; + +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, + + 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> { + 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, + + cache: FxHashMap::default(), + } + } + + /// Evaluates an expression to an integer interval. + /// + /// If the given expression is not of a supported integer type, None is + /// returned. + pub fn eval_int(&mut self, expr: &Expr<'cx>) -> Option { + if let Value::Int(interval) = self.eval(expr) { + Some(interval) + } else { + None + } + } + + 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(&cache_key) { + return interval.clone(); + } + + // 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, value.clone()); + self.cache.insert(cache_key, value.clone()); + + value + } + /// 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); + }, + ExprKind::Binary(op, lhs, rhs) => { + return self.binary_op(op.node, lhs, rhs); + }, + ExprKind::Unary(op, operand) => { + if let Some(operand_interval) = self.eval(operand).as_int(ty) { + return self.unary_op(op, &operand_interval); + } + }, + ExprKind::Cast(expr, _) => { + 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 + // 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)) => { + return Self::branches(&[if_true, if_false], |e| self.eval(*e)); + }, + ExprKind::Match(_expr, arms, _) => { + return Self::branches(arms, |arm| self.eval(arm.body)); + }, + + // 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).unknown_to_full(ty) + } + + 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).or_full(ty), + _ => Value::Unknown, + } + } + 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)).or_unknown(); + } + + let Value::Int(rhs) = &self.eval(r_expr) else { + return Value::Unknown; + }; + + match op { + 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) -> Value { + match op { + 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. + Value::Int(value.clone()) + }, + } + } + + /// Calls to methods that returns an integer. + fn method_call( + &mut self, + path: &PathSegment<'_>, + self_arg: &Expr<'cx>, + args: &[Expr<'cx>], + ret_ty: IntType, + ) -> Value { + 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 | 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)), + 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::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)), + + 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 + && let Value::Int(self_arg) = self.eval(self_arg) + { + return f(&self.arth, &self_arg).or_full(ret_ty); + } + }, + + [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::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)), + + 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 + && 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); + } + }, + + [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 + && 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); + } + }, + _ => {}, + } + + /// 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_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 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 ret_ty.is_signed() { + IInterval::single_signed(ret_ty, 0) + } else { + IInterval::single_unsigned(ret_ty, 0) + }; + return self_value.union(zero.into()); + }, + sym::unwrap_or => { + // the default value is given as the second argument + 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); + }, + _ => {}, + } + } + } + + 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) -> Value { + if let Some(Constant::Int(n)) = self.const_eval.eval(expr) { + return Self::u128_repr_to_interval(n, ty).or_full(ty); + } + Value::Unknown + } + + 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 { + 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(_, _) => { + 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)) + }, + } + } + 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), + 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.to_unsigned()), + 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 ce7cc9348fbd..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, @@ -94,6 +95,8 @@ generate! { cast, cast_const, cast_mut, + cast_signed, + cast_unsigned, ceil, ceil_char_boundary, chain, @@ -101,10 +104,21 @@ generate! { check_attributes, checked_abs, 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, + checked_shl, + checked_shr, checked_sub, clamp, clippy_utils, @@ -120,6 +134,7 @@ generate! { copy_to, copy_to_nonoverlapping, count_ones, + count_zeros, create, create_new, cycle, @@ -127,6 +142,8 @@ generate! { de, diagnostics, disallowed_types, + div_ceil, + div_euclid, drain, dump, ends_with, @@ -190,12 +207,17 @@ generate! { is_ok, is_some, is_some_and, + ilog, + ilog10, + ilog2, isqrt, itertools, join, kw, last, lazy_static, + leading_ones, + leading_zeros, lint_vec, ln, lock, @@ -215,6 +237,7 @@ generate! { max_by_key, max_value, maximum, + midpoint, min, min_by, min_by_key, @@ -230,6 +253,8 @@ generate! { next_back, next_if, next_if_eq, + next_multiple_of, + next_power_of_two, next_tuple, nth, ok, @@ -289,6 +314,8 @@ generate! { rustfmt_skip, rwlock, saturating_abs, + saturating_mul, + saturating_neg, saturating_pow, scan, seek, @@ -319,6 +346,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, @@ -341,14 +379,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, @@ -367,7 +410,14 @@ generate! { warnings, wildcard_imports, with_capacity, + wrapping_abs, + wrapping_div_euclid, + wrapping_neg, + wrapping_next_power_of_two, 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..37945d6320f8 100644 --- a/tests/ui/cast.rs +++ b/tests/ui/cast.rs @@ -15,26 +15,29 @@ 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 = 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 @@ -52,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 @@ -71,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; @@ -85,51 +89,51 @@ 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 - 1usize as i8; + get_value::() as i8; //~^ 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 @@ -157,9 +161,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 +169,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 +182,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; @@ -436,7 +436,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 } @@ -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 @@ -461,13 +459,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 +484,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 @@ -552,20 +550,165 @@ 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; + // Don't lint. + (get_value::() as u64 % get_value::()) as u8; +} + +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 +} + +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; + // this is round(x / 510 * 255), but faster + ((x + 1) >> 1) as u8 +} +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; + // 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 +} + +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; + } + + // 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; + let half_exp = unbiased_exp + 15; + + // Check for exponent overflow, return +infinity + if half_exp >= 0x1F { + return (half_sign | 0x7C00u32) as u16; + } + + // 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 + } +} + +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 +} + +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; + (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 1cb30d956679..611967feebcb 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,41 +73,47 @@ 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; - | ^^^^^^^^^^ +LL | get_value::() as i8; + | ^^^^^^^^^^^^^^^^^^^^^^^^ | = 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:58:5 + --> tests/ui/cast.rs:61:5 | -LL | 1i32 as u8; - | ^^^^^^^^^^ +LL | get_value::() as u8; + | ^^^^^^^^^^^^^^^^^^^^^^^^ | = 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 `f64` to `isize` may truncate the value +error: casting `i32` to `u8` may lose the sign of the value --> tests/ui/cast.rs:61:5 | +LL | get_value::() as u8; + | ^^^^^^^^^^^^^^^^^^^^^^^^ + +error: casting `f64` to `isize` may truncate the value + --> tests/ui/cast.rs:65:5 + | 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:68:5 | LL | 1f64 as usize; | ^^^^^^^^^^^^^ @@ -115,13 +121,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:64:5 + --> tests/ui/cast.rs:68:5 | LL | 1f64 as usize; | ^^^^^^^^^^^^^ error: casting `u32` to `u16` may truncate the value - --> tests/ui/cast.rs:68:5 + --> tests/ui/cast.rs:72:5 | LL | 1f32 as u32 as u16; | ^^^^^^^^^^^^^^^^^^ @@ -134,7 +140,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:72:5 | LL | 1f32 as u32 as u16; | ^^^^^^^^^^^ @@ -142,26 +148,26 @@ 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:72: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:78:22 | -LL | let _x: i8 = 1i32 as _; - | ^^^^^^^^^ +LL | let _x: i8 = get_value::() as _; + | ^^^^^^^^^^^^^^^^^^^^^^^ | = 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:77:9 + --> tests/ui/cast.rs:81:9 | LL | 1f32 as i32; | ^^^^^^^^^^^ @@ -169,7 +175,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:84:9 | LL | 1f64 as i32; | ^^^^^^^^^^^ @@ -177,7 +183,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:87:9 | LL | 1f32 as u8; | ^^^^^^^^^^ @@ -185,179 +191,161 @@ 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:87: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:92:5 | -LL | 1u8 as i8; - | ^^^^^^^^^ +LL | get_value::() as i8; + | ^^^^^^^^^^^^^^^^^^^^^^^ | = 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:95:5 | -LL | 1u16 as i16; - | ^^^^^^^^^^^ +LL | get_value::() as i16; + | ^^^^^^^^^^^^^^^^^^^^^^^^^ error: casting `u32` to `i32` may wrap around the value - --> tests/ui/cast.rs:94:5 + --> tests/ui/cast.rs:98:5 | -LL | 1u32 as i32; - | ^^^^^^^^^^^ +LL | get_value::() as i32; + | ^^^^^^^^^^^^^^^^^^^^^^^^^ error: casting `u64` to `i64` may wrap around the value - --> tests/ui/cast.rs:97:5 + --> tests/ui/cast.rs:101:5 | -LL | 1u64 as i64; - | ^^^^^^^^^^^ +LL | get_value::() as i64; + | ^^^^^^^^^^^^^^^^^^^^^^^^^ error: casting `usize` to `isize` may wrap around the value - --> tests/ui/cast.rs:100:5 + --> tests/ui/cast.rs:104:5 | -LL | 1usize as isize; - | ^^^^^^^^^^^^^^^ +LL | get_value::() as isize; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: casting `usize` to `i8` may truncate the value - --> tests/ui/cast.rs:104:5 +error: casting `isize` to `i8` may truncate the value + --> tests/ui/cast.rs:108:5 | -LL | 1usize as i8; - | ^^^^^^^^^^^^ +LL | get_value::() as i8; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ | = 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 - --> tests/ui/cast.rs:108:5 + --> tests/ui/cast.rs:112:5 | -LL | 1usize as i16; - | ^^^^^^^^^^^^^ +LL | get_value::() as i16; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = 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:108:5 + --> tests/ui/cast.rs:112:5 | -LL | 1usize as i16; - | ^^^^^^^^^^^^^ +LL | get_value::() as i16; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = 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:117:5 | -LL | 1usize as i32; - | ^^^^^^^^^^^^^ +LL | get_value::() as i32; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = 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:113:5 + --> tests/ui/cast.rs:117:5 | -LL | 1usize as i32; - | ^^^^^^^^^^^^^ +LL | get_value::() as i32; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ 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:122:5 | -LL | 1usize as i64; - | ^^^^^^^^^^^^^ +LL | get_value::() as i64; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ 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:128:5 | -LL | 1u16 as isize; - | ^^^^^^^^^^^^^ +LL | get_value::() as isize; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = 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:132:5 | -LL | 1u32 as isize; - | ^^^^^^^^^^^^^ +LL | get_value::() as isize; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ 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:136:5 | -LL | 1u64 as isize; - | ^^^^^^^^^^^^^ +LL | get_value::() as isize; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = 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:132:5 + --> tests/ui/cast.rs:136:5 | -LL | 1u64 as isize; - | ^^^^^^^^^^^^^ +LL | get_value::() as isize; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: casting `i32` to `u32` may lose the sign of the value - --> tests/ui/cast.rs:138:5 + --> tests/ui/cast.rs:142:5 | 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:142:5 + --> tests/ui/cast.rs:146:5 | 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:154:5 + --> tests/ui/cast.rs:158:5 | LL | (i8::MIN).abs() as u8; | ^^^^^^^^^^^^^^^^^^^^^ - -error: casting `i64` to `u64` may lose the sign of the value - --> tests/ui/cast.rs:159:5 - | -LL | (-1i64).abs() as u64; - | ^^^^^^^^^^^^^^^^^^^^ - -error: casting `isize` to `usize` may lose the sign of the value - --> tests/ui/cast.rs:161:5 - | -LL | (-1isize).abs() as usize; - | ^^^^^^^^^^^^^^^^^^^^^^^^ - -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; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -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 operand is `-128` error: casting `i64` to `i8` may truncate the value --> tests/ui/cast.rs:237:5 @@ -365,6 +353,7 @@ error: casting `i64` to `i8` may truncate the value LL | (-99999999999i64).min(1) as i8; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | + = 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 | @@ -378,6 +367,7 @@ error: casting `u64` to `u8` may truncate the value LL | 999999u64.clamp(0, 256) as u8; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | + = 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 | @@ -471,6 +461,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..=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 | @@ -484,6 +475,7 @@ error: casting `u32` to `u8` may truncate the value LL | let c = (q / 1000) as u8; | ^^^^^^^^^^^^^^^^ | + = 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 | @@ -492,13 +484,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:438:9 - | -LL | (x * x) as u32; - | ^^^^^^^^^^^^^^ - -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 }; | ^^^^^^^^^^^^^^^^^^^^^^ @@ -506,71 +492,73 @@ LL | let _a = |x: i32| -> u32 { (x * x * x * x) as u32 }; 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; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -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 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 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 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 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; | ^^^^^^^^^^^^^^ 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; | ^^^^^^^^^^^^^^^^^^ 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; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ 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; | ^^^^^^^^^^^^^^^^^^^^^^^^^^ 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; | ^^^^^^^^^^^^^^^^^^^^^^ 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; | ^^^^^^^^^^^^^^^^^^^^^^^ 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; | ^^^^^ @@ -578,97 +566,97 @@ 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; | ^^^^^^^^^^^^^^^^^^^^^^^ 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; | ^^^^^^^^^^^^^^^^^^^^^^ 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; | ^^^^^^^^^^^^^^^ 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; | ^^^^^^^^^^^^^^^^^^ 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; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 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; | ^^^^^^^^^^^^^^^^^^ 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; | ^^^^^^^^^^^^^^^^^^^ 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; | ^^^^^^^^^^^^^^^^^^^^^^ 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; | ^^^^^^^^^^^^^^^ 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; | ^^^^^^^^^^^^^^^^^^^^^^^ 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; | ^^^^^^^^^^^^^^ 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; | ^^^^^^^^^^^^^^^^^^ 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; | ^^^^^^^^^^^^^^^^^^^^^^ 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; | ^^^^^^^^^^^^^^^^^^^^^^^^^^ 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 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 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 | ^^^^^^^^^^^^^^^ @@ -676,10 +664,11 @@ LL | let _ = i32::MIN as u32; // cast_sign_loss LL | m!(); | ---- in this macro invocation | + = 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 | ^^^^^^^^^^^^^^ @@ -687,6 +676,7 @@ LL | let _ = u32::MAX as u8; // cast_possible_truncation LL | m!(); | ---- in this macro invocation | + = 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 @@ -696,7 +686,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 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -708,7 +698,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:549:5 + --> tests/ui/cast.rs:547:5 | LL | bar.unwrap().unwrap() as usize | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -721,36 +711,133 @@ 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 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 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; - | ^^^^^^^^^^^^^^^^^^^^^^^ +LL | (256 & get_value::()) as u8; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | + = 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 - (256 & 999999u64) as u8; -LL + u8::try_from(256 & 999999u64); +LL - (256 & get_value::()) as u8; +LL + u8::try_from(256 & get_value::()); | -error: casting `u64` to `u8` may truncate the value - --> tests/ui/cast.rs:569:5 +error: casting `u16` to `i16` may wrap around the value + --> tests/ui/cast.rs:584:13 | -LL | (255 % 999999u64) as u8; - | ^^^^^^^^^^^^^^^^^^^^^^^ +LL | let x = x as i16 - 0x180; + | ^^^^^^^^ + +error: casting `u16` to `i16` may wrap around the value + --> tests/ui/cast.rs:594:13 + | +LL | let x = x as i16 - 0x180; + | ^^^^^^^^ + +error: casting `u32` to `u16` may truncate the value + --> tests/ui/cast.rs:652:16 + | +LL | return (half_sign | half_man) as u16; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = 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); +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:657:20 + | +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:664:9 | +LL | ((half_sign | half_exp | half_man) + 1) as u16 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = 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:667:9 + | +LL | (half_sign | half_exp | half_man) as u16 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = 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: 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: casting `i32` to `u32` may lose the sign of the value + --> tests/ui/cast.rs:709:9 + | +LL | (value - 1) as u32; + | ^^^^^^^^^^^^^^^^^^ -error: aborting due to 92 previous errors +error: aborting due to 97 previous errors diff --git a/tests/ui/cast_size.64bit.stderr b/tests/ui/cast_size.64bit.stderr index ba1419583aeb..abbb86e6a311 100644 --- a/tests/ui/cast_size.64bit.stderr +++ b/tests/ui/cast_size.64bit.stderr @@ -1,20 +1,20 @@ 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; - | ^^^^^^^^^^^^ +LL | get_value::() as i8; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ | = 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) - --> tests/ui/cast_size.rs:24:5 + --> tests/ui/cast_size.rs:28:5 | LL | x0 as f32; | ^^^^^^^^^ @@ -23,167 +23,189 @@ 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; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = 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; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = 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: `-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; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = 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; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = 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: `-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; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = 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; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = 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; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 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; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = 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; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ 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; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = 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 | get_value::() as isize; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: casting `i32` to `usize` may lose the sign of the value + --> tests/ui/cast_size.rs:63:5 | -LL | 1u32 as isize; - | ^^^^^^^^^^^^^ +LL | get_value::() as usize; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ 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:79:20 | -LL | const N: u16 = M as u16; - | ^^^^^^^^ +LL | const O: u16 = (M * 1000) as u16; + | ^^^^^^^^^^^^^^^^^ | + = note: the cast operand is `100000` = 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..0c7222a5d0df 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; + get_value::() 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; @@ -69,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 }