Skip to content

Commit 707653f

Browse files
committed
Add lint for calling last() on DoubleEndedIterator
1 parent 33a6590 commit 707653f

File tree

7 files changed

+160
-0
lines changed

7 files changed

+160
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5493,6 +5493,7 @@ Released 2018-09-13
54935493
[`doc_markdown`]: https://rust-lang.github.io/rust-clippy/master/index.html#doc_markdown
54945494
[`doc_nested_refdefs`]: https://rust-lang.github.io/rust-clippy/master/index.html#doc_nested_refdefs
54955495
[`double_comparisons`]: https://rust-lang.github.io/rust-clippy/master/index.html#double_comparisons
5496+
[`double_ended_iterator_last`]: https://rust-lang.github.io/rust-clippy/master/index.html#double_ended_iterator_last
54965497
[`double_must_use`]: https://rust-lang.github.io/rust-clippy/master/index.html#double_must_use
54975498
[`double_neg`]: https://rust-lang.github.io/rust-clippy/master/index.html#double_neg
54985499
[`double_parens`]: https://rust-lang.github.io/rust-clippy/master/index.html#double_parens

clippy_lints/src/declared_lints.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
372372
crate::methods::CLONE_ON_REF_PTR_INFO,
373373
crate::methods::COLLAPSIBLE_STR_REPLACE_INFO,
374374
crate::methods::CONST_IS_EMPTY_INFO,
375+
crate::methods::DOUBLE_ENDED_ITERATOR_LAST_INFO,
375376
crate::methods::DRAIN_COLLECT_INFO,
376377
crate::methods::ERR_EXPECT_INFO,
377378
crate::methods::EXPECT_FUN_CALL_INFO,
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
use clippy_utils::diagnostics::span_lint_and_sugg;
2+
use clippy_utils::is_trait_method;
3+
use clippy_utils::ty::implements_trait;
4+
use rustc_errors::Applicability;
5+
use rustc_hir::Expr;
6+
use rustc_lint::LateContext;
7+
use rustc_span::{Span, sym};
8+
9+
use super::DOUBLE_ENDED_ITERATOR_LAST;
10+
11+
pub(super) fn check(cx: &LateContext<'_>, expr: &'_ Expr<'_>, self_expr: &'_ Expr<'_>, call_span: Span) {
12+
let typeck = cx.typeck_results();
13+
14+
// Check if the current "last" method is that of the Iterator trait
15+
if !is_trait_method(cx, expr, sym::Iterator) {
16+
return;
17+
}
18+
19+
// Find id for DoubleEndedIterator trait
20+
let Some(deiter_id) = cx.tcx.get_diagnostic_item(sym::DoubleEndedIterator) else {
21+
return;
22+
};
23+
24+
// Find the type of self
25+
let self_type = typeck.expr_ty(self_expr).peel_refs();
26+
27+
// Check that the object implements the DoubleEndedIterator trait
28+
if !implements_trait(cx, self_type, deiter_id, &[]) {
29+
return;
30+
}
31+
32+
// Emit lint
33+
span_lint_and_sugg(
34+
cx,
35+
DOUBLE_ENDED_ITERATOR_LAST,
36+
call_span,
37+
"called `Iterator::last` on a `DoubleEndedIterator`; this will needlessly iterate the entire iterator",
38+
"try",
39+
"next_back()".to_string(),
40+
Applicability::MachineApplicable,
41+
);
42+
}

clippy_lints/src/methods/mod.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ mod clone_on_copy;
1414
mod clone_on_ref_ptr;
1515
mod cloned_instead_of_copied;
1616
mod collapsible_str_replace;
17+
mod double_ended_iterator_last;
1718
mod drain_collect;
1819
mod err_expect;
1920
mod expect_fun_call;
@@ -4284,6 +4285,32 @@ declare_clippy_lint! {
42844285
"map of a trivial closure (not dependent on parameter) over a range"
42854286
}
42864287

