Skip to content

Commit b97784f

Browse files
committed
Auto merge of #8862 - Alexendoo:get-last-with-len, r=Jarcho,xFrednet
`get_last_with_len`: lint `VecDeque` and any deref to slice changelog: [`get_last_with_len`]: lint `VecDeque` and any deref to slice Previously only `Vec`s were linted, this will now catch any usages on slices, arrays, etc. It also suggests `.back()` for `VecDeque`s Also moves the lint into `methods/`
2 parents 67a0891 + 8558490 commit b97784f

10 files changed

+172
-125
lines changed

clippy_lints/src/get_last_with_len.rs

-107
This file was deleted.

clippy_lints/src/lib.register_all.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,6 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
9191
LintId::of(functions::NOT_UNSAFE_PTR_ARG_DEREF),
9292
LintId::of(functions::RESULT_UNIT_ERR),
9393
LintId::of(functions::TOO_MANY_ARGUMENTS),
94-
LintId::of(get_last_with_len::GET_LAST_WITH_LEN),
9594
LintId::of(identity_op::IDENTITY_OP),
9695
LintId::of(if_let_mutex::IF_LET_MUTEX),
9796
LintId::of(indexing_slicing::OUT_OF_BOUNDS_INDEXING),
@@ -166,6 +165,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
166165
LintId::of(methods::FILTER_MAP_IDENTITY),
167166
LintId::of(methods::FILTER_NEXT),
168167
LintId::of(methods::FLAT_MAP_IDENTITY),
168+
LintId::of(methods::GET_LAST_WITH_LEN),
169169
LintId::of(methods::INSPECT_FOR_EACH),
170170
LintId::of(methods::INTO_ITER_ON_REF),
171171
LintId::of(methods::IS_DIGIT_ASCII_RADIX),

