Skip to content

Commit b412ecf

Browse files
committed
new lint: manual_range_pattern
1 parent 3217f8a commit b412ecf

File tree

6 files changed

+132
-0
lines changed

6 files changed

+132
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4923,6 +4923,7 @@ Released 2018-09-13
49234923
[`manual_non_exhaustive`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_non_exhaustive
49244924
[`manual_ok_or`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_ok_or
49254925
[`manual_range_contains`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_range_contains
4926+
[`manual_range_pattern`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_range_pattern
49264927
[`manual_rem_euclid`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_rem_euclid
49274928
[`manual_retain`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_retain
49284929
[`manual_saturating_arithmetic`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_saturating_arithmetic

clippy_lints/src/declared_lints.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
277277
crate::manual_let_else::MANUAL_LET_ELSE_INFO,
278278
crate::manual_main_separator_str::MANUAL_MAIN_SEPARATOR_STR_INFO,
279279
crate::manual_non_exhaustive::MANUAL_NON_EXHAUSTIVE_INFO,
280+
crate::manual_range_pattern::MANUAL_RANGE_PATTERN_INFO,
280281
crate::manual_rem_euclid::MANUAL_REM_EUCLID_INFO,
281282
crate::manual_retain::MANUAL_RETAIN_INFO,
282283
crate::manual_slice_size_calculation::MANUAL_SLICE_SIZE_CALCULATION_INFO,

clippy_lints/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,7 @@ mod manual_is_ascii_check;
188188
mod manual_let_else;
189189
mod manual_main_separator_str;
190190
mod manual_non_exhaustive;
191+
mod manual_range_pattern;
191192
mod manual_rem_euclid;
192193
mod manual_retain;
193194
mod manual_slice_size_calculation;
@@ -1050,6 +1051,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
10501051
store.register_late_pass(move |_| Box::new(large_stack_frames::LargeStackFrames::new(stack_size_threshold)));
10511052
store.register_late_pass(|_| Box::new(single_range_in_vec_init::SingleRangeInVecInit));
10521053
store.register_late_pass(|_| Box::new(incorrect_impls::IncorrectImpls));
1054+
store.register_late_pass(|_| Box::new(manual_range_pattern::ManualRangePattern));
10531055
// add lints here, do not remove this comment, it's used in `new_lint`
10541056
}
10551057

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
use clippy_utils::diagnostics::span_lint_and_sugg;
2+
use rustc_ast::LitKind;
3+
use rustc_data_structures::fx::FxHashSet;
4+
use rustc_errors::Applicability;
5+
use rustc_hir::ExprKind;
6+
use rustc_hir::PatKind;
7+
use rustc_lint::{LateContext, LateLintPass};
8+
use rustc_session::{declare_lint_pass, declare_tool_lint};
9+
10+
declare_clippy_lint! {
11+
/// ### What it does
12+
/// Looks for combined OR patterns that are all contained in a specific range,
13+
/// e.g. `6 | 4 | 5 | 9 | 7 | 8` can be rewritten as `4..=9`.
14+
///
15+
/// ### Why is this bad?
16+
/// Using an explicit range is more concise and easier to read.
17+
///
18+
/// ### Example
19+
/// ```rust
20+
/// let x = 6;
21+
/// let foo = matches!(x, 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10);
22+
/// ```
23+
/// Use instead:
24+
/// ```rust
25+
/// let x = 6;
26+
/// let foo = matches!(x, 1..=10);
27+
/// ```
28+
#[clippy::version = "1.72.0"]
29+
pub MANUAL_RANGE_PATTERN,
30+
complexity,
31+
"manually writing range patterns using a combined OR pattern (`|`)"
32+
}
33+
declare_lint_pass!(ManualRangePattern => [MANUAL_RANGE_PATTERN]);
34+
35+
impl LateLintPass<'_> for ManualRangePattern {
36+
fn check_pat(&mut self, cx: &LateContext<'_>, pat: &'_ rustc_hir::Pat<'_>) {
37+
if pat.span.from_expansion() {
38+
return;
39+
}
40+
41+
if let PatKind::Or(pats) = pat.kind {
42+
let mut min = u128::MAX;
43+
let mut max = 0;
44+
let mut numbers_found = FxHashSet::default();
45+
46+
for pat in pats {
47+
if let PatKind::Lit(lit) = pat.kind
48+
&& let ExprKind::Lit(lit) = lit.kind
49+
&& let LitKind::Int(num, _) = lit.node
50+
{
51+
numbers_found.insert(num);
52+
53+
min = min.min(num);
54+
max = max.max(num);
55+
} else {
56+
return;
57+
}
58+
}
59+
60+
// a pattern like 1 | 2 seems fine, lint if there are at least 3 alternatives
61+
if numbers_found.len() >= 3 {
62+
let contains_whole_range = (max - min == numbers_found.len() as u128 - 1)
63+
&& (min..=max).all(|num| numbers_found.contains(&num));
64+
65+
if contains_whole_range {
66+
span_lint_and_sugg(
67+
cx,
68+
MANUAL_RANGE_PATTERN,
69+
pat.span,
70+
"this OR pattern can be rewritten using a range",
71+
"try",
72+
format!("{}..={}", min, max),
73+
Applicability::MachineApplicable,
74+
);
75+
}
76+
}
77+
}
78+
}
79+
}

tests/ui/manual_range_pattern.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
#![allow(unused)]
2+
#![warn(clippy::manual_range_pattern)]
3+
4+
fn main() {
5+
let f = 6;
6+
7+
let _ = matches!(f, 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10);
8+
let _ = matches!(f, 4 | 2 | 3 | 1 | 5 | 6 | 9 | 7 | 8 | 10);
9+
let _ = matches!(f, 4 | 2 | 3 | 1 | 5 | 6 | 9 | 8 | 10); // 7 is missing
10+
let _ = matches!(f, | 4);
11+
let _ = matches!(f, 4 | 5);
12+
let _ = matches!(f, 1 | 2147483647);
13+
let _ = matches!(f, 0 | 2147483647);
14+
let _ = matches!(f, -2147483647 | 2147483647);
15+
#[allow(clippy::match_like_matches_macro)]
16+
let _ = match f {
17+
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 => true,
18+
_ => false,
19+
};
20+
21+
macro_rules! mac {
22+
($e:expr) => {
23+
matches!($e, 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10)
24+
};
25+
}
26+
mac!(f);
27+
}

tests/ui/manual_range_pattern.stderr

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
error: this OR pattern can be rewritten using a range
2+
--> $DIR/manual_range_pattern.rs:7:25
3+
|
4+
LL | let _ = matches!(f, 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10);
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `1..=10`
6+
|
7+
= note: `-D clippy::manual-range-pattern` implied by `-D warnings`
8+
9+
error: this OR pattern can be rewritten using a range
10+
--> $DIR/manual_range_pattern.rs:8:25
11+
|
12+
LL | let _ = matches!(f, 4 | 2 | 3 | 1 | 5 | 6 | 9 | 7 | 8 | 10);
13+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `1..=10`
14+
15+
error: this OR pattern can be rewritten using a range
16+
--> $DIR/manual_range_pattern.rs:17:9
17+
|
18+
LL | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 => true,
19+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `1..=10`
20+
21+
error: aborting due to 3 previous errors
22+

0 commit comments

Comments
 (0)