1 use clippy_utils::diagnostics::span_lint_and_sugg;
2 use clippy_utils::is_qpath_def_path;
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_middle::ty::layout::LayoutOf;
10
check( cx: &LateContext<'_>, expr: &hir::Expr<'_>, arith_lhs: &hir::Expr<'_>, arith_rhs: &hir::Expr<'_>, unwrap_arg: &hir::Expr<'_>, arith: &str, )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
is_min_or_max<'tcx>(cx: &LateContext<'tcx>, expr: &hir::Expr<'_>) -> Option<MinMax>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 is_qpath_def_path(cx, path, expr.hir_id, &["core", &ty_str, "MAX"][..]) {
98 return Some(MinMax::Max);
99 }
100
101 if is_qpath_def_path(cx, path, expr.hir_id, &["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
lit_sign(expr: &hir::Expr<'_>) -> Option<Sign>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 }
165