Skip to content

Commit 6150bf5

Browse files
committed
Auto merge of #11462 - Alexendoo:manual-range-patterns-preserve-literals, r=blyxyas
Preserve literals and range kinds in `manual_range_patterns` Fixes #11461 Also enables linting when there are 3 or fewer alternatives if one of them is already a range pattern changelog: none
2 parents 415ba21 + bbf67c3 commit 6150bf5

File tree

4 files changed

+171
-52
lines changed

4 files changed

+171
-52
lines changed
+83-46
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1-
use clippy_utils::diagnostics::span_lint_and_sugg;
1+
use clippy_utils::diagnostics::span_lint_and_then;
2+
use clippy_utils::source::snippet_opt;
23
use rustc_ast::LitKind;
34
use rustc_data_structures::fx::FxHashSet;
45
use rustc_errors::Applicability;
56
use rustc_hir::{Expr, ExprKind, PatKind, RangeEnd, UnOp};
67
use rustc_lint::{LateContext, LateLintPass, LintContext};
78
use rustc_middle::lint::in_external_macro;
89
use rustc_session::{declare_lint_pass, declare_tool_lint};
10+
use rustc_span::{Span, DUMMY_SP};
911

1012
declare_clippy_lint! {
1113
/// ### What it does
@@ -49,78 +51,113 @@ fn expr_as_i128(expr: &Expr<'_>) -> Option<i128> {
4951
}
5052
}
5153

54+
#[derive(Copy, Clone)]
55+
struct Num {
56+
val: i128,
57+
span: Span,
58+
}
59+
60+
impl Num {
61+
fn new(expr: &Expr<'_>) -> Option<Self> {
62+
Some(Self {
63+
val: expr_as_i128(expr)?,
64+
span: expr.span,
65+
})
66+
}
67+
68+
fn dummy(val: i128) -> Self {
69+
Self { val, span: DUMMY_SP }
70+
}
71+
72+
fn min(self, other: Self) -> Self {
73+
if self.val < other.val { self } else { other }
74+
}
75+
}
76+
5277
impl LateLintPass<'_> for ManualRangePatterns {
5378
fn check_pat(&mut self, cx: &LateContext<'_>, pat: &'_ rustc_hir::Pat<'_>) {
5479
if in_external_macro(cx.sess(), pat.span) {
5580
return;
5681
}
5782

5883
// a pattern like 1 | 2 seems fine, lint if there are at least 3 alternatives
84+
// or at least one range
5985
if let PatKind::Or(pats) = pat.kind
60-
&& pats.len() >= 3
86+
&& (pats.len() >= 3 || pats.iter().any(|p| matches!(p.kind, PatKind::Range(..))))
6187
{
62-
let mut min = i128::MAX;
63-
let mut max = i128::MIN;
88+
let mut min = Num::dummy(i128::MAX);
89+
let mut max = Num::dummy(i128::MIN);
90+
let mut range_kind = RangeEnd::Included;
6491
let mut numbers_found = FxHashSet::default();
6592
let mut ranges_found = Vec::new();
6693

6794
for pat in pats {
6895
if let PatKind::Lit(lit) = pat.kind
69-
&& let Some(num) = expr_as_i128(lit)
96+
&& let Some(num) = Num::new(lit)
7097
{
71-
numbers_found.insert(num);
98+
numbers_found.insert(num.val);
7299

73100
min = min.min(num);
74-
max = max.max(num);
101+
if num.val >= max.val {
102+
max = num;
103+
range_kind = RangeEnd::Included;
104+
}
75105
} else if let PatKind::Range(Some(left), Some(right), end) = pat.kind
76-
&& let Some(left) = expr_as_i128(left)
77-
&& let Some(right) = expr_as_i128(right)
78-
&& right >= left
106+
&& let Some(left) = Num::new(left)
107+
&& let Some(mut right) = Num::new(right)
79108
{
109+
if let RangeEnd::Excluded = end {
110+
right.val -= 1;
111+
}
112+
80113
min = min.min(left);
81-
max = max.max(right);
82-
ranges_found.push(left..=match end {
83-
RangeEnd::Included => right,
84-
RangeEnd::Excluded => right - 1,
85-
});
114+
if right.val > max.val {
115+
max = right;
116+
range_kind = end;
117+
}
118+
ranges_found.push(left.val..=right.val);
86119
} else {
87120
return;
88121
}
89122
}
90123

91-
let contains_whole_range = 'contains: {
92-
let mut num = min;
93-
while num <= max {
94-
if numbers_found.contains(&num) {
95-
num += 1;
96-
}
97-
// Given a list of (potentially overlapping) ranges like:
98-
// 1..=5, 3..=7, 6..=10
99-
// We want to find the range with the highest end that still contains the current number
100-
else if let Some(range) = ranges_found
101-
.iter()
102-
.filter(|range| range.contains(&num))
103-
.max_by_key(|range| range.end())
104-
{
105-
num = range.end() + 1;
106-
} else {
107-
break 'contains false;
108-
}
124+
let mut num = min.val;
125+
while num <= max.val {
126+
if numbers_found.contains(&num) {
127+
num += 1;
128+
}
129+
// Given a list of (potentially overlapping) ranges like:
130+
// 1..=5, 3..=7, 6..=10
131+
// We want to find the range with the highest end that still contains the current number
132+
else if let Some(range) = ranges_found
133+
.iter()
134+
.filter(|range| range.contains(&num))
135+
.max_by_key(|range| range.end())
136+
{
137+
num = range.end() + 1;
138+
} else {
139+
return;
109140
}
110-
break 'contains true;
111-
};
112-
113-
if contains_whole_range {
114-
span_lint_and_sugg(
115-
cx,
116-
MANUAL_RANGE_PATTERNS,
117-
pat.span,
118-
"this OR pattern can be rewritten using a range",
119-
"try",
120-
format!("{min}..={max}"),
121-
Applicability::MachineApplicable,
122-
);
123141
}
142+
143+
span_lint_and_then(
144+
cx,
145+
MANUAL_RANGE_PATTERNS,
146+
pat.span,
147+
"this OR pattern can be rewritten using a range",
148+
|diag| {
149+
if let Some(min) = snippet_opt(cx, min.span)
150+
&& let Some(max) = snippet_opt(cx, max.span)
151+
{
152+
diag.span_suggestion(
153+
pat.span,
154+
"try",
155+
format!("{min}{range_kind}{max}"),
156+
Applicability::MachineApplicable,
157+
);
158+
}
159+
},
160+
);
124161
}
125162
}
126163
}

