Skip to content

Commit 45f7a60

Browse files
committed
.last() to .next_back() requires a mutable receiver
In the case where `iter` is a `DoubleEndedIterator`, replacing a call to `iter.last()` (which consumes `iter`) by `iter.next_back()` (which requires a mutable reference to `iter`) cannot be done when `iter` Is not a mutable binding or a mutable reference. When `iter` is a local binding, it can be made mutable by fixing its definition site.
1 parent 510d3b6 commit 45f7a60

6 files changed

+169
-21
lines changed

clippy_lints/src/methods/double_ended_iterator_last.rs

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
use clippy_utils::diagnostics::span_lint_and_sugg;
2-
use clippy_utils::is_trait_method;
1+
use clippy_utils::diagnostics::span_lint_and_then;
32
use clippy_utils::ty::implements_trait;
3+
use clippy_utils::{is_mutable, is_trait_method, path_to_local};
44
use rustc_errors::Applicability;
5-
use rustc_hir::Expr;
5+
use rustc_hir::{Expr, Node, PatKind};
66
use rustc_lint::LateContext;
77
use rustc_middle::ty::Instance;
88
use rustc_span::{Span, sym};
@@ -28,14 +28,40 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &'_ Expr<'_>, self_expr: &'_ Exp
2828
// if the resolved method is the same as the provided definition
2929
&& fn_def.def_id() == last_def.def_id
3030
{
31-
span_lint_and_sugg(
31+
let mut sugg = vec![(call_span, String::from("next_back()"))];
32+
let mut dont_apply = false;
33+
// if `self_expr` is a reference, it is mutable because it is used for `.last()`
34+
if !(is_mutable(cx, self_expr) || self_type.is_ref()) {
35+
if let Some(hir_id) = path_to_local(self_expr)
36+
&& let Node::Pat(pat) = cx.tcx.hir_node(hir_id)
37+
&& let PatKind::Binding(_, _, ident, _) = pat.kind
38+
{
39+
sugg.push((ident.span.shrink_to_lo(), String::from("mut ")));
40+
} else {
41+
// If we can't make the binding mutable, make the suggestion `Unspecified` to prevent it from being
42+
// automatically applied, and add a complementary help message.
43+
dont_apply = true;
44+
}
45+
}
46+
span_lint_and_then(
3247
cx,
3348
DOUBLE_ENDED_ITERATOR_LAST,
34-
call_span,
49+
expr.span,
3550
"called `Iterator::last` on a `DoubleEndedIterator`; this will needlessly iterate the entire iterator",
36-
"try",
37-
"next_back()".to_string(),
38-
Applicability::MachineApplicable,
51+
|diag| {
52+
diag.multipart_suggestion(
53+
"try",
54+
sugg,
55+
if dont_apply {
56+
Applicability::Unspecified
57+
} else {
58+
Applicability::MachineApplicable
59+
},
60+
);
61+
if dont_apply {
62+
diag.span_note(self_expr.span, "this must be made mutable to use `.next_back()`");
63+
}
64+
},
3965
);
4066
}
4167
}

tests/ui/double_ended_iterator_last.fixed

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@
22

33
// Typical case
44
pub fn last_arg(s: &str) -> Option<&str> {
5-
s.split(' ').next_back()
6-
//~^ double_ended_iterator_last
5+
s.split(' ').next_back() //~ ERROR: called `Iterator::last` on a `DoubleEndedIterator`
76
}
87

98
fn main() {
@@ -20,8 +19,7 @@ fn main() {
2019
Some(())
2120
}
2221
}
23-
let _ = DeIterator.next_back();
24-
//~^ double_ended_iterator_last
22+
let _ = DeIterator.next_back(); //~ ERROR: called `Iterator::last` on a `DoubleEndedIterator`
2523
// Should not apply to other methods of Iterator
2624
let _ = DeIterator.count();
2725

