Skip to content

Commit 54bf6f4

Browse files
committed
Add checked_arith_unwrap_or lint
1 parent 11da8c1 commit 54bf6f4

File tree

9 files changed

+478
-2
lines changed

9 files changed

+478
-2
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -925,6 +925,7 @@ Released 2018-09-13
925925
[`char_lit_as_u8`]: https://rust-lang.github.io/rust-clippy/master/index.html#char_lit_as_u8
926926
[`chars_last_cmp`]: https://rust-lang.github.io/rust-clippy/master/index.html#chars_last_cmp
927927
[`chars_next_cmp`]: https://rust-lang.github.io/rust-clippy/master/index.html#chars_next_cmp
928+
[`checked_arith_unwrap_or`]: https://rust-lang.github.io/rust-clippy/master/index.html#checked_arith_unwrap_or
928929
[`checked_conversions`]: https://rust-lang.github.io/rust-clippy/master/index.html#checked_conversions
929930
[`clone_double_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#clone_double_ref
930931
[`clone_on_copy`]: https://rust-lang.github.io/rust-clippy/master/index.html#clone_on_copy

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
A collection of lints to catch common mistakes and improve your [Rust](https://github.com/rust-lang/rust) code.
88

9-
[There are 312 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html)
9+
[There are 313 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html)
1010

1111
We have a bunch of lint categories to allow you to choose how much Clippy is supposed to ~~annoy~~ help you:
1212

clippy_lints/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -783,6 +783,7 @@ pub fn register_plugins(reg: &mut rustc_driver::plugin::Registry<'_>, conf: &Con
783783
mem_replace::MEM_REPLACE_OPTION_WITH_NONE,
784784
methods::CHARS_LAST_CMP,
785785
methods::CHARS_NEXT_CMP,
786+
methods::CHECKED_ARITH_UNWRAP_OR,
786787
methods::CLONE_DOUBLE_REF,
787788
methods::CLONE_ON_COPY,
788789
methods::EXPECT_FUN_CALL,
@@ -955,6 +956,7 @@ pub fn register_plugins(reg: &mut rustc_driver::plugin::Registry<'_>, conf: &Con
955956
matches::SINGLE_MATCH,
956957
mem_replace::MEM_REPLACE_OPTION_WITH_NONE,
957958
methods::CHARS_LAST_CMP,
959+
methods::CHECKED_ARITH_UNWRAP_OR,
958960
methods::INTO_ITER_ON_REF,
959961
methods::ITER_CLONED_COLLECT,
960962
methods::ITER_SKIP_NEXT,
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
use crate::utils::{match_qpath, snippet_with_applicability, span_lint_and_sugg};
2+
use if_chain::if_chain;
3+
use rustc::hir;
4+
use rustc::lint::LateContext;
5+
use rustc_errors::Applicability;
6+
use rustc_target::abi::LayoutOf;
7+
use syntax::ast;
8+
9+
pub fn lint(cx: &LateContext<'_, '_>, expr: &hir::Expr, args: &[&[hir::Expr]], arith: &str) {
10+
let unwrap_arg = &args[0][1];
11+
let arith_lhs = &args[1][0];
12+
let arith_rhs = &args[1][1];
13+
14+
let ty = cx.tables.expr_ty(arith_lhs);
15+
if !ty.is_integral() {
16+
return;
17+
}
18+
19+
let mm = if let Some(mm) = is_min_or_max(cx, unwrap_arg) {
20+
mm
21+
} else {
22+
return;
23+
};
24+
25+
if ty.is_signed() {
26+
use self::{MinMax::*, Sign::*};
27+
28+
let sign = if let Some(sign) = lit_sign(arith_rhs) {
29+
sign
30+
} else {
31+
return;
32+
};
33+
34+
match (arith, sign, mm) {
35+
("add", Pos, Max) | ("add", Neg, Min) | ("sub", Neg, Max) | ("sub", Pos, Min) => (),
36+
// "mul" is omitted because lhs can be negative.
37+
_ => return,
38+
}
39+
40+
let mut applicability = Applicability::MachineApplicable;
41+
span_lint_and_sugg(
42+
cx,
43+
super::CHECKED_ARITH_UNWRAP_OR,
44+
expr.span,
45+
"manual saturating arithmetic",
46+
&format!("try using `saturating_{}`", arith),
47+
format!(
48+
"{}.saturating_{}({})",
49+
snippet_with_applicability(cx, arith_lhs.span, "..", &mut applicability),
50+
arith,
51+
snippet_with_applicability(cx, arith_rhs.span, "..", &mut applicability),
52+
),
53+
applicability,
54+
);
55+
} else {
56+
match (mm, arith) {
57+
(MinMax::Max, "add") | (MinMax::Max, "mul") | (MinMax::Min, "sub") => (),
58+
_ => return,
59+
}
60+
61+
let mut applicability = Applicability::MachineApplicable;
62+
span_lint_and_sugg(
63+
cx,
64+
super::CHECKED_ARITH_UNWRAP_OR,
65+
expr.span,
66+
"manual saturating arithmetic",
67+
&format!("try using `saturating_{}`", arith),
68+
format!(
69+
"{}.saturating_{}({})",
70+
snippet_with_applicability(cx, arith_lhs.span, "..", &mut applicability),
71+
arith,
72+
snippet_with_applicability(cx, arith_rhs.span, "..", &mut applicability),
73+
),
74+
applicability,
75+
);
76+
}
77+
}
78+
79+
#[derive(PartialEq, Eq)]
80+
enum MinMax {
81+
Min,
82+
Max,
83+
}
84+
85+
fn is_min_or_max<'tcx>(cx: &LateContext<'_, 'tcx>, expr: &hir::Expr) -> Option<MinMax> {
86+
// `T::max_value()` `T::min_value()` inherent methods
87+
if_chain! {
88+
if let hir::ExprKind::Call(func, args) = &expr.node;
89+
if args.is_empty();
90+
if let hir::ExprKind::Path(path) = &func.node;
91+
if let hir::QPath::TypeRelative(_, segment) = path;
92+
then {
93+
match &*segment.ident.as_str() {
94+
"max_value" => return Some(MinMax::Max),
95+
"min_value" => return Some(MinMax::Min),
96+
_ => {}
97+
}
98+
}
99+
}
100+
101+
let ty = cx.tables.expr_ty(expr);
102+
let ty_str = ty.to_string();
103+
104+
// `std::T::MAX` `std::T::MIN` constants
105+
if let hir::ExprKind::Path(path) = &expr.node {
106+
if match_qpath(path, &["core", &ty_str, "MAX"][..]) {
107+
return Some(MinMax::Max);
108+
}
109+
110+
if match_qpath(path, &["core", &ty_str, "MIN"][..]) {
111+
return Some(MinMax::Min);
112+
}
113+
}
114+
115+
// Literals
116+
let bits = cx.layout_of(ty).unwrap().size.bits();
117+
let (minval, maxval): (u128, u128) = if ty.is_signed() {
118+
let minval = 1 << (bits - 1);
119+
let mut maxval = !(1 << (bits - 1));
120+
if bits != 128 {
121+
maxval &= (1 << bits) - 1;
122+
}
123+
(minval, maxval)
124+
} else {
125+
(0, if bits == 128 { !0 } else { (1 << bits) - 1 })
126+
};
127+
128+
let check_lit = |expr: &hir::Expr, check_min: bool| {
129+
if let hir::ExprKind::Lit(lit) = &expr.node {
130+
if let ast::LitKind::Int(value, _) = lit.node {
131+
if value == maxval {
132+
return Some(MinMax::Max);
133+
}
134+
135+
if check_min && value == minval {
136+
return Some(MinMax::Min);
137+
}
138+
}
139+
}
140+
141+
None
142+
};
143+
144+
if let r @ Some(_) = check_lit(expr, !ty.is_signed()) {
145+
return r;
146+
}
147+
148+
if ty.is_signed() {
149+
if let hir::ExprKind::Unary(hir::UnNeg, val) = &expr.node {
150+
return check_lit(val, true);
151+
}
152+
}
153+
154+
None
155+
}
156+
157+
#[derive(PartialEq, Eq)]
158+
enum Sign {
159+
Pos,
160+
Neg,
161+
}
162+
163+
fn lit_sign(expr: &hir::Expr) -> Option<Sign> {
164+
if let hir::ExprKind::Unary(hir::UnNeg, inner) = &expr.node {
165+
if let hir::ExprKind::Lit(..) = &inner.node {
166+
return Some(Sign::Neg);
167+
}
168+
} else if let hir::ExprKind::Lit(..) = &expr.node {
169+
return Some(Sign::Pos);
170+
}
171+
172+
None
173+
}

clippy_lints/src/methods/mod.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
mod checked_arith_unwrap_or;
12
mod option_map_unwrap_or;
23
mod unnecessary_filter_map;
34

@@ -983,6 +984,31 @@ declare_clippy_lint! {
983984
"`MaybeUninit::uninit().assume_init()`"
984985
}
985986

987+
declare_clippy_lint! {
988+
/// **What it does:** Checks for `.checked_add/sub(x).unwrap_or(MAX/MIN)`.
989+
///
990+
/// **Why is this bad?** These can be written simply with `saturating_add/sub` methods.
991+
///
992+
/// **Example:**
993+
///
994+
/// ```rust
995+
/// let x: u32 = 100;
996+
///
997+
/// let add = x.checked_add(y).unwrap_or(u32::max_value());
998+
/// let sub = x.checked_sub(y).unwrap_or(u32::min_value());
999+
/// ```
1000+
///
1001+
/// can be written using dedicated methods for saturating addition/subtraction as:
1002+
///
1003+
/// ```rust
1004+
/// let add = x.saturating_add(y);
1005+
/// let sub = x.saturating_sub(y);
1006+
/// ```
1007+
pub CHECKED_ARITH_UNWRAP_OR,
1008+
style,
1009+
"`.chcked_add/sub(x).unwrap_or(MAX/MIN)`"
1010+
}
1011+
9861012
declare_lint_pass!(Methods => [
9871013
OPTION_UNWRAP_USED,
9881014
RESULT_UNWRAP_USED,
@@ -1024,6 +1050,7 @@ declare_lint_pass!(Methods => [
10241050
INTO_ITER_ON_REF,
10251051
SUSPICIOUS_MAP,
10261052
UNINIT_ASSUMED_INIT,
1053+
CHECKED_ARITH_UNWRAP_OR,
10271054
]);
10281055

10291056
impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Methods {
@@ -1072,6 +1099,11 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Methods {
10721099
["filter_map", ..] => unnecessary_filter_map::lint(cx, expr, arg_lists[0]),
10731100
["count", "map"] => lint_suspicious_map(cx, expr),
10741101
["assume_init"] => lint_maybe_uninit(cx, &arg_lists[0][0], expr),
1102+
["unwrap_or", arith @ "checked_add"]
1103+
| ["unwrap_or", arith @ "checked_sub"]
1104+
| ["unwrap_or", arith @ "checked_mul"] => {
1105+
checked_arith_unwrap_or::lint(cx, expr, &arg_lists, &arith["checked_".len()..])
1106+
},
10751107
_ => {},
10761108
}
10771109

src/lintlist/mod.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ pub use lint::Lint;
66
pub use lint::LINT_LEVELS;
77

88
// begin lint list, do not remove this comment, it’s used in `update_lints`
9-
pub const ALL_LINTS: [Lint; 312] = [
9+
pub const ALL_LINTS: [Lint; 313] = [
1010
Lint {
1111
name: "absurd_extreme_comparisons",
1212
group: "correctness",
@@ -189,6 +189,13 @@ pub const ALL_LINTS: [Lint; 312] = [
189189
deprecation: None,
190190
module: "methods",
191191
},
192+
Lint {
193+
name: "checked_arith_unwrap_or",
194+
group: "style",
195+
desc: "`.chcked_add/sub(x).unwrap_or(MAX/MIN)`",
196+
deprecation: None,
197+
module: "methods",
198+
},
192199
Lint {
193200
name: "checked_conversions",
194201
group: "pedantic",
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// run-rustfix
2+
3+
#[allow(unused_imports)] // they become unnecessary after fix
4+
use std::{i128, i32, u128, u32};
5+
6+
fn main() {
7+
let _ = 1u32.saturating_add(1);
8+
let _ = 1u32.saturating_add(1);
9+
let _ = 1u8.saturating_add(1);
10+
let _ = 1u128.saturating_add(1);
11+
let _ = 1u32.checked_add(1).unwrap_or(1234); // ok
12+
let _ = 1u8.checked_add(1).unwrap_or(0); // ok
13+
let _ = 1u32.saturating_mul(1);
14+
15+
let _ = 1u32.saturating_sub(1);
16+
let _ = 1u32.saturating_sub(1);
17+
let _ = 1u8.saturating_sub(1);
18+
let _ = 1u32.checked_sub(1).unwrap_or(1234); // ok
19+
let _ = 1u8.checked_sub(1).unwrap_or(255); // ok
20+
21+
let _ = 1i32.saturating_add(1);
22+
let _ = 1i32.saturating_add(1);
23+
let _ = 1i8.saturating_add(1);
24+
let _ = 1i128.saturating_add(1);
25+
let _ = 1i32.saturating_add(-1);
26+
let _ = 1i32.saturating_add(-1);
27+
let _ = 1i8.saturating_add(-1);
28+
let _ = 1i128.saturating_add(-1);
29+
let _ = 1i32.checked_add(1).unwrap_or(1234); // ok
30+
let _ = 1i8.checked_add(1).unwrap_or(-128); // ok
31+
let _ = 1i8.checked_add(-1).unwrap_or(127); // ok
32+
33+
let _ = 1i32.saturating_sub(1);
34+
let _ = 1i32.saturating_sub(1);
35+
let _ = 1i8.saturating_sub(1);
36+
let _ = 1i128.saturating_sub(1);
37+
let _ = 1i32.saturating_sub(-1);
38+
let _ = 1i32.saturating_sub(-1);
39+
let _ = 1i8.saturating_sub(-1);
40+
let _ = 1i128.saturating_sub(-1);
41+
let _ = 1i32.checked_sub(1).unwrap_or(1234); // ok
42+
let _ = 1i8.checked_sub(1).unwrap_or(127); // ok
43+
let _ = 1i8.checked_sub(-1).unwrap_or(-128); // ok
44+
}

tests/ui/checked_arith_unwrap_or.rs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// run-rustfix
2+
3+
#[allow(unused_imports)] // they become unnecessary after fix
4+
use std::{i128, i32, u128, u32};
5+
6+
fn main() {
7+
let _ = 1u32.checked_add(1).unwrap_or(u32::max_value());
8+
let _ = 1u32.checked_add(1).unwrap_or(u32::MAX);
9+
let _ = 1u8.checked_add(1).unwrap_or(255);
10+
let _ = 1u128
11+
.checked_add(1)
12+
.unwrap_or(340_282_366_920_938_463_463_374_607_431_768_211_455);
13+
let _ = 1u32.checked_add(1).unwrap_or(1234); // ok
14+
let _ = 1u8.checked_add(1).unwrap_or(0); // ok
15+
let _ = 1u32.checked_mul(1).unwrap_or(u32::MAX);
16+
17+
let _ = 1u32.checked_sub(1).unwrap_or(u32::min_value());
18+
let _ = 1u32.checked_sub(1).unwrap_or(u32::MIN);
19+
let _ = 1u8.checked_sub(1).unwrap_or(0);
20+
let _ = 1u32.checked_sub(1).unwrap_or(1234); // ok
21+
let _ = 1u8.checked_sub(1).unwrap_or(255); // ok
22+
23+
let _ = 1i32.checked_add(1).unwrap_or(i32::max_value());
24+
let _ = 1i32.checked_add(1).unwrap_or(i32::MAX);
25+
let _ = 1i8.checked_add(1).unwrap_or(127);
26+
let _ = 1i128
27+
.checked_add(1)
28+
.unwrap_or(170_141_183_460_469_231_731_687_303_715_884_105_727);
29+
let _ = 1i32.checked_add(-1).unwrap_or(i32::min_value());
30+
let _ = 1i32.checked_add(-1).unwrap_or(i32::MIN);
31+
let _ = 1i8.checked_add(-1).unwrap_or(-128);
32+
let _ = 1i128
33+
.checked_add(-1)
34+
.unwrap_or(-170_141_183_460_469_231_731_687_303_715_884_105_728);
35+
let _ = 1i32.checked_add(1).unwrap_or(1234); // ok
36+
let _ = 1i8.checked_add(1).unwrap_or(-128); // ok
37+
let _ = 1i8.checked_add(-1).unwrap_or(127); // ok
38+
39+
let _ = 1i32.checked_sub(1).unwrap_or(i32::min_value());
40+
let _ = 1i32.checked_sub(1).unwrap_or(i32::MIN);
41+
let _ = 1i8.checked_sub(1).unwrap_or(-128);
42+
let _ = 1i128
43+
.checked_sub(1)
44+
.unwrap_or(-170_141_183_460_469_231_731_687_303_715_884_105_728);
45+
let _ = 1i32.checked_sub(-1).unwrap_or(i32::max_value());
46+
let _ = 1i32.checked_sub(-1).unwrap_or(i32::MAX);
47+
let _ = 1i8.checked_sub(-1).unwrap_or(127);
48+
let _ = 1i128
49+
.checked_sub(-1)
50+
.unwrap_or(170_141_183_460_469_231_731_687_303_715_884_105_727);
51+
let _ = 1i32.checked_sub(1).unwrap_or(1234); // ok
52+
let _ = 1i8.checked_sub(1).unwrap_or(127); // ok
53+
let _ = 1i8.checked_sub(-1).unwrap_or(-128); // ok
54+
}

0 commit comments

Comments
 (0)