tests/ui/manual_range_patterns.fixed

+14-3
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ fn main() {
1313
let _ = matches!(f, 1 | 2147483647);
1414
let _ = matches!(f, 0 | 2147483647);
1515
let _ = matches!(f, -2147483647 | 2147483647);
16-
let _ = matches!(f, 1 | (2..=4));
17-
let _ = matches!(f, 1 | (2..4));
16+
let _ = matches!(f, 1..=4);
17+
let _ = matches!(f, 1..4);
1818
let _ = matches!(f, 1..=48324729);
1919
let _ = matches!(f, 0..=48324730);
2020
let _ = matches!(f, 0..=3);
@@ -25,9 +25,20 @@ fn main() {
2525
};
2626
let _ = matches!(f, -5..=3);
2727
let _ = matches!(f, -1 | -5 | 3 | -2 | -4 | -3 | 0 | 1); // 2 is missing
28-
let _ = matches!(f, -1000001..=1000001);
28+
let _ = matches!(f, -1_000_001..=1_000_001);
2929
let _ = matches!(f, -1_000_000..=1_000_000 | -1_000_001 | 1_000_002);
3030

31+
matches!(f, 0x00..=0x03);
32+
matches!(f, 0x00..=0x07);
33+
matches!(f, -0x09..=0x00);
34+
35+
matches!(f, 0..=5);
36+
matches!(f, 0..5);
37+
38+
matches!(f, 0..10);
39+
matches!(f, 0..=10);
40+
matches!(f, 0..=10);
41+
3142
macro_rules! mac {
3243
($e:expr) => {
3344
matches!($e, 1..=10)

tests/ui/manual_range_patterns.rs

+11
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,17 @@ fn main() {
2828
let _ = matches!(f, -1_000_000..=1_000_000 | -1_000_001 | 1_000_001);
2929
let _ = matches!(f, -1_000_000..=1_000_000 | -1_000_001 | 1_000_002);
3030

31+
matches!(f, 0x00 | 0x01 | 0x02 | 0x03);
32+
matches!(f, 0x00..=0x05 | 0x06 | 0x07);
33+
matches!(f, -0x09 | -0x08 | -0x07..=0x00);
34+
35+
matches!(f, 0..5 | 5);
36+
matches!(f, 0 | 1..5);
37+
38+
matches!(f, 0..=5 | 6..10);
39+
matches!(f, 0..5 | 5..=10);
40+
matches!(f, 5..=10 | 0..5);
41+
3142
macro_rules! mac {
3243
($e:expr) => {
3344
matches!($e, 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10)

tests/ui/manual_range_patterns.stderr

+63-3
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,18 @@ error: this OR pattern can be rewritten using a range
1212
LL | let _ = matches!(f, 4 | 2 | 3 | 1 | 5 | 6 | 9 | 7 | 8 | 10);
1313
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `1..=10`
1414

15+
error: this OR pattern can be rewritten using a range
16+
--> $DIR/manual_range_patterns.rs:16:25
17+
|
18+
LL | let _ = matches!(f, 1 | (2..=4));
19+
| ^^^^^^^^^^^ help: try: `1..=4`
20+
21+
error: this OR pattern can be rewritten using a range
22+
--> $DIR/manual_range_patterns.rs:17:25
23+
|
24+
LL | let _ = matches!(f, 1 | (2..4));
25+
| ^^^^^^^^^^ help: try: `1..4`
26+
1527
error: this OR pattern can be rewritten using a range
1628
--> $DIR/manual_range_patterns.rs:18:25
1729
|
@@ -46,10 +58,58 @@ error: this OR pattern can be rewritten using a range
4658
--> $DIR/manual_range_patterns.rs:28:25
4759
|
4860
LL | let _ = matches!(f, -1_000_000..=1_000_000 | -1_000_001 | 1_000_001);
49-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `-1000001..=1000001`
61+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `-1_000_001..=1_000_001`
62+
63+
error: this OR pattern can be rewritten using a range
64+
--> $DIR/manual_range_patterns.rs:31:17
65+
|
66+
LL | matches!(f, 0x00 | 0x01 | 0x02 | 0x03);
67+
| ^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `0x00..=0x03`
68+
69+
error: this OR pattern can be rewritten using a range
70+
--> $DIR/manual_range_patterns.rs:32:17
71+
|
72+
LL | matches!(f, 0x00..=0x05 | 0x06 | 0x07);
73+
| ^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `0x00..=0x07`
74+
75+
error: this OR pattern can be rewritten using a range
76+
--> $DIR/manual_range_patterns.rs:33:17
77+
|
78+
LL | matches!(f, -0x09 | -0x08 | -0x07..=0x00);
79+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `-0x09..=0x00`
80+
81+
error: this OR pattern can be rewritten using a range
82+
--> $DIR/manual_range_patterns.rs:35:17
83+
|
84+
LL | matches!(f, 0..5 | 5);
85+
| ^^^^^^^^ help: try: `0..=5`
86+
87+
error: this OR pattern can be rewritten using a range
88+
--> $DIR/manual_range_patterns.rs:36:17
89+
|
90+
LL | matches!(f, 0 | 1..5);
91+
| ^^^^^^^^ help: try: `0..5`
92+
93+
error: this OR pattern can be rewritten using a range
94+
--> $DIR/manual_range_patterns.rs:38:17
95+
|
96+
LL | matches!(f, 0..=5 | 6..10);
97+
| ^^^^^^^^^^^^^ help: try: `0..10`
98+
99+
error: this OR pattern can be rewritten using a range
100+
--> $DIR/manual_range_patterns.rs:39:17
101+
|
102+
LL | matches!(f, 0..5 | 5..=10);
103+
| ^^^^^^^^^^^^^ help: try: `0..=10`
104+
105+
error: this OR pattern can be rewritten using a range
106+
--> $DIR/manual_range_patterns.rs:40:17
107+
|
108+
LL | matches!(f, 5..=10 | 0..5);
109+
| ^^^^^^^^^^^^^ help: try: `0..=10`
50110

51111
error: this OR pattern can be rewritten using a range
52-
--> $DIR/manual_range_patterns.rs:33:26
112+
--> $DIR/manual_range_patterns.rs:44:26
53113
|
54114
LL | matches!($e, 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10)
55115
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `1..=10`
@@ -59,5 +119,5 @@ LL | mac!(f);
59119
|
60120
= note: this error originates in the macro `mac` (in Nightly builds, run with -Z macro-backtrace for more info)
61121

62-
error: aborting due to 9 previous errors
122+
error: aborting due to 19 previous errors
63123

0 commit comments

Comments
 (0)