]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/casts/cast_possible_truncation.rs
Auto merge of #8322 - jubnzv:8282-single-match, r=llogiq
[rust.git] / clippy_lints / src / casts / cast_possible_truncation.rs
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::is_isize_or_usize;
5 use rustc_hir::{BinOpKind, Expr, ExprKind};
6 use rustc_lint::LateContext;
7 use rustc_middle::ty::{self, FloatTy, Ty};
8
9 use super::{utils, CAST_POSSIBLE_TRUNCATION};
10
11 fn constant_int(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<u128> {
12     if let Some((Constant::Int(c), _)) = constant(cx, cx.typeck_results(), expr) {
13         Some(c)
14     } else {
15         None
16     }
17 }
18
19 fn get_constant_bits(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<u64> {
20     constant_int(cx, expr).map(|c| u64::from(128 - c.leading_zeros()))
21 }
22
23 fn apply_reductions(cx: &LateContext<'_>, nbits: u64, expr: &Expr<'_>, signed: bool) -> u64 {
24     match expr_or_init(cx, expr).kind {
25         ExprKind::Cast(inner, _) => apply_reductions(cx, nbits, inner, signed),
26         ExprKind::Block(block, _) => block.expr.map_or(nbits, |e| apply_reductions(cx, nbits, e, signed)),
27         ExprKind::Binary(op, left, right) => match op.node {
28             BinOpKind::Div => {
29                 apply_reductions(cx, nbits, left, signed)
30                     - (if signed {
31                         0 // let's be conservative here
32                     } else {
33                         // by dividing by 1, we remove 0 bits, etc.
34                         get_constant_bits(cx, right).map_or(0, |b| b.saturating_sub(1))
35                     })
36             },
37             BinOpKind::Rem | BinOpKind::BitAnd => get_constant_bits(cx, right)
38                 .unwrap_or(u64::max_value())
39                 .min(apply_reductions(cx, nbits, left, signed)),
40             BinOpKind::Shr => {
41                 apply_reductions(cx, nbits, left, signed)
42                     - constant_int(cx, right).map_or(0, |s| u64::try_from(s).expect("shift too high"))
43             },
44             _ => nbits,
45         },
46         ExprKind::MethodCall(method, [left, right], _) => {
47             if signed {
48                 return nbits;
49             }
50             let max_bits = if method.ident.as_str() == "min" {
51                 get_constant_bits(cx, right)
52             } else {
53                 None
54             };
55             apply_reductions(cx, nbits, left, signed).min(max_bits.unwrap_or(u64::max_value()))
56         },
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);
62                 }
63             }
64             nbits
65         },
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
69             } else {
70                 nbits
71             }
72         },
73         _ => nbits,
74     }
75 }
76
77 pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>) {
78     let msg = match (cast_from.is_integral(), cast_to.is_integral()) {
79         (true, true) => {
80             let from_nbits = apply_reductions(
81                 cx,
82                 utils::int_ty_to_nbits(cast_from, cx.tcx),
83                 cast_expr,
84                 cast_from.is_signed(),
85             );
86             let to_nbits = utils::int_ty_to_nbits(cast_to, cx.tcx);
87
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, ""),
90                 (true, false) => (
91                     to_nbits <= 32,
92                     if to_nbits == 32 {
93                         " on targets with 64-bit wide pointers"
94                     } else {
95                         ""
96                     },
97                 ),
98                 (false, true) => (from_nbits == 64, " on targets with 32-bit wide pointers"),
99             };
100
101             if !should_lint {
102                 return;
103             }
104
105             format!(
106                 "casting `{}` to `{}` may truncate the value{}",
107                 cast_from, cast_to, suffix,
108             )
109         },
110
111         (false, true) => {
112             format!("casting `{}` to `{}` may truncate the value", cast_from, cast_to)
113         },
114
115         (_, _) => {
116             if matches!(cast_from.kind(), &ty::Float(FloatTy::F64))
117                 && matches!(cast_to.kind(), &ty::Float(FloatTy::F32))
118             {
119                 "casting `f64` to `f32` may truncate the value".to_string()
120             } else {
121                 return;
122             }
123         },
124     };
125
126     span_lint(cx, CAST_POSSIBLE_TRUNCATION, expr.span, &msg);
127 }