Skip to content

Commit 3360d24

Browse files
committed
New line: cloned_next
1 parent 0eff589 commit 3360d24

File tree

7 files changed

+110
-2
lines changed

7 files changed

+110
-2
lines changed

clippy_lints/src/lib.register_nursery.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ store.register_group(true, "clippy::nursery", Some("clippy_nursery"), vec![
1515
LintId::of(future_not_send::FUTURE_NOT_SEND),
1616
LintId::of(index_refutable_slice::INDEX_REFUTABLE_SLICE),
1717
LintId::of(let_if_seq::USELESS_LET_IF_SEQ),
18+
LintId::of(methods::CLONED_LAST),
1819
LintId::of(missing_const_for_fn::MISSING_CONST_FOR_FN),
1920
LintId::of(mutable_debug_assertion::DEBUG_ASSERT_WITH_MUT_CALL),
2021
LintId::of(mutex_atomic::MUTEX_INTEGER),

clippy_lints/src/matches.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -790,7 +790,7 @@ fn report_single_match_single_pattern(
790790
// will not: match Some(_) { &Some(_) => () }
791791
let ref_count_diff = ty_ref_count - pat_ref_count;
792792

793-
// Try to remove address of expressions first.
793+
// try to remove address of expressions first.
794794
let (ex, removed) = peel_n_hir_expr_refs(ex, ref_count_diff);
795795
let ref_count_diff = ref_count_diff - removed;
796796

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
use clippy_utils::diagnostics::{span_lint_and_sugg};
2+
use clippy_utils::source::snippet;
3+
use clippy_utils::ty::implements_trait;
4+
use rustc_errors::Applicability;
5+
use rustc_hir as hir;
6+
use rustc_lint::LateContext;
7+
use rustc_span::sym;
8+
9+
use super::CLONED_LAST;
10+
11+
/// lint use of `cloned().next()` for `Iterators`
12+
pub(super) fn check<'tcx>(
13+
cx: &LateContext<'tcx>,
14+
expr: &'tcx hir::Expr<'_>,
15+
recv: &'tcx hir::Expr<'_>,
16+
) {
17+
// lint if caller of `.filter().next()` is an Iterator
18+
let recv_impls_iterator = cx.tcx.get_diagnostic_item(sym::Iterator).map_or(false, |id| {
19+
implements_trait(cx, cx.typeck_results().expr_ty(recv), id, &[])
20+
});
21+
if recv_impls_iterator {
22+
let msg = "called `cloned().last()` on an `Iterator`. It may be more efficient to call
23+
`.last.cloned()` instead";
24+
let iter_snippet = snippet(cx, recv.span, "..");
25+
// add note if not multi-line
26+
span_lint_and_sugg(
27+
cx,
28+
CLONED_LAST,
29+
expr.span,
30+
msg,
31+
"try this",
32+
format!("{}.last().cloned()", iter_snippet),
33+
Applicability::MachineApplicable,
34+
);
35+
}
36+
}

clippy_lints/src/methods/mod.rs

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ mod chars_next_cmp_with_unwrap;
99
mod clone_on_copy;
1010
mod clone_on_ref_ptr;
1111
mod cloned_instead_of_copied;
12+
mod cloned_last;
1213
mod expect_fun_call;
1314
mod expect_used;
1415
mod extend_with_drain;
@@ -107,6 +108,29 @@ declare_clippy_lint! {
107108
"used `cloned` where `copied` could be used instead"
108109
}
109110

111+
declare_clippy_lint! {
112+
/// ### What it does
113+
/// Checks for usage of `_.cloned().last()`.
114+
///
115+
/// ### Why is this bad?
116+
/// Performance, clone only need to be executed once.
117+
///
118+
/// ### Example
119+
/// ```rust
120+
/// # let vec = vec!["string".to_string()];
121+
/// vec.iter().cloned().last();
122+
/// ```
123+
/// Could be written as
124+
/// ```rust
125+
/// # let vec = vec!["string".to_string()];
126+
/// vec.iter().last().cloned();
127+
/// ```
128+
#[clippy::version = "1.59.0"]
129+
pub CLONED_LAST,
130+
complexity,
131+
"using `cloned().last()`, which is less efficient than `.last().cloned()`"
132+
}
133+
110134
declare_clippy_lint! {
111135
/// ### What it does
112136
/// Checks for usages of `Iterator::flat_map()` where `filter_map()` could be
@@ -1946,6 +1970,7 @@ impl_lint_pass!(Methods => [
19461970
CLONE_ON_COPY,
19471971
CLONE_ON_REF_PTR,
19481972
CLONE_DOUBLE_REF,
1973+
CLONED_LAST,
19491974
CLONED_INSTEAD_OF_COPIED,
19501975
FLAT_MAP_OPTION,
19511976
INEFFICIENT_TO_STRING,
@@ -2232,7 +2257,9 @@ fn check_methods<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, msrv: Optio
22322257
("as_mut", []) => useless_asref::check(cx, expr, "as_mut", recv),
22332258
("as_ref", []) => useless_asref::check(cx, expr, "as_ref", recv),
22342259
("assume_init", []) => uninit_assumed_init::check(cx, expr, recv),
2235-
("cloned", []) => cloned_instead_of_copied::check(cx, expr, recv, span, msrv),
2260+
("cloned", []) => {
2261+
cloned_instead_of_copied::check(cx, expr, recv, span, msrv);
2262+
},
22362263
("collect", []) => match method_call!(recv) {
22372264
Some((name @ ("cloned" | "copied"), [recv2], _)) => {
22382265
iter_cloned_collect::check(cx, name, expr, recv2);
@@ -2285,6 +2312,14 @@ fn check_methods<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, msrv: Optio
22852312
("is_file", []) => filetype_is_file::check(cx, expr, recv),
22862313
("is_none", []) => check_is_some_is_none(cx, expr, recv, false),
22872314
("is_some", []) => check_is_some_is_none(cx, expr, recv, true),
2315+
("last", []) => {
2316+
if let Some((name, [recv, args @ ..], _)) = method_call!(recv) {
2317+
match (name, args) {
2318+
("cloned", []) => cloned_last::check(cx, expr, recv),
2319+
_ => {},
2320+
}
2321+
}
2322+
},
22882323
("map", [m_arg]) => {
22892324
if let Some((name, [recv2, args @ ..], span2)) = method_call!(recv) {
22902325
match (name, args) {

tests/ui/cloned_last.fixed

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// run-rustfix
2+
#![warn(clippy::nursery)]
3+
4+
fn main() {
5+
6+
#[rustfmt::skip]
7+
let _: Option<String> = vec!["1".to_string(), "2".to_string(), "3".to_string()]
8+
.iter().last().cloned();
9+
}

tests/ui/cloned_last.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// run-rustfix
2+
#![warn(clippy::nursery)]
3+
4+
fn main() {
5+
6+
#[rustfmt::skip]
7+
let _: Option<String> = vec!["1".to_string(), "2".to_string(), "3".to_string()]
8+
.iter().cloned().last();
9+
}

tests/ui/cloned_last.stderr

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
error: called `cloned().last()` on an `Iterator`. It may be more efficient to call
2+
`.last.cloned()` instead
3+
--> $DIR/cloned_last.rs:7:29
4+
|
5+
LL | let _: Option<String> = vec!["1".to_string(), "2".to_string(), "3".to_string()]
6+
| _____________________________^
7+
LL | | .iter().cloned().last();
8+
| |_______________________________^
9+
|
10+
= note: `-D clippy::cloned-last` implied by `-D warnings`
11+
help: try this
12+
|
13+
LL ~ let _: Option<String> = vec!["1".to_string(), "2".to_string(), "3".to_string()]
14+
LL ~ .iter().last().cloned();
15+
|
16+
17+
error: aborting due to previous error
18+

0 commit comments

Comments
 (0)