Skip to content

Commit 583af98

Browse files
committed
recognize x.saturating_sub(y).unwrap_or_default() as well
1 parent 06a3826 commit 583af98

File tree

5 files changed

+81
-14
lines changed

5 files changed

+81
-14
lines changed

clippy_lints/src/methods/manual_saturating_arithmetic.rs

Lines changed: 50 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,19 @@ use clippy_utils::source::snippet_with_applicability;
33
use clippy_utils::{path_res, sym};
44
use rustc_ast::ast;
55
use rustc_errors::Applicability;
6-
use rustc_hir as hir;
76
use rustc_hir::def::Res;
7+
use rustc_hir::{self as hir, Expr};
88
use rustc_lint::LateContext;
9+
use rustc_middle::ty::Ty;
910
use rustc_middle::ty::layout::LayoutOf;
1011
use rustc_span::Symbol;
1112

12-
pub fn check(
13+
pub fn check_unwrap_or(
1314
cx: &LateContext<'_>,
14-
expr: &hir::Expr<'_>,
15-
arith_lhs: &hir::Expr<'_>,
16-
arith_rhs: &hir::Expr<'_>,
17-
unwrap_arg: &hir::Expr<'_>,
15+
expr: &Expr<'_>,
16+
arith_lhs: &Expr<'_>,
17+
arith_rhs: &Expr<'_>,
18+
unwrap_arg: &Expr<'_>,
1819
arith: Symbol,
1920
) {
2021
let ty = cx.typeck_results().expr_ty(arith_lhs);
@@ -30,6 +31,42 @@ pub fn check(
3031
return;
3132
};
3233

34+
check(cx, expr, arith_lhs, arith_rhs, ty, mm, checked_arith);
35+
}
36+
37+
/// Precondition: should be called on `x.saturating_sub(y).unwrap_or_default()`
38+
pub(super) fn check_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+
// See precondition
56+
let checked_arith = CheckedArith::Sub;
57+
58+
check(cx, expr, arith_lhs, arith_rhs, ty, mm, checked_arith);
59+
}
60+
61+
fn check(
62+
cx: &LateContext<'_>,
63+
expr: &Expr<'_>,
64+
arith_lhs: &Expr<'_>,
65+
arith_rhs: &Expr<'_>,
66+
ty: Ty<'_>,
67+
mm: MinMax,
68+
checked_arith: CheckedArith,
69+
) {
3370
if ty.is_signed() {
3471
use self::MinMax::{Max, Min};
3572
use self::Sign::{Neg, Pos};
@@ -39,15 +76,15 @@ pub fn check(
3976
return;
4077
};
4178

42-
match (&checked_arith, sign, mm) {
79+
match (checked_arith, sign, mm) {
4380
(Add, Pos, Max) | (Add, Neg, Min) | (Sub, Neg, Max) | (Sub, Pos, Min) => (),
4481
// "mul" is omitted because lhs can be negative.
4582
_ => return,
4683
}
4784
} else {
4885
use self::MinMax::{Max, Min};
4986
use CheckedArith::{Add, Mul, Sub};
50-
match (mm, &checked_arith) {
87+
match (mm, checked_arith) {
5188
(Max, Add | Mul) | (Min, Sub) => (),
5289
_ => return,
5390
}
@@ -70,6 +107,7 @@ pub fn check(
70107
);
71108
}
72109

110+
#[derive(Clone, Copy)]
73111
enum CheckedArith {
74112
Add,
75113
Sub,
@@ -87,7 +125,7 @@ impl CheckedArith {
87125
Some(res)
88126
}
89127

90-
fn as_saturating(&self) -> &'static str {
128+
fn as_saturating(self) -> &'static str {
91129
match self {
92130
Self::Add => "saturating_add",
93131
Self::Sub => "saturating_sub",
@@ -102,7 +140,7 @@ enum MinMax {
102140
Max,
103141
}
104142

105-
fn is_min_or_max(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> Option<MinMax> {
143+
fn is_min_or_max(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<MinMax> {
106144
// `T::max_value()` `T::min_value()` inherent methods
107145
if let hir::ExprKind::Call(func, []) = &expr.kind
108146
&& let hir::ExprKind::Path(hir::QPath::TypeRelative(_, segment)) = &func.kind
@@ -140,7 +178,7 @@ fn is_min_or_max(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> Option<MinMax> {
140178
(0, if bits == 128 { !0 } else { (1 << bits) - 1 })
141179
};
142180

143-
let check_lit = |expr: &hir::Expr<'_>, check_min: bool| {
181+
let check_lit = |expr: &Expr<'_>, check_min: bool| {
144182
if let hir::ExprKind::Lit(lit) = &expr.kind
145183
&& let ast::LitKind::Int(value, _) = lit.node
146184
{
@@ -175,7 +213,7 @@ enum Sign {
175213
Neg,
176214
}
177215

178-
fn lit_sign(expr: &hir::Expr<'_>) -> Option<Sign> {
216+
fn lit_sign(expr: &Expr<'_>) -> Option<Sign> {
179217
if let hir::ExprKind::Unary(hir::UnOp::Neg, inner) = &expr.kind {
180218
if let hir::ExprKind::Lit(..) = &inner.kind {
181219
return Some(Sign::Neg);

clippy_lints/src/methods/mod.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5488,7 +5488,7 @@ impl Methods {
54885488
(sym::unwrap_or, [u_arg]) => {
54895489
match method_call(recv) {
54905490
Some((arith @ (sym::checked_add | sym::checked_sub | sym::checked_mul), lhs, [rhs], _, _)) => {
5491-
manual_saturating_arithmetic::check(cx, expr, lhs, rhs, u_arg, arith);
5491+
manual_saturating_arithmetic::check_unwrap_or(cx, expr, lhs, rhs, u_arg, arith);
54925492
},
54935493
Some((sym::map, m_recv, [m_arg], span, _)) => {
54945494
option_map_unwrap_or::check(cx, expr, m_recv, m_arg, recv, u_arg, span, self.msrv);
@@ -5502,6 +5502,9 @@ impl Methods {
55025502
},
55035503
(sym::unwrap_or_default, []) => {
55045504
match method_call(recv) {
5505+
Some((sym::checked_sub, lhs, [rhs], _, _)) => {
5506+
manual_saturating_arithmetic::check_unwrap_or_default(cx, expr, lhs, rhs);
5507+
},
55055508
Some((sym::map, m_recv, [arg], span, _)) => {
55065509
manual_is_variant_and::check(cx, expr, m_recv, arg, span, self.msrv);
55075510
},

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)