Skip to content

Commit ab67745

Browse files
committed
check for overlapping ranges
1 parent a70cabe commit ab67745

File tree

4 files changed

+81
-9
lines changed

4 files changed

+81
-9
lines changed

clippy_lints/src/manual_range_pattern.rs

Lines changed: 47 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@ use clippy_utils::diagnostics::span_lint_and_sugg;
22
use rustc_ast::LitKind;
33
use rustc_data_structures::fx::FxHashSet;
44
use rustc_errors::Applicability;
5+
use rustc_hir::Expr;
56
use rustc_hir::ExprKind;
67
use rustc_hir::PatKind;
8+
use rustc_hir::RangeEnd;
79
use rustc_lint::{LateContext, LateLintPass};
810
use rustc_session::{declare_lint_pass, declare_tool_lint};
911

@@ -32,6 +34,16 @@ declare_clippy_lint! {
3234
}
3335
declare_lint_pass!(ManualRangePattern => [MANUAL_RANGE_PATTERN]);
3436

37+
fn expr_as_u128(expr: &Expr<'_>) -> Option<u128> {
38+
if let ExprKind::Lit(lit) = expr.kind
39+
&& let LitKind::Int(num, _) = lit.node
40+
{
41+
Some(num)
42+
} else {
43+
None
44+
}
45+
}
46+
3547
impl LateLintPass<'_> for ManualRangePattern {
3648
fn check_pat(&mut self, cx: &LateContext<'_>, pat: &'_ rustc_hir::Pat<'_>) {
3749
if pat.span.from_expansion() {
@@ -42,25 +54,55 @@ impl LateLintPass<'_> for ManualRangePattern {
4254
let mut min = u128::MAX;
4355
let mut max = 0;
4456
let mut numbers_found = FxHashSet::default();
57+
let mut ranges_found = Vec::new();
4558

4659
for pat in pats {
4760
if let PatKind::Lit(lit) = pat.kind
48-
&& let ExprKind::Lit(lit) = lit.kind
49-
&& let LitKind::Int(num, _) = lit.node
61+
&& let Some(num) = expr_as_u128(lit)
5062
{
5163
numbers_found.insert(num);
5264

5365
min = min.min(num);
5466
max = max.max(num);
67+
} else if let PatKind::Range(Some(left), Some(right), end) = pat.kind
68+
&& let Some(left) = expr_as_u128(left)
69+
&& let Some(right) = expr_as_u128(right)
70+
&& right >= left
71+
{
72+
min = min.min(left);
73+
max = max.max(right);
74+
ranges_found.push(left..=match end {
75+
RangeEnd::Included => right,
76+
RangeEnd::Excluded => right - 1,
77+
});
5578
} else {
5679
return;
5780
}
5881
}
5982

6083
// 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));
84+
if pats.len() >= 3 {
85+
let contains_whole_range = 'contains: {
86+
let mut num = min;
87+
while num <= max {
88+
if numbers_found.contains(&num) {
89+
num += 1;
90+
}
91+
// Given a list of (potentially overlapping) ranges like:
92+
// 1..=5, 3..=7, 6..=10
93+
// We want to find the range with the highest end that still contains the current number
94+
else if let Some(range) = ranges_found
95+
.iter()
96+
.filter(|range| range.contains(&num))
97+
.max_by_key(|range| range.end())
98+
{
99+
num = range.end() + 1;
100+
} else {
101+
break 'contains false;
102+
}
103+
}
104+
break 'contains true;
105+
};
64106

65107
if contains_whole_range {
66108
span_lint_and_sugg(

tests/ui/manual_range_pattern.fixed

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
#![allow(unused)]
44
#![warn(clippy::manual_range_pattern)]
5+
#![feature(exclusive_range_pattern)]
56

67
fn main() {
78
let f = 6;
@@ -14,6 +15,11 @@ fn main() {
1415
let _ = matches!(f, 1 | 2147483647);
1516
let _ = matches!(f, 0 | 2147483647);
1617
let _ = matches!(f, -2147483647 | 2147483647);
18+
let _ = matches!(f, 1 | (2..=4));
19+
let _ = matches!(f, 1 | (2..4));
20+
let _ = matches!(f, 1..=48324729);
21+
let _ = matches!(f, 0..=48324730);
22+
let _ = matches!(f, 0..=3);
1723
#[allow(clippy::match_like_matches_macro)]
1824
let _ = match f {
1925
1..=10 => true,

tests/ui/manual_range_pattern.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
#![allow(unused)]
44
#![warn(clippy::manual_range_pattern)]
5+
#![feature(exclusive_range_pattern)]
56

67
fn main() {
78
let f = 6;
@@ -14,6 +15,11 @@ fn main() {
1415
let _ = matches!(f, 1 | 2147483647);
1516
let _ = matches!(f, 0 | 2147483647);
1617
let _ = matches!(f, -2147483647 | 2147483647);
18+
let _ = matches!(f, 1 | (2..=4));
19+
let _ = matches!(f, 1 | (2..4));
20+
let _ = matches!(f, (1..=10) | (2..=13) | (14..=48324728) | 48324729);
21+
let _ = matches!(f, 0 | (1..=10) | 48324730 | (2..=13) | (14..=48324728) | 48324729);
22+
let _ = matches!(f, 0..=1 | 0..=2 | 0..=3);
1723
#[allow(clippy::match_like_matches_macro)]
1824
let _ = match f {
1925
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 => true,

tests/ui/manual_range_pattern.stderr

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,40 @@
11
error: this OR pattern can be rewritten using a range
2-
--> $DIR/manual_range_pattern.rs:9:25
2+
--> $DIR/manual_range_pattern.rs:10:25
33
|
44
LL | let _ = matches!(f, 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10);
55
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `1..=10`
66
|
77
= note: `-D clippy::manual-range-pattern` implied by `-D warnings`
88

99
error: this OR pattern can be rewritten using a range
10-
--> $DIR/manual_range_pattern.rs:10:25
10+
--> $DIR/manual_range_pattern.rs:11:25
1111
|
1212
LL | let _ = matches!(f, 4 | 2 | 3 | 1 | 5 | 6 | 9 | 7 | 8 | 10);
1313
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `1..=10`
1414

1515
error: this OR pattern can be rewritten using a range
16-
--> $DIR/manual_range_pattern.rs:19:9
16+
--> $DIR/manual_range_pattern.rs:20:25
17+
|
18+
LL | let _ = matches!(f, (1..=10) | (2..=13) | (14..=48324728) | 48324729);
19+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `1..=48324729`
20+
21+
error: this OR pattern can be rewritten using a range
22+
--> $DIR/manual_range_pattern.rs:21:25
23+
|
24+
LL | let _ = matches!(f, 0 | (1..=10) | 48324730 | (2..=13) | (14..=48324728) | 48324729);
25+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `0..=48324730`
26+
27+
error: this OR pattern can be rewritten using a range
28+
--> $DIR/manual_range_pattern.rs:22:25
29+
|
30+
LL | let _ = matches!(f, 0..=1 | 0..=2 | 0..=3);
31+
| ^^^^^^^^^^^^^^^^^^^^^ help: try: `0..=3`
32+
33+
error: this OR pattern can be rewritten using a range
34+
--> $DIR/manual_range_pattern.rs:25:9
1735
|
1836
LL | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 => true,
1937
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `1..=10`
2038

21-
error: aborting due to 3 previous errors
39+
error: aborting due to 6 previous errors
2240

0 commit comments

Comments
 (0)