Skip to content

Commit ff31e49

Browse files
committed
new lint: unnecessary_indexing
1 parent 99e8000 commit ff31e49

File tree

10 files changed

+440
-12
lines changed

10 files changed

+440
-12
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5739,6 +5739,7 @@ Released 2018-09-13
57395739
[`unnecessary_find_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_find_map
57405740
[`unnecessary_fold`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_fold
57415741
[`unnecessary_get_then_check`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_get_then_check
5742+
[`unnecessary_indexing`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_indexing
57425743
[`unnecessary_join`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_join
57435744
[`unnecessary_lazy_evaluations`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_lazy_evaluations
57445745
[`unnecessary_literal_unwrap`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_literal_unwrap

clippy_lints/src/declared_lints.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -717,6 +717,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
717717
crate::unit_types::UNIT_CMP_INFO,
718718
crate::unnamed_address::FN_ADDRESS_COMPARISONS_INFO,
719719
crate::unnecessary_box_returns::UNNECESSARY_BOX_RETURNS_INFO,
720+
crate::unnecessary_indexing::UNNECESSARY_INDEXING_INFO,
720721
crate::unnecessary_map_on_constructor::UNNECESSARY_MAP_ON_CONSTRUCTOR_INFO,
721722
crate::unnecessary_owned_empty_strings::UNNECESSARY_OWNED_EMPTY_STRINGS_INFO,
722723
crate::unnecessary_self_imports::UNNECESSARY_SELF_IMPORTS_INFO,

clippy_lints/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,7 @@ mod unit_return_expecting_ord;
350350
mod unit_types;
351351
mod unnamed_address;
352352
mod unnecessary_box_returns;
353+
mod unnecessary_indexing;
353354
mod unnecessary_map_on_constructor;
354355
mod unnecessary_owned_empty_strings;
355356
mod unnecessary_self_imports;
@@ -1124,6 +1125,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
11241125
store.register_late_pass(|_| Box::new(assigning_clones::AssigningClones));
11251126
store.register_late_pass(|_| Box::new(zero_repeat_side_effects::ZeroRepeatSideEffects));
11261127
store.register_late_pass(|_| Box::new(manual_unwrap_or_default::ManualUnwrapOrDefault));
1128+
store.register_late_pass(|_| Box::new(unnecessary_indexing::UnnecessaryIndexing));
11271129
// add lints here, do not remove this comment, it's used in `new_lint`
11281130
}
11291131

clippy_lints/src/manual_strip.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ impl<'tcx> LateLintPass<'tcx> for ManualStrip {
9595
}
9696

9797
let strippings = find_stripping(cx, strip_kind, target_res, pattern, then);
98-
if !strippings.is_empty() {
98+
if let Some(first_stripping) = strippings.first() {
9999
let kind_word = match strip_kind {
100100
StripKind::Prefix => "prefix",
101101
StripKind::Suffix => "suffix",
@@ -105,7 +105,7 @@ impl<'tcx> LateLintPass<'tcx> for ManualStrip {
105105
span_lint_and_then(
106106
cx,
107107
MANUAL_STRIP,
108-
strippings[0],
108+
*first_stripping,
109109
&format!("stripping a {kind_word} manually"),
110110
|diag| {
111111
diag.span_note(test_span, format!("the {kind_word} was tested here"));
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
use std::ops::ControlFlow;
2+
3+
use clippy_utils::diagnostics::span_lint_and_then;
4+
use clippy_utils::eq_expr_value;
5+
use clippy_utils::source::snippet;
6+
use clippy_utils::ty::is_type_diagnostic_item;
7+
use clippy_utils::visitors::for_each_expr;
8+
use rustc_ast::LitKind;
9+
use rustc_errors::Applicability;
10+
use rustc_hir::{Expr, ExprKind, Local, Node, UnOp};
11+
use rustc_lint::{LateContext, LateLintPass};
12+
use rustc_session::declare_lint_pass;
13+
use rustc_span::sym;
14+
15+
declare_clippy_lint! {
16+
/// ### What it does
17+
/// Checks for the use of `seq.is_empty()` in an if-conditional where `seq` is a slice, array, or Vec,
18+
/// and in which the first element of the sequence is indexed.
19+
///
20+
/// ### Why is this bad?
21+
/// This code is unnecessarily complicated and can instead be simplified to the use of an
22+
/// if..let expression which accessed the first element of the sequence.
23+
///
24+
/// ### Example
25+
/// ```no_run
26+
/// let a: &[i32] = &[1];
27+
/// if !a.is_empty() {
28+
/// let b = a[0];
29+
/// }
30+
/// ```
31+
/// Use instead:
32+
/// ```no_run
33+
/// let a: &[i32] = &[1];
34+
/// if let Some(b) = a.first() {
35+
///
36+
/// }
37+
/// ```
38+
#[clippy::version = "1.78.0"]
39+
pub UNNECESSARY_INDEXING,
40+
complexity,
41+
"unnecessary use of `seq.is_empty()` in a conditional when if..let is more appropriate"
42+
}
43+
44+
declare_lint_pass!(UnnecessaryIndexing => [UNNECESSARY_INDEXING]);
45+
46+
impl LateLintPass<'_> for UnnecessaryIndexing {
47+
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &'_ rustc_hir::Expr<'_>) {
48+
if let Some(if_expr) = clippy_utils::higher::If::hir(expr)
49+
// check for negation
50+
&& let ExprKind::Unary(op, unary_inner) = if_expr.cond.kind
51+
&& UnOp::Not == op
52+
// check for call of is_empty
53+
&& let ExprKind::MethodCall(method, conditional_receiver, _, _) = unary_inner.kind
54+
&& method.ident.as_str() == "is_empty"
55+
&& let expr_ty = cx.typeck_results().expr_ty(conditional_receiver).peel_refs()
56+
&& (expr_ty.is_array_slice() || expr_ty.is_array() || is_type_diagnostic_item(cx, expr_ty, sym::Vec))
57+
&& let ExprKind::Block(block, _) = if_expr.then.kind
58+
{
59+
// the receiver for the index operation
60+
let mut index_receiver: Option<&Expr<'_>> = None;
61+
// first local in the block - used as pattern for `Some(pat)`
62+
let mut first_local: Option<&Local<'_>> = None;
63+
// any other locals to be aware of, these are set to the value of `pat`
64+
let mut extra_locals: Vec<&Local<'_>> = vec![];
65+
// any other index expressions to replace with `pat` (or "element" if no local exists)
66+
let mut extra_exprs: Vec<&Expr<'_>> = vec![];
67+
68+
for_each_expr(block.stmts, |x| {
69+
if let ExprKind::Index(receiver, index, _) = x.kind
70+
&& let ExprKind::Lit(lit) = index.kind
71+
&& let LitKind::Int(val, _) = lit.node
72+
&& eq_expr_value(cx, receiver, conditional_receiver)
73+
&& val.0 == 0
74+
{
75+
index_receiver = Some(receiver);
76+
if let Node::Local(local) = cx.tcx.parent_hir_node(x.hir_id) {
77+
if first_local.is_none() {
78+
first_local = Some(local);
79+
} else {
80+
extra_locals.push(local);
81+
};
82+
} else {
83+
extra_exprs.push(x);
84+
};
85+
};
86+
87+
ControlFlow::Continue::<()>(())
88+
});
89+
90+
if let Some(receiver) = index_receiver {
91+
span_lint_and_then(
92+
cx,
93+
UNNECESSARY_INDEXING,
94+
expr.span,
95+
"condition can be simplified with if..let syntax",
96+
|x| {
97+
if let Some(first_local) = first_local {
98+
x.span_suggestion(
99+
if_expr.cond.span,
100+
"consider using if..let syntax (variable may need to be dereferenced)",
101+
format!(
102+
"let Some({}) = {}.first()",
103+
snippet(cx, first_local.pat.span, ".."),
104+
snippet(cx, receiver.span, "..")
105+
),
106+
Applicability::Unspecified,
107+
);
108+
x.span_suggestion(
109+
first_local.span,
110+
"remove this line",
111+
"",
112+
Applicability::MachineApplicable,
113+
);
114+
if !extra_locals.is_empty() {
115+
let extra_local_suggestions = extra_locals
116+
.iter()
117+
.map(|x| {
118+
(
119+
x.init.unwrap().span,
120+
snippet(cx, first_local.pat.span, "..").to_string(),
121+
)
122+
})
123+
.collect::<Vec<_>>();
124+
125+
x.multipart_suggestion(
126+
"initialize this variable to be the `Some` variant (may need dereferencing)",
127+
extra_local_suggestions,
128+
Applicability::Unspecified,
129+
);
130+
}
131+
if !extra_exprs.is_empty() {
132+
let index_accesses = extra_exprs
133+
.iter()
134+
.map(|x| (x.span, snippet(cx, first_local.pat.span, "..").to_string()))
135+
.collect::<Vec<_>>();
136+
137+
x.multipart_suggestion(
138+
"set this index to be the `Some` variant (may need dereferencing)",
139+
index_accesses,
140+
Applicability::Unspecified,
141+
);
142+
}
143+
} else {
144+
let mut index_accesses = vec![(
145+
if_expr.cond.span,
146+
format!("let Some(element) = {}.first()", snippet(cx, receiver.span, "..")),
147+
)];
148+
index_accesses.extend(extra_exprs.iter().map(|x| (x.span, "element".to_owned())));
149+
150+
x.multipart_suggestion(
151+
"consider using if..let syntax (variable may need to be dereferenced)",
152+
index_accesses,
153+
Applicability::Unspecified,
154+
);
155+
}
156+
},
157+
);
158+
}
159+
}
160+
}
161+
}

