]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/methods/manual_saturating_arithmetic.rs
A new lint for shared code in if blocks
[rust.git] / clippy_lints / src / methods / manual_saturating_arithmetic.rs
1 use clippy_utils::diagnostics::span_lint_and_sugg;
2 use clippy_utils::match_qpath;
3 use clippy_utils::source::snippet_with_applicability;
4 use if_chain::if_chain;
5 use rustc_ast::ast;
6 use rustc_errors::Applicability;
7 use rustc_hir as hir;
8 use rustc_lint::LateContext;
9 use rustc_target::abi::LayoutOf;
10
11 pub fn check(
12     cx: &LateContext<'_>,
13     expr: &hir::Expr<'_>,
14     arith_lhs: &hir::Expr<'_>,
15     arith_rhs: &hir::Expr<'_>,
16     unwrap_arg: &hir::Expr<'_>,
17     arith: &str,
18 ) {
19     let ty = cx.typeck_results().expr_ty(arith_lhs);
20     if !ty.is_integral() {
21         return;
22     }
23
24     let mm = if let Some(mm) = is_min_or_max(cx, unwrap_arg) {
25         mm
26     } else {
27         return;
28     };
29
30     if ty.is_signed() {
31         use self::{
32             MinMax::{Max, Min},
33             Sign::{Neg, Pos},
34         };
35
36         let sign = if let Some(sign) = lit_sign(arith_rhs) {
37             sign
38         } else {
39             return;
40         };
41
42         match (arith, sign, mm) {
43             ("add", Pos, Max) | ("add", Neg, Min) | ("sub", Neg, Max) | ("sub", Pos, Min) => (),
44             // "mul" is omitted because lhs can be negative.
45             _ => return,
46         }
47     } else {
48         match (mm, arith) {
49             (MinMax::Max, "add" | "mul") | (MinMax::Min, "sub") => (),
50             _ => return,
51         }
52     }
53
54     let mut applicability = Applicability::MachineApplicable;
55     span_lint_and_sugg(
56         cx,
57         super::MANUAL_SATURATING_ARITHMETIC,
58         expr.span,
59         "manual saturating arithmetic",
60         &format!("try using `saturating_{}`", arith),
61         format!(
62             "{}.saturating_{}({})",
63             snippet_with_applicability(cx, arith_lhs.span, "..", &mut applicability),
64             arith,
65             snippet_with_applicability(cx, arith_rhs.span, "..", &mut applicability),
66         ),
67         applicability,
68     );
69 }
70
71 #[derive(PartialEq, Eq)]
72 enum MinMax {
73     Min,
74     Max,
75 }
76
77 fn is_min_or_max<'tcx>(cx: &LateContext<'tcx>, expr: &hir::Expr<'_>) -> Option<MinMax> {
78     // `T::max_value()` `T::min_value()` inherent methods
79     if_chain! {
80         if let hir::ExprKind::Call(func, args) = &expr.kind;
81         if args.is_empty();
82         if let hir::ExprKind::Path(hir::QPath::TypeRelative(_, segment)) = &func.kind;
83         then {
84             match &*segment.ident.as_str() {
85                 "max_value" => return Some(MinMax::Max),
86                 "min_value" => return Some(MinMax::Min),
87                 _ => {}
88             }
89         }
90     }
91
92     let ty = cx.typeck_results().expr_ty(expr);
93     let ty_str = ty.to_string();
94
95     // `std::T::MAX` `std::T::MIN` constants
96     if let hir::ExprKind::Path(path) = &expr.kind {
97         if match_qpath(path, &["core", &ty_str, "MAX"][..]) {
98             return Some(MinMax::Max);
99         }
100
101         if match_qpath(path, &["core", &ty_str, "MIN"][..]) {
102             return Some(MinMax::Min);
103         }
104     }
105
106     // Literals
107     let bits = cx.layout_of(ty).unwrap().size.bits();
108     let (minval, maxval): (u128, u128) = if ty.is_signed() {
109         let minval = 1 << (bits - 1);
110         let mut maxval = !(1 << (bits - 1));
111         if bits != 128 {
112             maxval &= (1 << bits) - 1;
113         }
114         (minval, maxval)
115     } else {
116         (0, if bits == 128 { !0 } else { (1 << bits) - 1 })
117     };
118
119     let check_lit = |expr: &hir::Expr<'_>, check_min: bool| {
120         if let hir::ExprKind::Lit(lit) = &expr.kind {
121             if let ast::LitKind::Int(value, _) = lit.node {
122                 if value == maxval {
123                     return Some(MinMax::Max);
124                 }
125
126                 if check_min && value == minval {
127                     return Some(MinMax::Min);
128                 }
129             }
130         }
131
132         None
133     };
134
135     if let r @ Some(_) = check_lit(expr, !ty.is_signed()) {
136         return r;
137     }
138
139     if ty.is_signed() {
140         if let hir::ExprKind::Unary(hir::UnOp::Neg, val) = &expr.kind {
141             return check_lit(val, true);
142         }
143     }
144
145     None
146 }
147
148 #[derive(PartialEq, Eq)]
149 enum Sign {
150     Pos,
151     Neg,
152 }
153
154 fn lit_sign(expr: &hir::Expr<'_>) -> Option<Sign> {
155     if let hir::ExprKind::Unary(hir::UnOp::Neg, inner) = &expr.kind {
156         if let hir::ExprKind::Lit(..) = &inner.kind {
157             return Some(Sign::Neg);
158         }
159     } else if let hir::ExprKind::Lit(..) = &expr.kind {
160         return Some(Sign::Pos);
161     }
162
163     None
164 }