Skip to content

Commit 15fc993

Browse files
committed
Refactor range_plus_one and range_minus_one lints
Those lints share the detection logic structure, but differed, probably because of an oversight, in lint emission: only one of them would take care of emitting parentheses around the suggestion if required. Factor the code into one function which checks for the right condition and emits the lint in an identical way.
1 parent d66e4f6 commit 15fc993

File tree

4 files changed

+124
-75
lines changed

4 files changed

+124
-75
lines changed

clippy_lints/src/ranges.rs

Lines changed: 57 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use rustc_ast::Mutability;
1313
use rustc_ast::ast::RangeLimits;
1414
use rustc_errors::Applicability;
1515
use rustc_hir::{BinOpKind, Expr, ExprKind, HirId, LangItem, Node};
16-
use rustc_lint::{LateContext, LateLintPass};
16+
use rustc_lint::{LateContext, LateLintPass, Lint};
1717
use rustc_middle::ty::{self, ClauseKind, GenericArgKind, PredicatePolarity, Ty};
1818
use rustc_session::impl_lint_pass;
1919
use rustc_span::source_map::Spanned;
@@ -468,70 +468,70 @@ fn can_switch_ranges<'tcx>(
468468

469469
// exclusive range plus one: `x..(y+1)`
470470
fn check_exclusive_range_plus_one<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
471-
if expr.span.can_be_used_for_suggestions()
472-
&& let Some(higher::Range {
473-
start,
474-
end: Some(end),
475-
limits: RangeLimits::HalfOpen,
476-
}) = higher::Range::hir(expr)
477-
&& let Some(y) = y_plus_one(cx, end)
478-
&& can_switch_ranges(cx, expr, RangeLimits::HalfOpen, cx.typeck_results().expr_ty(y))
479-
{
480-
let span = expr.span;
481-
span_lint_and_then(
482-
cx,
483-
RANGE_PLUS_ONE,
484-
span,
485-
"an inclusive range would be more readable",
486-
|diag| {
487-
let start = start.map_or(String::new(), |x| Sugg::hir(cx, x, "x").maybe_paren().to_string());
488-
let end = Sugg::hir(cx, y, "y").maybe_paren();
489-
match span.with_source_text(cx, |src| src.starts_with('(') && src.ends_with(')')) {
490-
Some(true) => {
491-
diag.span_suggestion(span, "use", format!("({start}..={end})"), Applicability::MaybeIncorrect);
492-
},
493-
Some(false) => {
494-
diag.span_suggestion(
495-
span,
496-
"use",
497-
format!("{start}..={end}"),
498-
Applicability::MachineApplicable, // snippet
499-
);
500-
},
501-
None => {},
502-
}
503-
},
504-
);
505-
}
471+
check_range_switch(
472+
cx,
473+
expr,
474+
RangeLimits::HalfOpen,
475+
y_plus_one,
476+
RANGE_PLUS_ONE,
477+
"an inclusive range would be more readable",
478+
"..=",
479+
);
506480
}
507481

508482
// inclusive range minus one: `x..=(y-1)`
509483
fn check_inclusive_range_minus_one<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
484+
check_range_switch(
485+
cx,
486+
expr,
487+
RangeLimits::Closed,
488+
y_minus_one,
489+
RANGE_MINUS_ONE,
490+
"an exclusive range would be more readable",
491+
"..",
492+
);
493+
}
494+
495+
/// Check for a `kind` of range in `expr`, check for `predicate` on the end,
496+
/// and emit the `lint` with `msg` and the `operator`.
497+
fn check_range_switch<'tcx>(
498+
cx: &LateContext<'tcx>,
499+
expr: &'tcx Expr<'_>,
500+
kind: RangeLimits,
501+
predicate: impl for<'hir> FnOnce(&LateContext<'_>, &Expr<'hir>) -> Option<&'hir Expr<'hir>>,
502+
lint: &'static Lint,
503+
msg: &'static str,
504+
operator: &str,
505+
) {
510506
if expr.span.can_be_used_for_suggestions()
511507
&& let Some(higher::Range {
512508
start,
513509
end: Some(end),
514-
limits: RangeLimits::Closed,
510+
limits,
515511
}) = higher::Range::hir(expr)
516-
&& let Some(y) = y_minus_one(cx, end)
517-
&& can_switch_ranges(cx, expr, RangeLimits::Closed, cx.typeck_results().expr_ty(y))
512+
&& limits == kind
513+
&& let Some(y) = predicate(cx, end)
514+
&& can_switch_ranges(cx, expr, kind, cx.typeck_results().expr_ty(y))
518515
{
519-
span_lint_and_then(
520-
cx,
521-
RANGE_MINUS_ONE,
522-
expr.span,
523-
"an exclusive range would be more readable",
524-
|diag| {
525-
let start = start.map_or(String::new(), |x| Sugg::hir(cx, x, "x").maybe_paren().to_string());
526-
let end = Sugg::hir(cx, y, "y").maybe_paren();
527-
diag.span_suggestion(
528-
expr.span,
529-
"use",
530-
format!("{start}..{end}"),
531-
Applicability::MachineApplicable, // snippet
532-
);
533-
},
534-
);
516+
let span = expr.span;
517+
span_lint_and_then(cx, lint, span, msg, |diag| {
518+
let mut app = Applicability::MachineApplicable;
519+
let start = start.map_or(String::new(), |x| {
520+
Sugg::hir_with_applicability(cx, x, "<x>", &mut app)
521+
.maybe_paren()
522+
.to_string()
523+
});
524+
let end = Sugg::hir_with_applicability(cx, y, "<y>", &mut app).maybe_paren();
525+
match span.with_source_text(cx, |src| src.starts_with('(') && src.ends_with(')')) {
526+
Some(true) => {
527+
diag.span_suggestion(span, "use", format!("({start}{operator}{end})"), app);
528+
},
529+
Some(false) => {
530+
diag.span_suggestion(span, "use", format!("{start}{operator}{end}"), app);
531+
},
532+
None => {},
533+
}
534+
});
535535
}
536536
}
537537

