Skip to content

Commit f312266

Browse files
J-ZhengLiJ-ZhengLi
authored andcommitted
adjust applicability to MaybeIncorrect & don't lint with expanded if condition & lint messages etc.
1 parent c9703cc commit f312266

File tree

5 files changed

+300
-128
lines changed

5 files changed

+300
-128
lines changed

clippy_lints/src/declared_lints.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -751,8 +751,8 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[
751751
crate::unit_types::UNIT_ARG_INFO,
752752
crate::unit_types::UNIT_CMP_INFO,
753753
crate::unnecessary_box_returns::UNNECESSARY_BOX_RETURNS_INFO,
754-
crate::unnecessary_literal_bound::UNNECESSARY_LITERAL_BOUND_INFO,
755754
crate::unnecessary_indexing::UNNECESSARY_INDEXING_INFO,
755+
crate::unnecessary_literal_bound::UNNECESSARY_LITERAL_BOUND_INFO,
756756
crate::unnecessary_map_on_constructor::UNNECESSARY_MAP_ON_CONSTRUCTOR_INFO,
757757
crate::unnecessary_mut_passed::UNNECESSARY_MUT_PASSED_INFO,
758758
crate::unnecessary_owned_empty_strings::UNNECESSARY_OWNED_EMPTY_STRINGS_INFO,

clippy_lints/src/unnecessary_indexing.rs

Lines changed: 40 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use rustc_hir::{Block, Expr, ExprKind, HirId, LetStmt, Node, UnOp};
1111
use rustc_lint::{LateContext, LateLintPass};
1212
use rustc_middle::ty::adjustment::{Adjust, AutoBorrow, AutoBorrowMutability};
1313
use rustc_session::declare_lint_pass;
14-
use rustc_span::sym;
14+
use rustc_span::{Span, sym};
1515

1616
declare_clippy_lint! {
1717
/// ### What it does
@@ -36,7 +36,7 @@ declare_clippy_lint! {
3636
///
3737
/// }
3838
/// ```
39-
#[clippy::version = "1.78.0"]
39+
#[clippy::version = "1.86.0"]
4040
pub UNNECESSARY_INDEXING,
4141
complexity,
4242
"unnecessary use of `seq.is_empty()` in a conditional when if..let is more appropriate"
@@ -47,6 +47,7 @@ declare_lint_pass!(UnnecessaryIndexing => [UNNECESSARY_INDEXING]);
4747
impl<'tcx> LateLintPass<'tcx> for UnnecessaryIndexing {
4848
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'_ Expr<'tcx>) {
4949
if let Some(if_expr) = clippy_utils::higher::If::hir(expr)
50+
&& !if_expr.cond.span.from_expansion()
5051
// check for negation
5152
&& let ExprKind::Unary(UnOp::Not, unary_inner) = if_expr.cond.kind
5253
// check for call of is_empty
@@ -60,96 +61,65 @@ impl<'tcx> LateLintPass<'tcx> for UnnecessaryIndexing {
6061
&& expr_ty.ref_mutability() != Some(Mutability::Mut)
6162
&& let Some(con_path) = path_to_local(conditional_receiver)
6263
&& let Some(r) = process_indexing(cx, block, con_path)
63-
&& let Some(receiver) = r.index_receiver
64+
&& let Some(receiver_span) = r.index_receiver_span
6465
{
66+
let receiver = snippet(cx, receiver_span, "..");
67+
let mut suggestions: Vec<(Span, String)> = vec![];
68+
let mut message = "consider using `if..let` syntax instead of indexing".to_string();
6569
span_lint_and_then(
6670
cx,
6771
UNNECESSARY_INDEXING,
6872
expr.span,
69-
"condition can be simplified with if..let syntax",
73+
"condition can be simplified with `if..let` syntax",
7074
|diag| {
7175
if let Some(first_local) = r.first_local
72-
&& first_local.pat.simple_ident().is_some()
76+
&& let Some(name) = first_local.pat.simple_ident().map(|ident| ident.name)
7377
{
74-
diag.span_suggestion(
78+
suggestions.push((
7579
if_expr.cond.span,
76-
"consider using if..let syntax (variable may need to be dereferenced)",
7780
format!(
78-
"let Some({}{}) = {}.first()",
81+
"let Some({}{name}) = {receiver}.first()",
7982
// if we arent borrowing anything then we can pass a reference here for correctness
8083
if r.extra_exprs_borrow.is_empty() { "&" } else { "" },
81-
snippet(cx, first_local.pat.span, ".."),
82-
snippet(cx, receiver.span, "..")
8384
),
84-
Applicability::Unspecified,
85-
);
86-
diag.span_suggestion(first_local.span, "remove this line", "", Applicability::Unspecified);
87-
if !r.extra_exprs_borrow.is_empty() {
88-
let mut index_accesses = r
89-
.extra_exprs_borrow
90-
.iter()
91-
.map(|x| (x.span, snippet(cx, first_local.pat.span, "..").to_string()))
92-
.collect::<Vec<_>>();
85+
));
86+
suggestions.push((first_local.span, String::new()));
9387

94-
index_accesses.extend(
95-
r.extra_exprs_copy
88+
if !r.extra_exprs_borrow.is_empty() {
89+
suggestions.extend(
90+
r.extra_exprs_borrow
9691
.iter()
97-
.map(|x| (x.span, snippet(cx, first_local.pat.span, "..").to_string())),
92+
.chain(r.extra_exprs_copy.iter())
93+
.map(|x| (x.span, name.to_string())),
9894
);
9995

100-
diag.multipart_suggestion(
101-
"set this indexing to be the `Some` variant (may need dereferencing)",
102-
index_accesses,
103-
Applicability::Unspecified,
104-
);
96+
message.push_str(", and replacing indexing expression(s) with the value in `Some` variant");
10597
} else if !r.extra_exprs_copy.is_empty() {
106-
let index_accesses = r
107-
.extra_exprs_copy
108-
.iter()
109-
.map(|x| (x.span, snippet(cx, first_local.pat.span, "..").to_string()))
110-
.collect::<Vec<_>>();
111-
112-
diag.multipart_suggestion(
113-
"set this indexing to be the `Some` variant",
114-
index_accesses,
115-
Applicability::Unspecified,
116-
);
98+
suggestions.extend(r.extra_exprs_copy.iter().map(|x| (x.span, name.to_string())));
11799
}
118100
} else if r.extra_exprs_borrow.is_empty() {
119-
let mut index_accesses = vec![(
120-
if_expr.cond.span,
121-
format!("let Some(&element) = {}.first()", snippet(cx, receiver.span, "..")),
122-
)];
123-
index_accesses.extend(r.extra_exprs_copy.iter().map(|x| (x.span, "element".to_owned())));
124-
125-
diag.multipart_suggestion(
126-
"consider using if..let syntax",
127-
index_accesses,
128-
Applicability::Unspecified,
129-
);
101+
suggestions.push((if_expr.cond.span, format!("let Some(&element) = {receiver}.first()")));
102+
suggestions.extend(r.extra_exprs_copy.iter().map(|x| (x.span, "element".to_owned())));
130103
} else {
131-
let mut index_accesses = vec![(
132-
if_expr.cond.span,
133-
format!("let Some(element) = {}.first()", snippet(cx, receiver.span, "..")),
134-
)];
135-
index_accesses.extend(r.extra_exprs_borrow.iter().map(|x| (x.span, "element".to_owned())));
136-
index_accesses.extend(r.extra_exprs_copy.iter().map(|x| (x.span, "element".to_owned())));
137-
138-
diag.multipart_suggestion(
139-
"consider using if..let syntax (variable may need to be dereferenced)",
140-
index_accesses,
141-
Applicability::Unspecified,
104+
suggestions.push((if_expr.cond.span, format!("let Some(element) = {receiver}.first()")));
105+
suggestions.extend(
106+
r.extra_exprs_borrow
107+
.iter()
108+
.chain(r.extra_exprs_copy.iter())
109+
.map(|x| (x.span, "element".to_owned())),
142110
);
143111
}
112+
113+
diag.multipart_suggestion(message, suggestions, Applicability::MaybeIncorrect);
144114
},
145115
);
146116
}
147117
}
148118
}
149119

150120
struct IndexCheckResult<'a> {
151-
// the receiver for the index operation, only Some in the event the indexing is via a direct primitive
152-
index_receiver: Option<&'a Expr<'a>>,
121+
// span of the receiver for the index operation, only Some in the event the indexing is via a direct primitive
122+
index_receiver_span: Option<Span>,
153123
// first local in the block - used as pattern for `Some(pat)`
154124
first_local: Option<&'a LetStmt<'a>>,
155125
// any other index expressions to replace with `pat` (or "element" if no local exists)
@@ -166,27 +136,27 @@ fn process_indexing<'a>(
166136
block: &'a Block<'a>,
167137
conditional_receiver_hid: HirId,
168138
) -> Option<IndexCheckResult<'a>> {
169-
let mut index_receiver: Option<&Expr<'_>> = None;
139+
let mut index_receiver_span: Option<Span> = None;
170140
let mut first_local: Option<&LetStmt<'_>> = None;
171141
let mut extra_exprs_borrow: Vec<&Expr<'_>> = vec![];
172142
let mut extra_exprs_copy: Vec<&Expr<'_>> = vec![];
173143

174144
// if res == Some(()), then mutation occurred
175145
// & therefore we should not lint on this
176-
let res = for_each_expr(cx, block.stmts, |x| {
146+
let res = for_each_expr(cx, block, |x| {
177147
let adjustments = cx.typeck_results().expr_adjustments(x);
178148
if let ExprKind::Index(receiver, index, _) = x.kind
179149
&& let ExprKind::Lit(lit) = index.kind
180150
&& let LitKind::Int(val, _) = lit.node
181151
&& path_to_local_id(receiver, conditional_receiver_hid)
182152
&& val.0 == 0
183153
{
184-
index_receiver = Some(receiver);
154+
index_receiver_span = Some(receiver.span);
185155
if let Node::LetStmt(local) = cx.tcx.parent_hir_node(x.hir_id) {
186156
if first_local.is_none() {
187157
first_local = Some(local);
188158
return ControlFlow::Continue::<()>(());
189-
};
159+
}
190160
}
191161

192162
if let Node::Expr(x) = cx.tcx.parent_hir_node(x.hir_id)
@@ -195,24 +165,24 @@ fn process_indexing<'a>(
195165
extra_exprs_borrow.push(x);
196166
} else {
197167
extra_exprs_copy.push(x);
198-
};
168+
}
199169
} else if adjustments.iter().any(|adjustment| {
200170
matches!(
201171
adjustment.kind,
202-
Adjust::Borrow(AutoBorrow::Ref(_, AutoBorrowMutability::Mut { .. }))
172+
Adjust::Borrow(AutoBorrow::Ref(AutoBorrowMutability::Mut { .. }))
203173
)
204174
}) {
205175
// do not lint on mutable auto borrows (https://github.com/rust-lang/rust-clippy/pull/12464#discussion_r1600352696)
206176
return ControlFlow::Break(());
207177
} else if let ExprKind::AddrOf(_, Mutability::Mut, _) = x.kind {
208178
return ControlFlow::Break(());
209-
};
179+
}
210180

211181
ControlFlow::Continue::<()>(())
212182
});
213183

214184
res.is_none().then_some(IndexCheckResult {
215-
index_receiver,
185+
index_receiver_span,
216186
first_local,
217187
extra_exprs_borrow,
218188
extra_exprs_copy,
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
#![allow(unused)]
2+
#![allow(dropping_copy_types)]
3+
#![allow(dropping_references)]
4+
#![warn(clippy::unnecessary_indexing)]
5+
6+
macro_rules! not_empty {
7+
($seq:ident) => {
8+
!$seq.is_empty()
9+
};
10+
}
11+
12+
fn c(x: i32) -> i32 {
13+
println!("{x}");
14+
10
15+
}
16+
17+
struct Struct;
18+
impl Struct {
19+
pub fn a(x: i32) -> i32 {
20+
println!("{x}");
21+
10
22+
}
23+
}
24+
25+
fn main() {
26+
// lint on vecs with a call
27+
let a: Vec<i32> = vec![1];
28+
if let Some(&element) = a.first() {
29+
//~^ ERROR: condition can be simplified with `if..let` syntax
30+
let b = c(element);
31+
}
32+
33+
// lint on vecs with a method call
34+
let a: Vec<i32> = vec![1];
35+
if let Some(&element) = a.first() {
36+
//~^ ERROR: condition can be simplified with `if..let` syntax
37+
let b = Struct::a(element);
38+
}
39+
40+
// lint on arrays with a call
41+
let a: &[i32] = &[1];
42+
if let Some(&element) = a.first() {
43+
//~^ ERROR: condition can be simplified with `if..let` syntax
44+
let b = c(element);
45+
}
46+
47+
// lint on arrays with a method call
48+
let a: &[i32] = &[1];
49+
if let Some(&element) = a.first() {
50+
//~^ ERROR: condition can be simplified with `if..let` syntax
51+
let b = Struct::a(element);
52+
}
53+
54+
// lint on vecs with a local access
55+
let a: Vec<i32> = vec![1];
56+
if let Some(&b) = a.first() {
57+
//~^ ERROR: condition can be simplified with `if..let` syntax
58+
59+
}
60+
61+
// lint on arrays with a local access
62+
let a: &[i32] = &[1];
63+
if let Some(&b) = a.first() {
64+
//~^ ERROR: condition can be simplified with `if..let` syntax
65+
66+
}
67+
68+
// lint when access is not first line
69+
let a: &[i32] = &[1];
70+
if let Some(&b) = a.first() {
71+
//~^ ERROR: condition can be simplified with `if..let` syntax
72+
dbg!(a);
73+
74+
}
75+
76+
// lint on multiple accesses/locals
77+
let a: &[i32] = &[1];
78+
if let Some(c) = a.first() {
79+
//~^ ERROR: condition can be simplified with `if..let` syntax
80+
dbg!(a);
81+
let b = c;
82+
83+
drop(c);
84+
}
85+
86+
// lint on multiple accesses
87+
let a: &[i32] = &[1];
88+
if let Some(&element) = a.first() {
89+
//~^ ERROR: condition can be simplified with `if..let` syntax
90+
dbg!(a);
91+
drop(element);
92+
drop(element);
93+
}
94+
95+
let _first = if let Some(&element) = a.first() {
96+
//~^ ERROR: condition can be simplified with `if..let` syntax
97+
element
98+
} else {
99+
1
100+
};
101+
102+
// don't lint when the condition is from expansion
103+
if not_empty!(a) {
104+
let b = a[0];
105+
}
106+
107+
// dont lint when not accessing [0]
108+
let a: &[i32] = &[1, 2];
109+
if !a.is_empty() {
110+
let b = a[1];
111+
}
112+
113+
// dont lint when access is dynamic
114+
const T: usize = 0;
115+
116+
let a: &[i32] = &[1];
117+
if !a.is_empty() {
118+
let b = a[T];
119+
}
120+
121+
// dont lint without unary
122+
let a: &[i32] = &[1];
123+
if a.is_empty() {
124+
let b = a[0];
125+
}
126+
127+
// dont lint if we have mutable reference
128+
let mut a: &[i32] = &[1];
129+
if !a.is_empty() {
130+
drop(&mut a);
131+
let b = a[0];
132+
}
133+
134+
// dont lint if we have a mutable reference, even if the mutable reference occurs after what we are
135+
// linting against
136+
let mut a: &[i32] = &[1];
137+
if !a.is_empty() {
138+
let b = a[0];
139+
drop(&mut a);
140+
}
141+
142+
// dont lint on mutable auto borrow
143+
let mut a = vec![1, 2, 3];
144+
if !a.is_empty() {
145+
a.push(1);
146+
let b = a[0];
147+
b;
148+
}
149+
150+
// do not lint if conditional receiver is mutable reference
151+
let a = &mut vec![1, 2, 3];
152+
if !a.is_empty() {
153+
let b = a[0];
154+
a;
155+
b;
156+
}
157+
}

0 commit comments

Comments
 (0)