Skip to content

Commit 961ea1c

Browse files
authored
feat(manual_saturating_arithmetic): lint x.checked_sub(y).unwrap_or_default() (#15845)
Resolves #15655 changelog: [`manual_saturating_arithmetic`]: lint `x.checked_sub(y).unwrap_or_default()`
2 parents 06ded88 + 95834c9 commit 961ea1c

File tree

5 files changed

+119
-28
lines changed

5 files changed

+119
-28
lines changed

clippy_lints/src/methods/manual_saturating_arithmetic.rs

Lines changed: 88 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,20 @@ use clippy_utils::source::snippet_with_applicability;
44
use clippy_utils::sym;
55
use rustc_ast::ast;
66
use rustc_errors::Applicability;
7-
use rustc_hir as hir;
87
use rustc_hir::def::Res;
8+
use rustc_hir::{self as hir, Expr};
99
use rustc_lint::LateContext;
10+
use rustc_middle::ty::Ty;
1011
use rustc_middle::ty::layout::LayoutOf;
12+
use rustc_span::Symbol;
1113

12-
pub fn check(
14+
pub fn check_unwrap_or(
1315
cx: &LateContext<'_>,
14-
expr: &hir::Expr<'_>,
15-
arith_lhs: &hir::Expr<'_>,
16-
arith_rhs: &hir::Expr<'_>,
17-
unwrap_arg: &hir::Expr<'_>,
18-
arith: &str,
16+
expr: &Expr<'_>,
17+
arith_lhs: &Expr<'_>,
18+
arith_rhs: &Expr<'_>,
19+
unwrap_arg: &Expr<'_>,
20+
arith: Symbol,
1921
) {
2022
let ty = cx.typeck_results().expr_ty(arith_lhs);
2123
if !ty.is_integral() {
@@ -26,49 +28,116 @@ pub fn check(
2628
return;
2729
};
2830

29-
if ty.is_signed() {
30-
use self::MinMax::{Max, Min};
31-
use self::Sign::{Neg, Pos};
31+
let Some(checked_arith) = CheckedArith::new(arith) else {
32+
return;
33+
};
34+
35+
check(cx, expr, arith_lhs, arith_rhs, ty, mm, checked_arith);
36+
}
37+
38+
pub(super) fn check_sub_unwrap_or_default(
39+
cx: &LateContext<'_>,
40+
expr: &Expr<'_>,
41+
arith_lhs: &Expr<'_>,
42+
arith_rhs: &Expr<'_>,
43+
) {
44+
let ty = cx.typeck_results().expr_ty(arith_lhs);
45+
if !ty.is_integral() {
46+
return;
47+
}
48+
49+
let mm = if ty.is_signed() {
50+
return; // iN::default() is 0, which is neither MIN nor MAX
51+
} else {
52+
MinMax::Min // uN::default() is 0, which is also the MIN
53+
};
54+
55+
let checked_arith = CheckedArith::Sub;
3256

57+
check(cx, expr, arith_lhs, arith_rhs, ty, mm, checked_arith);
58+
}
59+
60+
fn check(
61+
cx: &LateContext<'_>,
62+
expr: &Expr<'_>,
63+
arith_lhs: &Expr<'_>,
64+
arith_rhs: &Expr<'_>,
65+
ty: Ty<'_>,
66+
mm: MinMax,
67+
checked_arith: CheckedArith,
68+
) {
69+
use self::MinMax::{Max, Min};
70+
use self::Sign::{Neg, Pos};
71+
use CheckedArith::{Add, Mul, Sub};
72+
73+
if ty.is_signed() {
3374
let Some(sign) = lit_sign(arith_rhs) else {
3475
return;
3576
};
3677

37-
match (arith, sign, mm) {
38-
("add", Pos, Max) | ("add", Neg, Min) | ("sub", Neg, Max) | ("sub", Pos, Min) => (),
78+
match (checked_arith, sign, mm) {
79+
(Add, Pos, Max) | (Add, Neg, Min) | (Sub, Neg, Max) | (Sub, Pos, Min) => (),
3980
// "mul" is omitted because lhs can be negative.
4081
_ => return,
4182
}
4283
} else {
43-
match (mm, arith) {
44-
(MinMax::Max, "add" | "mul") | (MinMax::Min, "sub") => (),
84+
match (mm, checked_arith) {
85+
(Max, Add | Mul) | (Min, Sub) => (),
4586
_ => return,
4687
}
4788
}
4889

4990
let mut applicability = Applicability::MachineApplicable;
91+
let saturating_arith = checked_arith.as_saturating();
5092
span_lint_and_sugg(
5193
cx,
5294
super::MANUAL_SATURATING_ARITHMETIC,
5395
expr.span,
5496
"manual saturating arithmetic",
55-
format!("consider using `saturating_{arith}`"),
97+
format!("consider using `{saturating_arith}`"),
5698
format!(
57-
"{}.saturating_{arith}({})",
99+
"{}.{saturating_arith}({})",
58100
snippet_with_applicability(cx, arith_lhs.span, "..", &mut applicability),
59101
snippet_with_applicability(cx, arith_rhs.span, "..", &mut applicability),
60102
),
61103
applicability,
62104
);
63105
}
64106

107+
#[derive(Clone, Copy)]
108+
enum CheckedArith {
109+
Add,
110+
Sub,
111+
Mul,
112+
}
113+
114+
impl CheckedArith {
115+
fn new(sym: Symbol) -> Option<Self> {
116+
let res = match sym {
117+
sym::checked_add => Self::Add,
118+
sym::checked_sub => Self::Sub,
119+
sym::checked_mul => Self::Mul,
120+
_ => return None,
121+
};
122+
Some(res)
123+
}
124+
125+
fn as_saturating(self) -> &'static str {
126+
match self {
127+
Self::Add => "saturating_add",
128+
Self::Sub => "saturating_sub",
129+
Self::Mul => "saturating_mul",
130+
}
131+
}
132+
}
133+
65134
#[derive(PartialEq, Eq)]
66135
enum MinMax {
67136
Min,
68137
Max,
69138
}
70139

71-
fn is_min_or_max(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> Option<MinMax> {
140+
fn is_min_or_max(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<MinMax> {
72141
// `T::max_value()` `T::min_value()` inherent methods
73142
if let hir::ExprKind::Call(func, []) = &expr.kind
74143
&& let hir::ExprKind::Path(hir::QPath::TypeRelative(_, segment)) = &func.kind
@@ -106,7 +175,7 @@ fn is_min_or_max(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> Option<MinMax> {
106175
(0, if bits == 128 { !0 } else { (1 << bits) - 1 })
107176
};
108177

109-
let check_lit = |expr: &hir::Expr<'_>, check_min: bool| {
178+
let check_lit = |expr: &Expr<'_>, check_min: bool| {
110179
if let hir::ExprKind::Lit(lit) = &expr.kind
111180
&& let ast::LitKind::Int(value, _) = lit.node
112181
{
@@ -141,7 +210,7 @@ enum Sign {
141210
Neg,
142211
}
143212

144-
fn lit_sign(expr: &hir::Expr<'_>) -> Option<Sign> {
213+
fn lit_sign(expr: &Expr<'_>) -> Option<Sign> {
145214
if let hir::ExprKind::Unary(hir::UnOp::Neg, inner) = &expr.kind {
146215
if let hir::ExprKind::Lit(..) = &inner.kind {
147216
return Some(Sign::Neg);

clippy_lints/src/methods/mod.rs

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5604,14 +5604,7 @@ impl Methods {
56045604
(sym::unwrap_or, [u_arg]) => {
56055605
match method_call(recv) {
56065606
Some((arith @ (sym::checked_add | sym::checked_sub | sym::checked_mul), lhs, [rhs], _, _)) => {
5607-
manual_saturating_arithmetic::check(
5608-
cx,
5609-
expr,
5610-
lhs,
5611-
rhs,
5612-
u_arg,
5613-
&arith.as_str()[const { "checked_".len() }..],
5614-
);
5607+
manual_saturating_arithmetic::check_unwrap_or(cx, expr, lhs, rhs, u_arg, arith);
56155608
},
56165609
Some((sym::map, m_recv, [m_arg], span, _)) => {
56175610
option_map_unwrap_or::check(cx, expr, m_recv, m_arg, recv, u_arg, span, self.msrv);
@@ -5632,6 +5625,9 @@ impl Methods {
56325625
},
56335626
(sym::unwrap_or_default, []) => {
56345627
match method_call(recv) {
5628+
Some((sym::checked_sub, lhs, [rhs], _, _)) => {
5629+
manual_saturating_arithmetic::check_sub_unwrap_or_default(cx, expr, lhs, rhs);
5630+
},
56355631
Some((sym::map, m_recv, [arg], span, _)) => {
56365632
manual_is_variant_and::check(cx, expr, m_recv, arg, span, self.msrv);
56375633
},

tests/ui/manual_saturating_arithmetic.fixed

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,3 +58,13 @@ fn main() {
5858
let _ = 1i8.checked_sub(1).unwrap_or(127); // ok
5959
let _ = 1i8.checked_sub(-1).unwrap_or(-128); // ok
6060
}
61+
62+
fn issue15655() {
63+
let _ = 5u32.saturating_sub(1u32); //~ manual_saturating_arithmetic
64+
let _ = 5u32.checked_add(1u32).unwrap_or_default(); // ok
65+
let _ = 5u32.checked_mul(1u32).unwrap_or_default(); // ok
66+
67+
let _ = 5i32.checked_sub(1i32).unwrap_or_default(); // ok
68+
let _ = 5i32.checked_add(1i32).unwrap_or_default(); // ok
69+
let _ = 5i32.checked_mul(1i32).unwrap_or_default(); // ok
70+
}

tests/ui/manual_saturating_arithmetic.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,3 +73,13 @@ fn main() {
7373
let _ = 1i8.checked_sub(1).unwrap_or(127); // ok
7474
let _ = 1i8.checked_sub(-1).unwrap_or(-128); // ok
7575
}
76+
77+
fn issue15655() {
78+
let _ = 5u32.checked_sub(1u32).unwrap_or_default(); //~ manual_saturating_arithmetic
79+
let _ = 5u32.checked_add(1u32).unwrap_or_default(); // ok
80+
let _ = 5u32.checked_mul(1u32).unwrap_or_default(); // ok
81+
82+
let _ = 5i32.checked_sub(1i32).unwrap_or_default(); // ok
83+
let _ = 5i32.checked_add(1i32).unwrap_or_default(); // ok
84+
let _ = 5i32.checked_mul(1i32).unwrap_or_default(); // ok
85+
}

tests/ui/manual_saturating_arithmetic.stderr

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,5 +165,11 @@ LL | | .checked_sub(-1)
165165
LL | | .unwrap_or(170_141_183_460_469_231_731_687_303_715_884_105_727);
166166
| |_______________________________________________________________________^ help: consider using `saturating_sub`: `1i128.saturating_sub(-1)`
167167

168-
error: aborting due to 24 previous errors
168+
error: manual saturating arithmetic
169+
--> tests/ui/manual_saturating_arithmetic.rs:78:13
170+
|
171+
LL | let _ = 5u32.checked_sub(1u32).unwrap_or_default();
172+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `saturating_sub`: `5u32.saturating_sub(1u32)`
173+
174+
error: aborting due to 25 previous errors
169175

0 commit comments

Comments
 (0)