Skip to content

Commit 975a813

Browse files
authored
.last() to .next_back() requires a mutable receiver (#14140)
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 a non-mutable binding which is not a mutable reference. When possible, a local immutable binding is made into a mutable one. Also, the applicability is switched to `MaybeIncorrect` and a note is added to the output when the element types have a significant drop, because the drop order will potentially be modified because `.next_back()` does not consume the iterator nor the elements before the last one. Fix #14139 changelog: [`double_ended_iterator_last`]: do not trigger on non-reference immutable receiver, and warn about possible drop order change
2 parents 6872e94 + dcd643a commit 975a813

8 files changed

+272
-33
lines changed

clippy_lints/src/methods/double_ended_iterator_last.rs

Lines changed: 41 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,47 @@ 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+
let expr_ty = cx.typeck_results().expr_ty(expr);
53+
let droppable_elements = expr_ty.has_significant_drop(cx.tcx, cx.typing_env());
54+
diag.multipart_suggestion(
55+
"try",
56+
sugg,
57+
if dont_apply {
58+
Applicability::Unspecified
59+
} else if droppable_elements {
60+
Applicability::MaybeIncorrect
61+
} else {
62+
Applicability::MachineApplicable
63+
},
64+
);
65+
if droppable_elements {
66+
diag.note("this change will alter drop order which may be undesirable");
67+
}
68+
if dont_apply {
69+
diag.span_note(self_expr.span, "this must be made mutable to use `.next_back()`");
70+
}
71+
},
3972
);
4073
}
4174
}

clippy_lints/src/unnecessary_struct_initialization.rs

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
use clippy_utils::diagnostics::span_lint_and_sugg;
22
use clippy_utils::source::snippet;
33
use clippy_utils::ty::is_copy;
4-
use clippy_utils::{get_parent_expr, path_to_local};
5-
use rustc_hir::{BindingMode, Expr, ExprField, ExprKind, Node, PatKind, Path, QPath, StructTailExpr, UnOp};
4+
use clippy_utils::{get_parent_expr, is_mutable, path_to_local};
5+
use rustc_hir::{Expr, ExprField, ExprKind, Path, QPath, StructTailExpr, UnOp};
66
use rustc_lint::{LateContext, LateLintPass};
77
use rustc_session::declare_lint_pass;
88

@@ -157,16 +157,6 @@ fn same_path_in_all_fields<'tcx>(
157157
}
158158
}
159159

160-
fn is_mutable(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
161-
if let Some(hir_id) = path_to_local(expr)
162-
&& let Node::Pat(pat) = cx.tcx.hir_node(hir_id)
163-
{
164-
matches!(pat.kind, PatKind::Binding(BindingMode::MUT, ..))
165-
} else {
166-
true
167-
}
168-
}
169-
170160
fn check_references(cx: &LateContext<'_>, expr_a: &Expr<'_>, expr_b: &Expr<'_>) -> bool {
171161
if let Some(parent) = get_parent_expr(cx, expr_a)
172162
&& let parent_ty = cx.typeck_results().expr_ty_adjusted(parent)

clippy_utils/src/lib.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3679,3 +3679,24 @@ pub fn expr_requires_coercion<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) -
36793679
_ => false,
36803680
}
36813681
}
3682+
3683+
/// Returns `true` if `expr` designates a mutable static, a mutable local binding, or an expression
3684+
/// that can be owned.
3685+
pub fn is_mutable(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
3686+
if let Some(hir_id) = path_to_local(expr)
3687+
&& let Node::Pat(pat) = cx.tcx.hir_node(hir_id)
3688+
{
3689+
matches!(pat.kind, PatKind::Binding(BindingMode::MUT, ..))
3690+
} else if let ExprKind::Path(p) = &expr.kind
3691+
&& let Some(mutability) = cx
3692+
.qpath_res(p, expr.hir_id)
3693+
.opt_def_id()
3694+
.and_then(|id| cx.tcx.static_mutability(id))
3695+
{
3696+
mutability == Mutability::Mut
3697+
} else if let ExprKind::Field(parent, _) = expr.kind {
3698+
is_mutable(cx, parent)
3699+
} else {
3700+
true
3701+
}
3702+
}

tests/ui/double_ended_iterator_last.fixed

Lines changed: 41 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,42 @@ 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+
}
78+
79+
fn drop_order() {
80+
struct S(&'static str);
81+
impl std::ops::Drop for S {
82+
fn drop(&mut self) {
83+
println!("Dropping {}", self.0);
84+
}
85+
}
86+
87+
let v = vec![S("one"), S("two"), S("three")];
88+
let mut v = v.into_iter();
89+
println!("Last element is {}", v.next_back().unwrap().0);
90+
//~^ ERROR: called `Iterator::last` on a `DoubleEndedIterator`
91+
println!("Done");
92+
}

tests/ui/double_ended_iterator_last.rs

Lines changed: 41 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,42 @@ 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+
}
78+
79+
fn drop_order() {
80+
struct S(&'static str);
81+
impl std::ops::Drop for S {
82+
fn drop(&mut self) {
83+
println!("Dropping {}", self.0);
84+
}
85+
}
86+
87+
let v = vec![S("one"), S("two"), S("three")];
88+
let v = v.into_iter();
89+
println!("Last element is {}", v.last().unwrap().0);
90+
//~^ ERROR: called `Iterator::last` on a `DoubleEndedIterator`
91+
println!("Done");
92+
}
Lines changed: 70 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,82 @@
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: called `Iterator::last` on a `DoubleEndedIterator`; this will needlessly iterate the entire iterator
69+
--> tests/ui/double_ended_iterator_last.rs:89:36
70+
|
71+
LL | println!("Last element is {}", v.last().unwrap().0);
72+
| ^^^^^^^^
73+
|
74+
= note: this change will alter drop order which may be undesirable
75+
help: try
76+
|
77+
LL ~ let mut v = v.into_iter();
78+
LL ~ println!("Last element is {}", v.next_back().unwrap().0);
79+
|
80+
81+
error: aborting due to 8 previous errors
1782

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
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+
}
9+
10+
fn drop_order() {
11+
struct S(&'static str);
12+
impl std::ops::Drop for S {
13+
fn drop(&mut self) {
14+
println!("Dropping {}", self.0);
15+
}
16+
}
17+
18+
let v = vec![S("one"), S("two"), S("three")];
19+
let v = (v.into_iter(), 42);
20+
println!("Last element is {}", v.0.last().unwrap().0);
21+
//~^ ERROR: called `Iterator::last` on a `DoubleEndedIterator`
22+
println!("Done");
23+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
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: called `Iterator::last` on a `DoubleEndedIterator`; this will needlessly iterate the entire iterator
18+
--> tests/ui/double_ended_iterator_last_unfixable.rs:20:36
19+
|
20+
LL | println!("Last element is {}", v.0.last().unwrap().0);
21+
| ^^^^------
22+
| |
23+
| help: try: `next_back()`
24+
|
25+
= note: this change will alter drop order which may be undesirable
26+
note: this must be made mutable to use `.next_back()`
27+
--> tests/ui/double_ended_iterator_last_unfixable.rs:20:36
28+
|
29+
LL | println!("Last element is {}", v.0.last().unwrap().0);
30+
| ^^^
31+
32+
error: aborting due to 2 previous errors
33+

0 commit comments

Comments
 (0)