4288+
declare_clippy_lint! {
4289+
/// ### What it does
4290+
///
4291+
/// Checks for `Iterator::last` being called on a `DoubleEndedIterator`, which can be replaced
4292+
/// with `DoubleEndedIterator::next_back`.
4293+
///
4294+
/// ### Why is this bad?
4295+
///
4296+
/// `Iterator::last` is implemented by consuming the iterator, which is unnecessary if
4297+
/// the iterator is a `DoubleEndedIterator`. Since Rust traits do not allow specialization,
4298+
/// `Iterator::last` cannot be optimized for `DoubleEndedIterator`.
4299+
///
4300+
/// ### Example
4301+
/// ```no_run
4302+
/// let last_arg = "echo hello world".split(' ').last();
4303+
/// ```
4304+
/// Use instead:
4305+
/// ```no_run
4306+
/// let last_arg = "echo hello world".split(' ').next_back();
4307+
/// ```
4308+
#[clippy::version = "1.85.0"]
4309+
pub DOUBLE_ENDED_ITERATOR_LAST,
4310+
perf,
4311+
"using `Iterator::last` on a `DoubleEndedIterator`"
4312+
}
4313+
42874314
pub struct Methods {
42884315
avoid_breaking_exported_api: bool,
42894316
msrv: Msrv,
@@ -4449,6 +4476,7 @@ impl_lint_pass!(Methods => [
44494476
MAP_ALL_ANY_IDENTITY,
44504477
MAP_WITH_UNUSED_ARGUMENT_OVER_RANGES,
44514478
UNNECESSARY_MAP_OR,
4479+
DOUBLE_ENDED_ITERATOR_LAST,
44524480
]);
44534481

44544482
/// Extracts a method call name, args, and `Span` of the method name.
@@ -4931,6 +4959,7 @@ impl Methods {
49314959
false,
49324960
);
49334961
}
4962+
double_ended_iterator_last::check(cx, expr, recv, call_span);
49344963
},
49354964
("len", []) => {
49364965
if let Some(("as_bytes", prev_recv, [], _, _)) = method_call(recv) {
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
#![warn(clippy::double_ended_iterator_last)]
2+
3+
// Typical case
4+
pub fn last_arg(s: &str) -> Option<&str> {
5+
s.split(' ').next_back()
6+
}
7+
8+
fn main() {
9+
// General case
10+
struct DeIterator;
11+
impl Iterator for DeIterator {
12+
type Item = ();
13+
fn next(&mut self) -> Option<Self::Item> {
14+
Some(())
15+
}
16+
}
17+
impl DoubleEndedIterator for DeIterator {
18+
fn next_back(&mut self) -> Option<Self::Item> {
19+
Some(())
20+
}
21+
}
22+
let _ = DeIterator.next_back();
23+
// Should not apply to other methods of Iterator
24+
let _ = DeIterator.count();
25+
26+
// Should not apply to simple iterators
27+
struct SimpleIterator;
28+
impl Iterator for SimpleIterator {
29+
type Item = ();
30+
fn next(&mut self) -> Option<Self::Item> {
31+
Some(())
32+
}
33+
}
34+
let _ = SimpleIterator.last();
35+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
#![warn(clippy::double_ended_iterator_last)]
2+
3+
// Typical case
4+
pub fn last_arg(s: &str) -> Option<&str> {
5+
s.split(' ').last()
6+
}
7+
8+
fn main() {
9+
// General case
10+
struct DeIterator;
11+
impl Iterator for DeIterator {
12+
type Item = ();
13+
fn next(&mut self) -> Option<Self::Item> {
14+
Some(())
15+
}
16+
}
17+
impl DoubleEndedIterator for DeIterator {
18+
fn next_back(&mut self) -> Option<Self::Item> {
19+
Some(())
20+
}
21+
}
22+
let _ = DeIterator.last();
23+
// Should not apply to other methods of Iterator
24+
let _ = DeIterator.count();
25+
26+
// Should not apply to simple iterators
27+
struct SimpleIterator;
28+
impl Iterator for SimpleIterator {
29+
type Item = ();
30+
fn next(&mut self) -> Option<Self::Item> {
31+
Some(())
32+
}
33+
}
34+
let _ = SimpleIterator.last();
35+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
error: called `Iterator::last` on a `DoubleEndedIterator`; this will needlessly iterate the entire iterator
2+
--> tests/ui/double_ended_iterator_last.rs:5:18
3+
|
4+
LL | s.split(' ').last()
5+
| ^^^^^^ help: try: `next_back()`
6+
|
7+
= note: `-D clippy::double-ended-iterator-last` implied by `-D warnings`
8+
= help: to override `-D warnings` add `#[allow(clippy::double_ended_iterator_last)]`
9+
10+
error: called `Iterator::last` on a `DoubleEndedIterator`; this will needlessly iterate the entire iterator
11+
--> tests/ui/double_ended_iterator_last.rs:22:24
12+
|
13+
LL | let _ = DeIterator.last();
14+
| ^^^^^^ help: try: `next_back()`
15+
16+
error: aborting due to 2 previous errors
17+

0 commit comments

Comments
 (0)