clippy_lints/src/lib.register_complexity.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ store.register_group(true, "clippy::complexity", Some("clippy_complexity"), vec!
1515
LintId::of(explicit_write::EXPLICIT_WRITE),
1616
LintId::of(format::USELESS_FORMAT),
1717
LintId::of(functions::TOO_MANY_ARGUMENTS),
18-
LintId::of(get_last_with_len::GET_LAST_WITH_LEN),
1918
LintId::of(identity_op::IDENTITY_OP),
2019
LintId::of(int_plus_one::INT_PLUS_ONE),
2120
LintId::of(lifetimes::EXTRA_UNUSED_LIFETIMES),
@@ -37,6 +36,7 @@ store.register_group(true, "clippy::complexity", Some("clippy_complexity"), vec!
3736
LintId::of(methods::FILTER_MAP_IDENTITY),
3837
LintId::of(methods::FILTER_NEXT),
3938
LintId::of(methods::FLAT_MAP_IDENTITY),
39+
LintId::of(methods::GET_LAST_WITH_LEN),
4040
LintId::of(methods::INSPECT_FOR_EACH),
4141
LintId::of(methods::ITER_COUNT),
4242
LintId::of(methods::MANUAL_FILTER_MAP),

clippy_lints/src/lib.register_lints.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,6 @@ store.register_lints(&[
183183
functions::TOO_MANY_ARGUMENTS,
184184
functions::TOO_MANY_LINES,
185185
future_not_send::FUTURE_NOT_SEND,
186-
get_last_with_len::GET_LAST_WITH_LEN,
187186
identity_op::IDENTITY_OP,
188187
if_let_mutex::IF_LET_MUTEX,
189188
if_not_else::IF_NOT_ELSE,
@@ -302,6 +301,7 @@ store.register_lints(&[
302301
methods::FLAT_MAP_IDENTITY,
303302
methods::FLAT_MAP_OPTION,
304303
methods::FROM_ITER_INSTEAD_OF_COLLECT,
304+
methods::GET_LAST_WITH_LEN,
305305
methods::GET_UNWRAP,
306306
methods::IMPLICIT_CLONE,
307307
methods::INEFFICIENT_TO_STRING,

clippy_lints/src/lib.rs

-2
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,6 @@ mod from_over_into;
242242
mod from_str_radix_10;
243243
mod functions;
244244
mod future_not_send;
245-
mod get_last_with_len;
246245
mod identity_op;
247246
mod if_let_mutex;
248247
mod if_not_else;
@@ -652,7 +651,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
652651
store.register_late_pass(|| Box::new(strings::StringLitAsBytes));
653652
store.register_late_pass(|| Box::new(derive::Derive));
654653
store.register_late_pass(|| Box::new(derivable_impls::DerivableImpls));
655-
store.register_late_pass(|| Box::new(get_last_with_len::GetLastWithLen));
656654
store.register_late_pass(|| Box::new(drop_forget_ref::DropForgetRef));
657655
store.register_late_pass(|| Box::new(empty_enum::EmptyEnum));
658656
store.register_late_pass(|| Box::new(absurd_extreme_comparisons::AbsurdExtremeComparisons));
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
use clippy_utils::diagnostics::span_lint_and_sugg;
2+
use clippy_utils::source::snippet_with_applicability;
3+
use clippy_utils::SpanlessEq;
4+
use rustc_ast::LitKind;
5+
use rustc_errors::Applicability;
6+
use rustc_hir::{BinOpKind, Expr, ExprKind};
7+
use rustc_lint::LateContext;
8+
use rustc_middle::ty;
9+
use rustc_span::source_map::Spanned;
10+
use rustc_span::sym;
11+
12+
use super::GET_LAST_WITH_LEN;
13+
14+
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, arg: &Expr<'_>) {
15+
// Argument to "get" is a subtraction
16+
if let ExprKind::Binary(
17+
Spanned {
18+
node: BinOpKind::Sub, ..
19+
},
20+
lhs,
21+
rhs,
22+
) = arg.kind
23+
24+
// LHS of subtraction is "x.len()"
25+
&& let ExprKind::MethodCall(lhs_path, [lhs_recv], _) = &lhs.kind
26+
&& lhs_path.ident.name == sym::len
27+
28+
// RHS of subtraction is 1
29+
&& let ExprKind::Lit(rhs_lit) = &rhs.kind
30+
&& let LitKind::Int(1, ..) = rhs_lit.node
31+
32+
// check that recv == lhs_recv `recv.get(lhs_recv.len() - 1)`
33+
&& SpanlessEq::new(cx).eq_expr(recv, lhs_recv)
34+
&& !recv.can_have_side_effects()
35+
{
36+
let method = match cx.typeck_results().expr_ty_adjusted(recv).peel_refs().kind() {
37+
ty::Adt(def, _) if cx.tcx.is_diagnostic_item(sym::VecDeque, def.did()) => "back",
38+
ty::Slice(_) => "last",
39+
_ => return,
40+
};
41+
42+
let mut applicability = Applicability::MachineApplicable;
43+
let recv_snippet = snippet_with_applicability(cx, recv.span, "_", &mut applicability);
44+
45+
span_lint_and_sugg(
46+
cx,
47+
GET_LAST_WITH_LEN,
48+
expr.span,
49+
&format!("accessing last element with `{recv_snippet}.get({recv_snippet}.len() - 1)`"),
50+
"try",
51+
format!("{recv_snippet}.{method}()"),
52+
applicability,
53+
);
54+
}
55+
}

clippy_lints/src/methods/mod.rs

+35
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ mod filter_next;
2121
mod flat_map_identity;
2222
mod flat_map_option;
2323
mod from_iter_instead_of_collect;
24+
mod get_last_with_len;
2425
mod get_unwrap;
2526
mod implicit_clone;
2627
mod inefficient_to_string;
@@ -1210,6 +1211,38 @@ declare_clippy_lint! {
12101211
"replace `.drain(..)` with `.into_iter()`"
12111212
}
12121213

1214+
declare_clippy_lint! {
1215+
/// ### What it does
1216+
/// Checks for using `x.get(x.len() - 1)` instead of
1217+
/// `x.last()`.
1218+
///
1219+
/// ### Why is this bad?
1220+
/// Using `x.last()` is easier to read and has the same
1221+
/// result.
1222+
///
1223+
/// Note that using `x[x.len() - 1]` is semantically different from
1224+
/// `x.last()`. Indexing into the array will panic on out-of-bounds
1225+
/// accesses, while `x.get()` and `x.last()` will return `None`.
1226+
///
1227+
/// There is another lint (get_unwrap) that covers the case of using
1228+
/// `x.get(index).unwrap()` instead of `x[index]`.
1229+
///
1230+
/// ### Example
1231+
/// ```rust
1232+
/// // Bad
1233+
/// let x = vec![2, 3, 5];
1234+
/// let last_element = x.get(x.len() - 1);
1235+
///
1236+
/// // Good
1237+
/// let x = vec![2, 3, 5];
1238+
/// let last_element = x.last();
1239+
/// ```
1240+
#[clippy::version = "1.37.0"]
1241+
pub GET_LAST_WITH_LEN,
1242+
complexity,
1243+
"Using `x.get(x.len() - 1)` when `x.last()` is correct and simpler"
1244+
}
1245+
12131246
declare_clippy_lint! {
12141247
/// ### What it does
12151248
/// Checks for use of `.get().unwrap()` (or
@@ -2283,6 +2316,7 @@ impl_lint_pass!(Methods => [
22832316
BYTES_NTH,
22842317
ITER_SKIP_NEXT,
22852318
GET_UNWRAP,
2319+
GET_LAST_WITH_LEN,
22862320
STRING_EXTEND_CHARS,
22872321
ITER_CLONED_COLLECT,
22882322
ITER_WITH_DRAIN,
@@ -2610,6 +2644,7 @@ impl Methods {
26102644
inspect_for_each::check(cx, expr, span2);
26112645
}
26122646
},
2647+
("get", [arg]) => get_last_with_len::check(cx, expr, recv, arg),
26132648
("get_or_insert_with", [arg]) => unnecessary_lazy_eval::check(cx, expr, recv, arg, "get_or_insert"),
26142649
("is_file", []) => filetype_is_file::check(cx, expr, recv),
26152650
("is_digit", [radix]) => is_digit_ascii_radix::check(cx, expr, recv, radix, self.msrv),

tests/ui/get_last_with_len.fixed

+23-5
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
// run-rustfix
22

33
#![warn(clippy::get_last_with_len)]
4+
#![allow(unused)]
5+
6+
use std::collections::VecDeque;
47

58
fn dont_use_last() {
69
let x = vec![2, 3, 5];
7-
let _ = x.last(); // ~ERROR Use x.last()
10+
let _ = x.last();
811
}
912

1013
fn indexing_two_from_end() {
@@ -23,9 +26,24 @@ fn use_last_with_different_vec_length() {
2326
let _ = x.get(y.len() - 1);
2427
}
2528

29+
struct S {
30+
field: Vec<usize>,
31+
}
32+
33+
fn in_field(s: &S) {
34+
let _ = s.field.last();
35+
}
36+
2637
fn main() {
27-
dont_use_last();
28-
indexing_two_from_end();
29-
index_into_last();
30-
use_last_with_different_vec_length();
38+
let slice = &[1, 2, 3];
39+
let _ = slice.last();
40+
41+
let array = [4, 5, 6];
42+
let _ = array.last();
43+
44+
let deq = VecDeque::from([7, 8, 9]);
45+
let _ = deq.back();
46+
47+
let nested = [[1]];
48+
let _ = nested[0].last();
3149
}

tests/ui/get_last_with_len.rs

+23-5
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
// run-rustfix
22

33
#![warn(clippy::get_last_with_len)]
4+
#![allow(unused)]
5+
6+
use std::collections::VecDeque;
47

58
fn dont_use_last() {
69
let x = vec![2, 3, 5];
7-
let _ = x.get(x.len() - 1); // ~ERROR Use x.last()
10+
let _ = x.get(x.len() - 1);
811
}
912

1013
fn indexing_two_from_end() {
@@ -23,9 +26,24 @@ fn use_last_with_different_vec_length() {
2326
let _ = x.get(y.len() - 1);
2427
}
2528

29+
struct S {
30+
field: Vec<usize>,
31+
}
32+
33+
fn in_field(s: &S) {
34+
let _ = s.field.get(s.field.len() - 1);
35+
}
36+
2637
fn main() {
27-
dont_use_last();
28-
indexing_two_from_end();
29-
index_into_last();
30-
use_last_with_different_vec_length();
38+
let slice = &[1, 2, 3];
39+
let _ = slice.get(slice.len() - 1);
40+
41+
let array = [4, 5, 6];
42+
let _ = array.get(array.len() - 1);
43+
44+
let deq = VecDeque::from([7, 8, 9]);
45+
let _ = deq.get(deq.len() - 1);
46+
47+
let nested = [[1]];
48+
let _ = nested[0].get(nested[0].len() - 1);
3149
}

0 commit comments

Comments
 (0)