tests/ui/index_refutable_slice/if_let_slice_binding.fixed

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#![deny(clippy::index_refutable_slice)]
22
#![allow(clippy::uninlined_format_args)]
3+
#![allow(clippy::unnecessary_indexing)]
34

45
enum SomeEnum<T> {
56
One(T),

tests/ui/index_refutable_slice/if_let_slice_binding.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#![deny(clippy::index_refutable_slice)]
22
#![allow(clippy::uninlined_format_args)]
3+
#![allow(clippy::unnecessary_indexing)]
34

45
enum SomeEnum<T> {
56
One(T),

tests/ui/index_refutable_slice/if_let_slice_binding.stderr

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
error: this binding can be a slice pattern to avoid indexing
2-
--> tests/ui/index_refutable_slice/if_let_slice_binding.rs:14:17
2+
--> tests/ui/index_refutable_slice/if_let_slice_binding.rs:15:17
33
|
44
LL | if let Some(slice) = slice {
55
| ^^^^^
@@ -19,7 +19,7 @@ LL | println!("{}", slice_0);
1919
| ~~~~~~~
2020

2121
error: this binding can be a slice pattern to avoid indexing
22-
--> tests/ui/index_refutable_slice/if_let_slice_binding.rs:21:17
22+
--> tests/ui/index_refutable_slice/if_let_slice_binding.rs:22:17
2323
|
2424
LL | if let Some(slice) = slice {
2525
| ^^^^^
@@ -34,7 +34,7 @@ LL | println!("{}", slice_0);
3434
| ~~~~~~~
3535

3636
error: this binding can be a slice pattern to avoid indexing
37-
--> tests/ui/index_refutable_slice/if_let_slice_binding.rs:28:17
37+
--> tests/ui/index_refutable_slice/if_let_slice_binding.rs:29:17
3838
|
3939
LL | if let Some(slice) = slice {
4040
| ^^^^^
@@ -50,7 +50,7 @@ LL ~ println!("{}", slice_0);
5050
|
5151

5252
error: this binding can be a slice pattern to avoid indexing
53-
--> tests/ui/index_refutable_slice/if_let_slice_binding.rs:36:26
53+
--> tests/ui/index_refutable_slice/if_let_slice_binding.rs:37:26
5454
|
5555
LL | if let SomeEnum::One(slice) | SomeEnum::Three(slice) = slice_wrapped {
5656
| ^^^^^
@@ -65,7 +65,7 @@ LL | println!("{}", slice_0);
6565
| ~~~~~~~
6666

6767
error: this binding can be a slice pattern to avoid indexing
68-
--> tests/ui/index_refutable_slice/if_let_slice_binding.rs:44:29
68+
--> tests/ui/index_refutable_slice/if_let_slice_binding.rs:45:29
6969
|
7070
LL | if let (SomeEnum::Three(a), Some(b)) = (a_wrapped, b_wrapped) {
7171
| ^
@@ -80,7 +80,7 @@ LL | println!("{} -> {}", a_2, b[1]);
8080
| ~~~
8181

8282
error: this binding can be a slice pattern to avoid indexing
83-
--> tests/ui/index_refutable_slice/if_let_slice_binding.rs:44:38
83+
--> tests/ui/index_refutable_slice/if_let_slice_binding.rs:45:38
8484
|
8585
LL | if let (SomeEnum::Three(a), Some(b)) = (a_wrapped, b_wrapped) {
8686
| ^
@@ -95,7 +95,7 @@ LL | println!("{} -> {}", a[2], b_1);
9595
| ~~~
9696

9797
error: this binding can be a slice pattern to avoid indexing
98-
--> tests/ui/index_refutable_slice/if_let_slice_binding.rs:53:21
98+
--> tests/ui/index_refutable_slice/if_let_slice_binding.rs:54:21
9999
|
100100
LL | if let Some(ref slice) = slice {
101101
| ^^^^^
@@ -110,7 +110,7 @@ LL | println!("{:?}", slice_1);
110110
| ~~~~~~~
111111

112112
error: this binding can be a slice pattern to avoid indexing
113-
--> tests/ui/index_refutable_slice/if_let_slice_binding.rs:62:17
113+
--> tests/ui/index_refutable_slice/if_let_slice_binding.rs:63:17
114114
|
115115
LL | if let Some(slice) = &slice {
116116
| ^^^^^
@@ -125,7 +125,7 @@ LL | println!("{:?}", slice_0);
125125
| ~~~~~~~
126126

127127
error: this binding can be a slice pattern to avoid indexing
128-
--> tests/ui/index_refutable_slice/if_let_slice_binding.rs:132:17
128+
--> tests/ui/index_refutable_slice/if_let_slice_binding.rs:133:17
129129
|
130130
LL | if let Some(slice) = wrap.inner {
131131
| ^^^^^
@@ -140,7 +140,7 @@ LL | println!("This is awesome! {}", slice_0);
140140
| ~~~~~~~
141141

142142
error: this binding can be a slice pattern to avoid indexing
143-
--> tests/ui/index_refutable_slice/if_let_slice_binding.rs:140:17
143+
--> tests/ui/index_refutable_slice/if_let_slice_binding.rs:141:17
144144
|
145145
LL | if let Some(slice) = wrap.inner {
146146
| ^^^^^

0 commit comments

Comments
 (0)