@@ -53,3 +51,27 @@ fn main() {
5351
}
5452
let _ = CustomLast.last();
5553
}
54+
55+
fn issue_14139() {
56+
let mut index = [true, true, false, false, false, true].iter();
57+
let mut subindex = index.by_ref().take(3);
58+
let _ = subindex.next_back(); //~ ERROR: called `Iterator::last` on a `DoubleEndedIterator`
59+
60+
let mut index = [true, true, false, false, false, true].iter();
61+
let mut subindex = index.by_ref().take(3);
62+
let _ = subindex.next_back(); //~ ERROR: called `Iterator::last` on a `DoubleEndedIterator`
63+
64+
let mut index = [true, true, false, false, false, true].iter();
65+
let mut subindex = index.by_ref().take(3);
66+
let subindex = &mut subindex;
67+
let _ = subindex.next_back(); //~ ERROR: called `Iterator::last` on a `DoubleEndedIterator`
68+
69+
let mut index = [true, true, false, false, false, true].iter();
70+
let mut subindex = index.by_ref().take(3);
71+
let subindex = &mut subindex;
72+
let _ = subindex.next_back(); //~ ERROR: called `Iterator::last` on a `DoubleEndedIterator`
73+
74+
let mut index = [true, true, false, false, false, true].iter();
75+
let (mut subindex, _) = (index.by_ref().take(3), 42);
76+
let _ = subindex.next_back(); //~ ERROR: called `Iterator::last` on a `DoubleEndedIterator`
77+
}

tests/ui/double_ended_iterator_last.rs

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@
22

33
// Typical case
44
pub fn last_arg(s: &str) -> Option<&str> {
5-
s.split(' ').last()
6-
//~^ double_ended_iterator_last
5+
s.split(' ').last() //~ ERROR: called `Iterator::last` on a `DoubleEndedIterator`
76
}
87

98
fn main() {
@@ -20,8 +19,7 @@ fn main() {
2019
Some(())
2120
}
2221
}
23-
let _ = DeIterator.last();
24-
//~^ double_ended_iterator_last
22+
let _ = DeIterator.last(); //~ ERROR: called `Iterator::last` on a `DoubleEndedIterator`
2523
// Should not apply to other methods of Iterator
2624
let _ = DeIterator.count();
2725

@@ -53,3 +51,27 @@ fn main() {
5351
}
5452
let _ = CustomLast.last();
5553
}
54+
55+
fn issue_14139() {
56+
let mut index = [true, true, false, false, false, true].iter();
57+
let subindex = index.by_ref().take(3);
58+
let _ = subindex.last(); //~ ERROR: called `Iterator::last` on a `DoubleEndedIterator`
59+
60+
let mut index = [true, true, false, false, false, true].iter();
61+
let mut subindex = index.by_ref().take(3);
62+
let _ = subindex.last(); //~ ERROR: called `Iterator::last` on a `DoubleEndedIterator`
63+
64+
let mut index = [true, true, false, false, false, true].iter();
65+
let mut subindex = index.by_ref().take(3);
66+
let subindex = &mut subindex;
67+
let _ = subindex.last(); //~ ERROR: called `Iterator::last` on a `DoubleEndedIterator`
68+
69+
let mut index = [true, true, false, false, false, true].iter();
70+
let mut subindex = index.by_ref().take(3);
71+
let subindex = &mut subindex;
72+
let _ = subindex.last(); //~ ERROR: called `Iterator::last` on a `DoubleEndedIterator`
73+
74+
let mut index = [true, true, false, false, false, true].iter();
75+
let (subindex, _) = (index.by_ref().take(3), 42);
76+
let _ = subindex.last(); //~ ERROR: called `Iterator::last` on a `DoubleEndedIterator`
77+
}
Lines changed: 57 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,69 @@
11
error: called `Iterator::last` on a `DoubleEndedIterator`; this will needlessly iterate the entire iterator
2-
--> tests/ui/double_ended_iterator_last.rs:5:18
2+
--> tests/ui/double_ended_iterator_last.rs:5:5
33
|
44
LL | s.split(' ').last()
5-
| ^^^^^^ help: try: `next_back()`
5+
| ^^^^^^^^^^^^^------
6+
| |
7+
| help: try: `next_back()`
68
|
79
= note: `-D clippy::double-ended-iterator-last` implied by `-D warnings`
810
= help: to override `-D warnings` add `#[allow(clippy::double_ended_iterator_last)]`
911

