-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
) I [recently realized that `.last()` might not call `next_back()` when it is available](https://qsantos.fr/2025/01/01/rust-gotcha-last-on-doubleendediterator/). Although the implementor could make sure to implement `last()` to do so, this is not what will happen by default. As a result, I think it is useful to add a lint to promote using `.next_back()` over `.last()` on `DoubleEndedIterator`. If this is merged, we might want to close #1822. changelog: [`double_ended_iterator_last`]: Add lint for calling `Iterator::last()` on `DoubleEndedIterator`
- Loading branch information
Showing
15 changed files
with
231 additions
and
26 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
use clippy_utils::diagnostics::span_lint_and_sugg; | ||
use clippy_utils::is_trait_method; | ||
use clippy_utils::ty::implements_trait; | ||
use rustc_errors::Applicability; | ||
use rustc_hir::Expr; | ||
use rustc_lint::LateContext; | ||
use rustc_middle::ty::Instance; | ||
use rustc_span::{Span, sym}; | ||
|
||
use super::DOUBLE_ENDED_ITERATOR_LAST; | ||
|
||
pub(super) fn check(cx: &LateContext<'_>, expr: &'_ Expr<'_>, self_expr: &'_ Expr<'_>, call_span: Span) { | ||
let typeck = cx.typeck_results(); | ||
|
||
// if the "last" method is that of Iterator | ||
if is_trait_method(cx, expr, sym::Iterator) | ||
// if self implements DoubleEndedIterator | ||
&& let Some(deiter_id) = cx.tcx.get_diagnostic_item(sym::DoubleEndedIterator) | ||
&& let self_type = cx.typeck_results().expr_ty(self_expr) | ||
&& implements_trait(cx, self_type.peel_refs(), deiter_id, &[]) | ||
// resolve the method definition | ||
&& let id = typeck.type_dependent_def_id(expr.hir_id).unwrap() | ||
&& let args = typeck.node_args(expr.hir_id) | ||
&& let Ok(Some(fn_def)) = Instance::try_resolve(cx.tcx, cx.typing_env(), id, args) | ||
// find the provided definition of Iterator::last | ||
&& let Some(item) = cx.tcx.get_diagnostic_item(sym::Iterator) | ||
&& let Some(last_def) = cx.tcx.provided_trait_methods(item).find(|m| m.name.as_str() == "last") | ||
// if the resolved method is the same as the provided definition | ||
&& fn_def.def_id() == last_def.def_id | ||
{ | ||
span_lint_and_sugg( | ||
cx, | ||
DOUBLE_ENDED_ITERATOR_LAST, | ||
call_span, | ||
"called `Iterator::last` on a `DoubleEndedIterator`; this will needlessly iterate the entire iterator", | ||
"try", | ||
"next_back()".to_string(), | ||
Applicability::MachineApplicable, | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
#![warn(clippy::double_ended_iterator_last)] | ||
|
||
// Typical case | ||
pub fn last_arg(s: &str) -> Option<&str> { | ||
s.split(' ').next_back() | ||
} | ||
|
||
fn main() { | ||
// General case | ||
struct DeIterator; | ||
impl Iterator for DeIterator { | ||
type Item = (); | ||
fn next(&mut self) -> Option<Self::Item> { | ||
Some(()) | ||
} | ||
} | ||
impl DoubleEndedIterator for DeIterator { | ||
fn next_back(&mut self) -> Option<Self::Item> { | ||
Some(()) | ||
} | ||
} | ||
let _ = DeIterator.next_back(); | ||
// Should not apply to other methods of Iterator | ||
let _ = DeIterator.count(); | ||
|
||
// Should not apply to simple iterators | ||
struct SimpleIterator; | ||
impl Iterator for SimpleIterator { | ||
type Item = (); | ||
fn next(&mut self) -> Option<Self::Item> { | ||
Some(()) | ||
} | ||
} | ||
let _ = SimpleIterator.last(); | ||
|
||
// Should not apply to custom implementations of last() | ||
struct CustomLast; | ||
impl Iterator for CustomLast { | ||
type Item = (); | ||
fn next(&mut self) -> Option<Self::Item> { | ||
Some(()) | ||
} | ||
fn last(self) -> Option<Self::Item> { | ||
Some(()) | ||
} | ||
} | ||
impl DoubleEndedIterator for CustomLast { | ||
fn next_back(&mut self) -> Option<Self::Item> { | ||
Some(()) | ||
} | ||
} | ||
let _ = CustomLast.last(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
#![warn(clippy::double_ended_iterator_last)] | ||
|
||
// Typical case | ||
pub fn last_arg(s: &str) -> Option<&str> { | ||
s.split(' ').last() | ||
} | ||
|
||
fn main() { | ||
// General case | ||
struct DeIterator; | ||
impl Iterator for DeIterator { | ||
type Item = (); | ||
fn next(&mut self) -> Option<Self::Item> { | ||
Some(()) | ||
} | ||
} | ||
impl DoubleEndedIterator for DeIterator { | ||
fn next_back(&mut self) -> Option<Self::Item> { | ||
Some(()) | ||
} | ||
} | ||
let _ = DeIterator.last(); | ||
// Should not apply to other methods of Iterator | ||
let _ = DeIterator.count(); | ||
|
||
// Should not apply to simple iterators | ||
struct SimpleIterator; | ||
impl Iterator for SimpleIterator { | ||
type Item = (); | ||
fn next(&mut self) -> Option<Self::Item> { | ||
Some(()) | ||
} | ||
} | ||
let _ = SimpleIterator.last(); | ||
|
||
// Should not apply to custom implementations of last() | ||
struct CustomLast; | ||
impl Iterator for CustomLast { | ||
type Item = (); | ||
fn next(&mut self) -> Option<Self::Item> { | ||
Some(()) | ||
} | ||
fn last(self) -> Option<Self::Item> { | ||
Some(()) | ||
} | ||
} | ||
impl DoubleEndedIterator for CustomLast { | ||
fn next_back(&mut self) -> Option<Self::Item> { | ||
Some(()) | ||
} | ||
} | ||
let _ = CustomLast.last(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
error: called `Iterator::last` on a `DoubleEndedIterator`; this will needlessly iterate the entire iterator | ||
--> tests/ui/double_ended_iterator_last.rs:5:18 | ||
| | ||
LL | s.split(' ').last() | ||
| ^^^^^^ help: try: `next_back()` | ||
| | ||
= note: `-D clippy::double-ended-iterator-last` implied by `-D warnings` | ||
= help: to override `-D warnings` add `#[allow(clippy::double_ended_iterator_last)]` | ||
|
||
error: called `Iterator::last` on a `DoubleEndedIterator`; this will needlessly iterate the entire iterator | ||
--> tests/ui/double_ended_iterator_last.rs:22:24 | ||
| | ||
LL | let _ = DeIterator.last(); | ||
| ^^^^^^ help: try: `next_back()` | ||
|
||
error: aborting due to 2 previous errors | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.