@@ -618,7 +618,7 @@ fn check_reversed_empty_range(cx: &LateContext<'_>, expr: &Expr<'_>) {
618618
}
619619
}
620620

621-
fn y_plus_one<'t>(cx: &LateContext<'_>, expr: &'t Expr<'_>) -> Option<&'t Expr<'t>> {
621+
fn y_plus_one<'tcx>(cx: &LateContext<'_>, expr: &Expr<'tcx>) -> Option<&'tcx Expr<'tcx>> {
622622
match expr.kind {
623623
ExprKind::Binary(
624624
Spanned {
@@ -639,7 +639,7 @@ fn y_plus_one<'t>(cx: &LateContext<'_>, expr: &'t Expr<'_>) -> Option<&'t Expr<'
639639
}
640640
}
641641

642-
fn y_minus_one<'t>(cx: &LateContext<'_>, expr: &'t Expr<'_>) -> Option<&'t Expr<'t>> {
642+
fn y_minus_one<'tcx>(cx: &LateContext<'_>, expr: &Expr<'tcx>) -> Option<&'tcx Expr<'tcx>> {
643643
match expr.kind {
644644
ExprKind::Binary(
645645
Spanned {

tests/ui/range_plus_minus_one.fixed

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
#![warn(clippy::range_minus_one)]
2-
#![warn(clippy::range_plus_one)]
1+
#![warn(clippy::range_minus_one, clippy::range_plus_one)]
32
#![allow(unused_parens)]
43
#![allow(clippy::iter_with_drain)]
54

@@ -160,3 +159,24 @@ fn no_index_mut_with_switched_range(a: usize) {
160159

161160
S(2)[0..a + 1] = 3;
162161
}
162+
163+
fn issue9908() {
164+
// Simplified test case
165+
let _ = || 0..=1;
166+
167+
// Original test case
168+
let full_length = 1024;
169+
let range = {
170+
// do some stuff, omit here
171+
None
172+
};
173+
174+
let range = range.map(|(s, t)| s..=t).unwrap_or(0..=(full_length - 1));
175+
176+
assert_eq!(range, 0..=1023);
177+
}
178+
179+
fn issue9908_2(n: usize) -> usize {
180+
(1..n).sum()
181+
//~^ range_minus_one
182+
}

tests/ui/range_plus_minus_one.rs

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
#![warn(clippy::range_minus_one)]
2-
#![warn(clippy::range_plus_one)]
1+
#![warn(clippy::range_minus_one, clippy::range_plus_one)]
32
#![allow(unused_parens)]
43
#![allow(clippy::iter_with_drain)]
54

@@ -160,3 +159,24 @@ fn no_index_mut_with_switched_range(a: usize) {
160159

161160
S(2)[0..a + 1] = 3;
162161
}
162+
163+
fn issue9908() {
164+
// Simplified test case
165+
let _ = || 0..=1;
166+
167+
// Original test case
168+
let full_length = 1024;
169+
let range = {
170+
// do some stuff, omit here
171+
None
172+
};
173+
174+
let range = range.map(|(s, t)| s..=t).unwrap_or(0..=(full_length - 1));
175+
176+
assert_eq!(range, 0..=1023);
177+
}
178+
179+
fn issue9908_2(n: usize) -> usize {
180+
(1..=n - 1).sum()
181+
//~^ range_minus_one
182+
}

tests/ui/range_plus_minus_one.stderr

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
error: an inclusive range would be more readable
2-
--> tests/ui/range_plus_minus_one.rs:32:14
2+
--> tests/ui/range_plus_minus_one.rs:31:14
33
|
44
LL | for _ in 0..3 + 1 {}
55
| ^^^^^^^^ help: use: `0..=3`
@@ -8,76 +8,85 @@ LL | for _ in 0..3 + 1 {}
88
= help: to override `-D warnings` add `#[allow(clippy::range_plus_one)]`
99

1010
error: an inclusive range would be more readable
11-
--> tests/ui/range_plus_minus_one.rs:36:14
11+
--> tests/ui/range_plus_minus_one.rs:35:14
1212
|
1313
LL | for _ in 0..1 + 5 {}
1414
| ^^^^^^^^ help: use: `0..=5`
1515

1616
error: an inclusive range would be more readable
17-
--> tests/ui/range_plus_minus_one.rs:40:14
17+
--> tests/ui/range_plus_minus_one.rs:39:14
1818
|
1919
LL | for _ in 1..1 + 1 {}
2020
| ^^^^^^^^ help: use: `1..=1`
2121

2222
error: an inclusive range would be more readable
23-
--> tests/ui/range_plus_minus_one.rs:47:14
23+
--> tests/ui/range_plus_minus_one.rs:46:14
2424
|
2525
LL | for _ in 0..(1 + f()) {}
2626
| ^^^^^^^^^^^^ help: use: `0..=f()`
2727

2828
error: an inclusive range would be more readable
29-
--> tests/ui/range_plus_minus_one.rs:61:14
29+
--> tests/ui/range_plus_minus_one.rs:60:14
3030
|
3131
LL | for _ in 1..ONE + ONE {}
3232
| ^^^^^^^^^^^^ help: use: `1..=ONE`
3333

3434
error: an inclusive range would be more readable
35-
--> tests/ui/range_plus_minus_one.rs:71:5
35+
--> tests/ui/range_plus_minus_one.rs:70:5
3636
|
3737
LL | (1..10 + 1).for_each(|_| {});
3838
| ^^^^^^^^^^^ help: use: `(1..=10)`
3939

4040
error: an inclusive range would be more readable
41-
--> tests/ui/range_plus_minus_one.rs:76:5
41+
--> tests/ui/range_plus_minus_one.rs:75:5
4242
|
4343
LL | (1..10 + 1).into_iter().for_each(|_| {});
4444
| ^^^^^^^^^^^ help: use: `(1..=10)`
4545

4646
error: an inclusive range would be more readable
47-
--> tests/ui/range_plus_minus_one.rs:81:17
47+
--> tests/ui/range_plus_minus_one.rs:80:17
4848
|
4949
LL | let _ = (1..10 + 1).start_bound();
5050
| ^^^^^^^^^^^ help: use: `(1..=10)`
5151

5252
error: an inclusive range would be more readable
53-
--> tests/ui/range_plus_minus_one.rs:87:16
53+
--> tests/ui/range_plus_minus_one.rs:86:16
5454
|
5555
LL | let _ = &a[1..1 + 1];
5656
| ^^^^^^^^ help: use: `1..=1`
5757

5858
error: an inclusive range would be more readable
59-
--> tests/ui/range_plus_minus_one.rs:91:15
59+
--> tests/ui/range_plus_minus_one.rs:90:15
6060
|
6161
LL | vec.drain(2..3 + 1);
6262
| ^^^^^^^^ help: use: `2..=3`
6363

6464
error: an inclusive range would be more readable
65-
--> tests/ui/range_plus_minus_one.rs:95:14
65+
--> tests/ui/range_plus_minus_one.rs:94:14
6666
|
6767
LL | take_arg(10..20 + 1);
6868
| ^^^^^^^^^^ help: use: `10..=20`
6969

7070
error: an inclusive range would be more readable
71-
--> tests/ui/range_plus_minus_one.rs:99:16
71+
--> tests/ui/range_plus_minus_one.rs:98:16
7272
|
7373
LL | take_arg({ 10..20 + 1 });
7474
| ^^^^^^^^^^ help: use: `10..=20`
7575

7676
error: an inclusive range would be more readable
77-
--> tests/ui/range_plus_minus_one.rs:113:7
77+
--> tests/ui/range_plus_minus_one.rs:112:7
7878
|
7979
LL | a[0..2 + 1][0] = 1;
8080
| ^^^^^^^^ help: use: `0..=2`
8181

82-
error: aborting due to 13 previous errors
82+
error: an exclusive range would be more readable
83+
--> tests/ui/range_plus_minus_one.rs:180:5
84+
|
85+
LL | (1..=n - 1).sum()
86+
| ^^^^^^^^^^^ help: use: `(1..n)`
87+
|
88+
= note: `-D clippy::range-minus-one` implied by `-D warnings`
89+
= help: to override `-D warnings` add `#[allow(clippy::range_minus_one)]`
90+
91+
error: aborting due to 14 previous errors
8392

0 commit comments

Comments
 (0)