1012
error: called `Iterator::last` on a `DoubleEndedIterator`; this will needlessly iterate the entire iterator
11-
--> tests/ui/double_ended_iterator_last.rs:23:24
13+
--> tests/ui/double_ended_iterator_last.rs:22:13
1214
|
1315
LL | let _ = DeIterator.last();
14-
| ^^^^^^ help: try: `next_back()`
16+
| ^^^^^^^^^^^------
17+
| |
18+
| help: try: `next_back()`
1519

16-
error: aborting due to 2 previous errors
20+
error: called `Iterator::last` on a `DoubleEndedIterator`; this will needlessly iterate the entire iterator
21+
--> tests/ui/double_ended_iterator_last.rs:58:13
22+
|
23+
LL | let _ = subindex.last();
24+
| ^^^^^^^^^^^^^^^
25+
|
26+
help: try
27+
|
28+
LL ~ let mut subindex = index.by_ref().take(3);
29+
LL ~ let _ = subindex.next_back();
30+
|
31+
32+
error: called `Iterator::last` on a `DoubleEndedIterator`; this will needlessly iterate the entire iterator
33+
--> tests/ui/double_ended_iterator_last.rs:62:13
34+
|
35+
LL | let _ = subindex.last();
36+
| ^^^^^^^^^------
37+
| |
38+
| help: try: `next_back()`
39+
40+
error: called `Iterator::last` on a `DoubleEndedIterator`; this will needlessly iterate the entire iterator
41+
--> tests/ui/double_ended_iterator_last.rs:67:13
42+
|
43+
LL | let _ = subindex.last();
44+
| ^^^^^^^^^------
45+
| |
46+
| help: try: `next_back()`
47+
48+
error: called `Iterator::last` on a `DoubleEndedIterator`; this will needlessly iterate the entire iterator
49+
--> tests/ui/double_ended_iterator_last.rs:72:13
50+
|
51+
LL | let _ = subindex.last();
52+
| ^^^^^^^^^------
53+
| |
54+
| help: try: `next_back()`
55+
56+
error: called `Iterator::last` on a `DoubleEndedIterator`; this will needlessly iterate the entire iterator
57+
--> tests/ui/double_ended_iterator_last.rs:76:13
58+
|
59+
LL | let _ = subindex.last();
60+
| ^^^^^^^^^^^^^^^
61+
|
62+
help: try
63+
|
64+
LL ~ let (mut subindex, _) = (index.by_ref().take(3), 42);
65+
LL ~ let _ = subindex.next_back();
66+
|
67+
68+
error: aborting due to 7 previous errors
1769

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
//@no-rustfix
2+
#![warn(clippy::double_ended_iterator_last)]
3+
4+
fn main() {
5+
let mut index = [true, true, false, false, false, true].iter();
6+
let subindex = (index.by_ref().take(3), 42);
7+
let _ = subindex.0.last(); //~ ERROR: called `Iterator::last` on a `DoubleEndedIterator`
8+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
error: called `Iterator::last` on a `DoubleEndedIterator`; this will needlessly iterate the entire iterator
2+
--> tests/ui/double_ended_iterator_last_unfixable.rs:7:13
3+
|
4+
LL | let _ = subindex.0.last();
5+
| ^^^^^^^^^^^------
6+
| |
7+
| help: try: `next_back()`
8+
|
9+
note: this must be made mutable to use `.next_back()`
10+
--> tests/ui/double_ended_iterator_last_unfixable.rs:7:13
11+
|
12+
LL | let _ = subindex.0.last();
13+
| ^^^^^^^^^^
14+
= note: `-D clippy::double-ended-iterator-last` implied by `-D warnings`
15+
= help: to override `-D warnings` add `#[allow(clippy::double_ended_iterator_last)]`
16+
17+
error: aborting due to 1 previous error
18+

0 commit comments

Comments
 (0)