1 use clippy_utils::consts::{constant, Constant};
2 use clippy_utils::diagnostics::span_lint;
3 use clippy_utils::expr_or_init;
4 use clippy_utils::ty::{get_discriminant_value, is_isize_or_usize};
5 use rustc_hir::def::{DefKind, Res};
6 use rustc_hir::{BinOpKind, Expr, ExprKind};
7 use rustc_lint::LateContext;
8 use rustc_middle::ty::{self, FloatTy, Ty};
9 use rustc_target::abi::IntegerType;
11 use super::{utils, CAST_ENUM_TRUNCATION, CAST_POSSIBLE_TRUNCATION};
13 fn constant_int(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<u128> {
14 if let Some((Constant::Int(c), _)) = constant(cx, cx.typeck_results(), expr) {
21 fn get_constant_bits(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<u64> {
22 constant_int(cx, expr).map(|c| u64::from(128 - c.leading_zeros()))
25 fn apply_reductions(cx: &LateContext<'_>, nbits: u64, expr: &Expr<'_>, signed: bool) -> u64 {
26 match expr_or_init(cx, expr).kind {
27 ExprKind::Cast(inner, _) => apply_reductions(cx, nbits, inner, signed),
28 ExprKind::Block(block, _) => block.expr.map_or(nbits, |e| apply_reductions(cx, nbits, e, signed)),
29 ExprKind::Binary(op, left, right) => match op.node {
31 apply_reductions(cx, nbits, left, signed).saturating_sub(if signed {
32 // let's be conservative here
35 // by dividing by 1, we remove 0 bits, etc.
36 get_constant_bits(cx, right).map_or(0, |b| b.saturating_sub(1))
39 BinOpKind::Rem | BinOpKind::BitAnd => get_constant_bits(cx, right)
40 .unwrap_or(u64::max_value())
41 .min(apply_reductions(cx, nbits, left, signed)),
42 BinOpKind::Shr => apply_reductions(cx, nbits, left, signed)
43 .saturating_sub(constant_int(cx, right).map_or(0, |s| u64::try_from(s).expect("shift too high"))),
46 ExprKind::MethodCall(method, left, [right], _) => {
50 let max_bits = if method.ident.as_str() == "min" {
51 get_constant_bits(cx, right)
55 apply_reductions(cx, nbits, left, signed).min(max_bits.unwrap_or(u64::max_value()))
57 ExprKind::MethodCall(method, _, [lo, hi], _) => {
58 if method.ident.as_str() == "clamp" {
59 //FIXME: make this a diagnostic item
60 if let (Some(lo_bits), Some(hi_bits)) = (get_constant_bits(cx, lo), get_constant_bits(cx, hi)) {
61 return lo_bits.max(hi_bits);
66 ExprKind::MethodCall(method, _value, [], _) => {
67 if method.ident.name.as_str() == "signum" {
68 0 // do not lint if cast comes from a `signum` function
77 pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>) {
78 let msg = match (cast_from.kind(), cast_to.is_integral()) {
79 (ty::Int(_) | ty::Uint(_), true) => {
80 let from_nbits = apply_reductions(
82 utils::int_ty_to_nbits(cast_from, cx.tcx),
84 cast_from.is_signed(),
86 let to_nbits = utils::int_ty_to_nbits(cast_to, cx.tcx);
88 let (should_lint, suffix) = match (is_isize_or_usize(cast_from), is_isize_or_usize(cast_to)) {
89 (true, true) | (false, false) => (to_nbits < from_nbits, ""),
93 " on targets with 64-bit wide pointers"
98 (false, true) => (from_nbits == 64, " on targets with 32-bit wide pointers"),
105 format!("casting `{cast_from}` to `{cast_to}` may truncate the value{suffix}",)
108 (ty::Adt(def, _), true) if def.is_enum() => {
109 let (from_nbits, variant) = if let ExprKind::Path(p) = &cast_expr.kind
110 && let Res::Def(DefKind::Ctor(..), id) = cx.qpath_res(p, cast_expr.hir_id)
112 let i = def.variant_index_with_ctor_id(id);
113 let variant = def.variant(i);
114 let nbits = utils::enum_value_nbits(get_discriminant_value(cx.tcx, *def, i));
115 (nbits, Some(variant))
117 (utils::enum_ty_to_nbits(*def, cx.tcx), None)
119 let to_nbits = utils::int_ty_to_nbits(cast_to, cx.tcx);
121 let cast_from_ptr_size = def.repr().int.map_or(true, |ty| matches!(ty, IntegerType::Pointer(_),));
122 let suffix = match (cast_from_ptr_size, is_isize_or_usize(cast_to)) {
123 (false, false) if from_nbits > to_nbits => "",
124 (true, false) if from_nbits > to_nbits => "",
125 (false, true) if from_nbits > 64 => "",
126 (false, true) if from_nbits > 32 => " on targets with 32-bit wide pointers",
130 if let Some(variant) = variant {
133 CAST_ENUM_TRUNCATION,
136 "casting `{cast_from}::{}` to `{cast_to}` will truncate the value{suffix}",
142 format!("casting `{cast_from}` to `{cast_to}` may truncate the value{suffix}",)
145 (ty::Float(_), true) => {
146 format!("casting `{cast_from}` to `{cast_to}` may truncate the value")
149 (ty::Float(FloatTy::F64), false) if matches!(cast_to.kind(), &ty::Float(FloatTy::F32)) => {
150 "casting `f64` to `f32` may truncate the value".to_string()
156 span_lint(cx, CAST_POSSIBLE_TRUNCATION, expr.span, &msg);