Skip to content

Commit a6f952b

Browse files
committed
use visitors to support full block
1 parent e12409d commit a6f952b

File tree

7 files changed

+214
-123
lines changed

7 files changed

+214
-123
lines changed

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: 102 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
1+
use std::ops::ControlFlow;
2+
13
use clippy_utils::diagnostics::span_lint_and_then;
24
use clippy_utils::eq_expr_value;
35
use clippy_utils::source::snippet;
46
use clippy_utils::ty::is_type_diagnostic_item;
7+
use clippy_utils::visitors::for_each_expr;
58
use rustc_ast::LitKind;
69
use rustc_errors::Applicability;
7-
use rustc_hir::{ExprKind, Local, StmtKind, UnOp};
10+
use rustc_hir::{Expr, ExprKind, Local, Node, UnOp};
811
use rustc_lint::{LateContext, LateLintPass};
912
use rustc_session::declare_lint_pass;
10-
use rustc_span::{sym, Span};
13+
use rustc_span::sym;
1114

1215
declare_clippy_lint! {
1316
/// ### What it does
@@ -53,111 +56,106 @@ impl LateLintPass<'_> for UnnecessaryIndexing {
5356
&& (expr_ty.is_array_slice() || expr_ty.is_array() || is_type_diagnostic_item(cx, expr_ty, sym::Vec))
5457
&& let ExprKind::Block(block, _) = if_expr.then.kind
5558
{
56-
// checked if conditional is calling `is_empty` on a sequence, now check if the first
57-
// statement inside the 'if' statement does unchecked get of first element
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![];
5867

59-
// if calling a function on the indexing - local
60-
if let Some(first) = block.stmts.first()
61-
&& let StmtKind::Local(local) = first.kind
62-
&& let Some(init) = local.init
63-
&& let ExprKind::Call(_, args) | ExprKind::MethodCall(_, _, args, _) = init.kind
64-
{
65-
for arg_expr in args {
66-
if check_arg_stmt(cx, expr, arg_expr, conditional_receiver, if_expr.cond.span) {
67-
break;
68-
}
69-
}
70-
// if calling a function on the indexing - statement or expression
71-
} else if let Some(first) = block.stmts.first()
72-
&& let StmtKind::Expr(stmt_expr) | StmtKind::Semi(stmt_expr) = first.kind
73-
&& let ExprKind::Call(_, args) | ExprKind::MethodCall(_, _, args, _) = stmt_expr.kind
74-
{
75-
for arg_expr in args {
76-
if check_arg_stmt(cx, expr, arg_expr, conditional_receiver, if_expr.cond.span) {
77-
break;
78-
}
79-
}
80-
// if calling on a local which is not in itself a call or methodcall
81-
} else if let Some(first) = block.stmts.first()
82-
&& let StmtKind::Local(local) = first.kind
83-
&& let Some(_) = local.init
84-
{
85-
check_local_stmt(cx, expr, local, conditional_receiver, if_expr.cond.span);
86-
}
87-
}
88-
}
89-
}
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+
};
9086

91-
fn check_local_stmt(
92-
cx: &LateContext<'_>,
93-
expr: &'_ rustc_hir::Expr<'_>,
94-
local: &Local<'_>,
95-
conditional_receiver: &'_ rustc_hir::Expr<'_>,
96-
cond_span: Span,
97-
) {
98-
if let ExprKind::Index(receiver, index, _) = local.init.unwrap().kind
99-
&& let ExprKind::Lit(lit) = index.kind
100-
&& let LitKind::Int(val, _) = lit.node
101-
&& eq_expr_value(cx, receiver, conditional_receiver)
102-
&& val.0 == 0
103-
{
104-
span_lint_and_then(
105-
cx,
106-
UNNECESSARY_INDEXING,
107-
expr.span,
108-
"condition can be simplified with if..let syntax",
109-
|x| {
110-
x.span_suggestion(
111-
cond_span,
112-
"consider using if..let syntax (variable may need to be dereferenced)",
113-
format!(
114-
"let Some({}) = {}.first()",
115-
snippet(cx, local.pat.span, ".."),
116-
snippet(cx, receiver.span, "..")
117-
),
118-
Applicability::Unspecified,
119-
);
120-
x.span_suggestion(local.span, "remove this line", "", Applicability::MachineApplicable);
121-
},
122-
);
123-
}
124-
}
87+
ControlFlow::Continue::<()>(())
88+
});
12589

126-
fn check_arg_stmt(
127-
cx: &LateContext<'_>,
128-
expr: &'_ rustc_hir::Expr<'_>,
129-
arg_expr: &'_ rustc_hir::Expr<'_>,
130-
conditional_receiver: &'_ rustc_hir::Expr<'_>,
131-
cond_span: Span,
132-
) -> bool {
133-
if let ExprKind::Index(receiver, index, _) = arg_expr.kind
134-
&& let ExprKind::Lit(lit) = index.kind
135-
&& let LitKind::Int(val, _) = lit.node
136-
&& eq_expr_value(cx, receiver, conditional_receiver)
137-
&& val.0 == 0
138-
{
139-
span_lint_and_then(
140-
cx,
141-
UNNECESSARY_INDEXING,
142-
expr.span,
143-
"condition can be simplified with if..let syntax",
144-
|x| {
145-
x.multipart_suggestion(
146-
"consider using if..let syntax (variable may need to be dereferenced)",
147-
vec![
148-
(
149-
cond_span,
150-
format!("let Some(element) = {}.first()", snippet(cx, receiver.span, "..")),
151-
),
152-
(arg_expr.span, "element".to_owned()),
153-
],
154-
Applicability::Unspecified,
155-
);
156-
},
157-
);
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<_>>();
158124

159-
return true;
160-
}
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<_>>();
161136

162-
false
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+
}
163161
}

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
| ^^^^^

tests/ui/unnecessary_indexing.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
//@no-rustfix
22
#![allow(unused)]
3+
#![allow(dropping_copy_types)]
34
#![warn(clippy::unnecessary_indexing)]
45

56
fn c(x: i32) -> i32 {
@@ -52,6 +53,30 @@ fn main() {
5253
let b = a[0];
5354
}
5455

56+
// lint when access is not first line
57+
let a: &[i32] = &[1];
58+
if !a.is_empty() {
59+
dbg!(a);
60+
let b = a[0];
61+
}
62+
63+
// lint on multiple accesses/locals
64+
let a: &[i32] = &[1];
65+
if !a.is_empty() {
66+
dbg!(a);
67+
let b = a[0];
68+
let c = a[0];
69+
drop(a[0]);
70+
}
71+
72+
// lint on multiple accesses
73+
let a: &[i32] = &[1];
74+
if !a.is_empty() {
75+
dbg!(a);
76+
drop(a[0]);
77+
drop(a[0]);
78+
}
79+
5580
// dont lint when not accessing [0]
5681
let a: &[i32] = &[1, 2];
5782
if !a.is_empty() {

0 commit comments

Comments
 (0)