Skip to content

Commit

Permalink
double_ended_iterator_last: note when drop order is changed
Browse files Browse the repository at this point in the history
`iter.last()` will drop all elements of `iter` in order, while
`iter.next_back()` will drop the non-last elements of `iter` when
`iter` goes out of scope since `.next_back()` does not consume its
argument.

When the transformation proposed by `double_ended_iterator_last` would
concern an iterator whose element type has a significant drop, a note is
added to warn about the possible drop order change, and the suggestion
is switched from `MachineApplicable` to `MaybeIncorrect`.
  • Loading branch information
samueltardieu committed Feb 18, 2025
1 parent fff6d89 commit 7f560e9
Show file tree
Hide file tree
Showing 6 changed files with 82 additions and 2 deletions.
7 changes: 7 additions & 0 deletions clippy_lints/src/methods/double_ended_iterator_last.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,15 +49,22 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &'_ Expr<'_>, self_expr: &'_ Exp
expr.span,
"called `Iterator::last` on a `DoubleEndedIterator`; this will needlessly iterate the entire iterator",
|diag| {
let expr_ty = cx.typeck_results().expr_ty(expr);
let droppable_elements = expr_ty.has_significant_drop(cx.tcx, cx.typing_env());
diag.multipart_suggestion(
"try",
sugg,
if dont_apply {
Applicability::Unspecified
} else if droppable_elements {
Applicability::MaybeIncorrect
} else {
Applicability::MachineApplicable
},
);
if droppable_elements {
diag.note("this might cause unretrieved elements to be dropped after the retrieved one");
}
if dont_apply {
diag.span_note(self_expr.span, "this must be made mutable to use `.next_back()`");
}
Expand Down
15 changes: 15 additions & 0 deletions tests/ui/double_ended_iterator_last.fixed
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,18 @@ fn issue_14139() {
let (mut subindex, _) = (index.by_ref().take(3), 42);
let _ = subindex.next_back(); //~ ERROR: called `Iterator::last` on a `DoubleEndedIterator`
}

fn drop_order() {
struct S(&'static str);
impl std::ops::Drop for S {
fn drop(&mut self) {
println!("Dropping {}", self.0);
}
}

let v = vec![S("one"), S("two"), S("three")];
let mut v = v.into_iter();
println!("Last element is {}", v.next_back().unwrap().0);
//~^ ERROR: called `Iterator::last` on a `DoubleEndedIterator`
println!("Done");
}
15 changes: 15 additions & 0 deletions tests/ui/double_ended_iterator_last.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,18 @@ fn issue_14139() {
let (subindex, _) = (index.by_ref().take(3), 42);
let _ = subindex.last(); //~ ERROR: called `Iterator::last` on a `DoubleEndedIterator`
}

fn drop_order() {
struct S(&'static str);
impl std::ops::Drop for S {
fn drop(&mut self) {
println!("Dropping {}", self.0);
}
}

let v = vec![S("one"), S("two"), S("three")];
let v = v.into_iter();
println!("Last element is {}", v.last().unwrap().0);
//~^ ERROR: called `Iterator::last` on a `DoubleEndedIterator`
println!("Done");
}
15 changes: 14 additions & 1 deletion tests/ui/double_ended_iterator_last.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -65,5 +65,18 @@ LL ~ let (mut subindex, _) = (index.by_ref().take(3), 42);
LL ~ let _ = subindex.next_back();
|

error: aborting due to 7 previous errors
error: called `Iterator::last` on a `DoubleEndedIterator`; this will needlessly iterate the entire iterator
--> tests/ui/double_ended_iterator_last.rs:89:36
|
LL | println!("Last element is {}", v.last().unwrap().0);
| ^^^^^^^^
|
= note: this might cause unretrieved elements to be dropped after the retrieved one
help: try
|
LL ~ let mut v = v.into_iter();
LL ~ println!("Last element is {}", v.next_back().unwrap().0);
|

error: aborting due to 8 previous errors

15 changes: 15 additions & 0 deletions tests/ui/double_ended_iterator_last_unfixable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,18 @@ fn main() {
let subindex = (index.by_ref().take(3), 42);
let _ = subindex.0.last(); //~ ERROR: called `Iterator::last` on a `DoubleEndedIterator`
}

fn drop_order() {
struct S(&'static str);
impl std::ops::Drop for S {
fn drop(&mut self) {
println!("Dropping {}", self.0);
}
}

let v = vec![S("one"), S("two"), S("three")];
let v = (v.into_iter(), 42);
println!("Last element is {}", v.0.last().unwrap().0);
//~^ ERROR: called `Iterator::last` on a `DoubleEndedIterator`
println!("Done");
}
17 changes: 16 additions & 1 deletion tests/ui/double_ended_iterator_last_unfixable.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,20 @@ LL | let _ = subindex.0.last();
= note: `-D clippy::double-ended-iterator-last` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::double_ended_iterator_last)]`

error: aborting due to 1 previous error
error: called `Iterator::last` on a `DoubleEndedIterator`; this will needlessly iterate the entire iterator
--> tests/ui/double_ended_iterator_last_unfixable.rs:20:36
|
LL | println!("Last element is {}", v.0.last().unwrap().0);
| ^^^^------
| |
| help: try: `next_back()`
|
= note: this might cause unretrieved elements to be dropped after the retrieved one
note: this must be made mutable to use `.next_back()`
--> tests/ui/double_ended_iterator_last_unfixable.rs:20:36
|
LL | println!("Last element is {}", v.0.last().unwrap().0);
| ^^^

error: aborting due to 2 previous errors

0 comments on commit 7f560e9

Please # to comment.