Skip to content

Commit b38f173

Browse files
committed
Build suggestion within visitor + add more tests
1 parent 5ed93af commit b38f173

File tree

6 files changed

+355
-294
lines changed

6 files changed

+355
-294
lines changed

clippy_lints/src/methods/search_is_some.rs

Lines changed: 102 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
1-
use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg, span_lint_and_then};
1+
use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg};
22
use clippy_utils::source::{snippet, snippet_with_applicability};
33
use clippy_utils::ty::is_type_diagnostic_item;
4-
use clippy_utils::{is_trait_method, strip_pat_refs};
4+
use clippy_utils::{get_parent_expr_for_hir, is_trait_method, strip_pat_refs};
55
use if_chain::if_chain;
66
use rustc_errors::Applicability;
77
use rustc_hir as hir;
8-
use rustc_hir::{self, HirId, HirIdMap, HirIdSet, PatKind};
8+
use rustc_hir::{self, Expr, ExprKind, HirId, PatKind};
99
use rustc_infer::infer::TyCtxtInferExt;
1010
use rustc_lint::LateContext;
1111
use rustc_middle::hir::place::ProjectionKind;
12-
use rustc_middle::mir::FakeReadCause;
12+
use rustc_middle::mir::{FakeReadCause, Mutability};
1313
use rustc_middle::ty;
14-
use rustc_span::source_map::Span;
14+
use rustc_span::source_map::{BytePos, Span};
1515
use rustc_span::symbol::sym;
1616
use rustc_typeck::expr_use_visitor::{Delegate, ExprUseVisitor, PlaceBase, PlaceWithHirId};
1717

@@ -46,42 +46,12 @@ pub(super) fn check<'tcx>(
4646
if let hir::ExprKind::Closure(_, _, body_id, ..) = search_arg.kind;
4747
let closure_body = cx.tcx.hir().body(body_id);
4848
if let Some(closure_arg) = closure_body.params.get(0);
49-
5049
then {
5150
if let hir::PatKind::Ref(..) = closure_arg.pat.kind {
52-
Some((search_snippet.replacen('&', "", 1), None))
51+
Some(search_snippet.replacen('&', "", 1))
5352
} else if let PatKind::Binding(..) = strip_pat_refs(closure_arg.pat).kind {
54-
let mut visitor = DerefDelegate {
55-
cx,
56-
set: HirIdSet::default(),
57-
deref_suggs: HirIdMap::default(),
58-
borrow_suggs: HirIdMap::default()
59-
};
60-
61-
let fn_def_id = cx.tcx.hir().local_def_id(search_arg.hir_id);
62-
cx.tcx.infer_ctxt().enter(|infcx| {
63-
ExprUseVisitor::new(
64-
&mut visitor, &infcx, fn_def_id, cx.param_env, cx.typeck_results()
65-
).consume_body(closure_body);
66-
});
67-
68-
let replacements = if visitor.set.is_empty() {
69-
None
70-
} else {
71-
let mut deref_suggs = Vec::new();
72-
let mut borrow_suggs = Vec::new();
73-
for node in visitor.set {
74-
let span = cx.tcx.hir().span(node);
75-
if let Some(sugg) = visitor.deref_suggs.get(&node) {
76-
deref_suggs.push((span, sugg.clone()));
77-
}
78-
if let Some(sugg) = visitor.borrow_suggs.get(&node) {
79-
borrow_suggs.push((span, sugg.clone()));
80-
}
81-
}
82-
Some((deref_suggs, borrow_suggs))
83-
};
84-
Some((search_snippet.to_string(), replacements))
53+
get_closure_suggestion(cx, search_arg, closure_body)
54+
.or_else(|| Some(search_snippet.to_string()))
8555
} else {
8656
None
8757
}
@@ -90,38 +60,35 @@ pub(super) fn check<'tcx>(
9060
}
9161
};
9262
// add note if not multi-line
93-
let (closure_snippet, replacements) = any_search_snippet
94-
.as_ref()
95-
.map_or((&*search_snippet, None), |s| (&s.0, s.1.clone()));
96-
let (span, help, sugg) = if is_some {
97-
(
63+
if is_some {
64+
span_lint_and_sugg(
65+
cx,
66+
SEARCH_IS_SOME,
9867
method_span.with_hi(expr.span.hi()),
68+
&msg,
9969
"use `any()` instead",
100-
format!("any({})", closure_snippet),
101-
)
70+
format!(
71+
"any({})",
72+
any_search_snippet.as_ref().map_or(&*search_snippet, String::as_str)
73+
),
74+
Applicability::MachineApplicable,
75+
);
10276
} else {
10377
let iter = snippet(cx, search_recv.span, "..");
104-
(
78+
span_lint_and_sugg(
79+
cx,
80+
SEARCH_IS_SOME,
10581
expr.span,
82+
&msg,
10683
"use `!_.any()` instead",
107-
format!("!{}.any({})", iter, closure_snippet),
108-
)
109-
};
110-
111-
span_lint_and_then(cx, SEARCH_IS_SOME, span, &msg, |db| {
112-
if let Some((deref_suggs, borrow_suggs)) = replacements {
113-
db.span_suggestion(span, help, sugg, Applicability::MaybeIncorrect);
114-
115-
if !deref_suggs.is_empty() {
116-
db.multipart_suggestion("...and remove deref", deref_suggs, Applicability::MaybeIncorrect);
117-
}
118-
if !borrow_suggs.is_empty() {
119-
db.multipart_suggestion("...and borrow variable", borrow_suggs, Applicability::MaybeIncorrect);
120-
}
121-
} else {
122-
db.span_suggestion(span, help, sugg, Applicability::MachineApplicable);
123-
}
124-
});
84+
format!(
85+
"!{}.any({})",
86+
iter,
87+
any_search_snippet.as_ref().map_or(&*search_snippet, String::as_str)
88+
),
89+
Applicability::MachineApplicable,
90+
);
91+
}
12592
} else {
12693
let hint = format!(
12794
"this is more succinctly expressed by calling `any()`{}",
@@ -184,53 +151,86 @@ pub(super) fn check<'tcx>(
184151
}
185152
}
186153

154+
fn get_closure_suggestion<'tcx>(
155+
cx: &LateContext<'_>,
156+
search_arg: &'tcx hir::Expr<'_>,
157+
closure_body: &hir::Body<'_>,
158+
) -> Option<String> {
159+
let mut visitor = DerefDelegate {
160+
cx,
161+
closure_span: search_arg.span,
162+
next_pos: None,
163+
suggestion_start: String::new(),
164+
suggestion_end: String::new(),
165+
};
166+
167+
let fn_def_id = cx.tcx.hir().local_def_id(search_arg.hir_id);
168+
cx.tcx.infer_ctxt().enter(|infcx| {
169+
ExprUseVisitor::new(&mut visitor, &infcx, fn_def_id, cx.param_env, cx.typeck_results())
170+
.consume_body(closure_body);
171+
});
172+
173+
if visitor.suggestion_start.is_empty() {
174+
None
175+
} else {
176+
Some(format!("{}{}", visitor.suggestion_start, visitor.suggestion_end))
177+
}
178+
}
179+
187180
struct DerefDelegate<'a, 'tcx> {
188181
cx: &'a LateContext<'tcx>,
189-
set: HirIdSet,
190-
deref_suggs: HirIdMap<String>,
191-
borrow_suggs: HirIdMap<String>,
182+
closure_span: Span,
183+
next_pos: Option<BytePos>,
184+
suggestion_start: String,
185+
suggestion_end: String,
192186
}
193187

194188
impl<'tcx> Delegate<'tcx> for DerefDelegate<'_, 'tcx> {
195-
fn consume(&mut self, cmt: &PlaceWithHirId<'tcx>, _: HirId) {
189+
fn consume(&mut self, _: &PlaceWithHirId<'tcx>, _: HirId) {}
190+
191+
fn borrow(&mut self, cmt: &PlaceWithHirId<'tcx>, _: HirId, _: ty::BorrowKind) {
196192
if let PlaceBase::Local(id) = cmt.place.base {
197193
let map = self.cx.tcx.hir();
198-
if cmt.place.projections.is_empty() {
199-
self.set.insert(cmt.hir_id);
194+
let ident_str = map.name(id).to_string();
195+
let span = map.span(cmt.hir_id);
196+
let start_span = if let Some(next_pos) = self.next_pos {
197+
Span::new(next_pos, span.lo(), span.ctxt())
200198
} else {
201-
let mut replacement_str = map.name(id).to_string();
202-
let last_deref = cmt
203-
.place
204-
.projections
205-
.iter()
206-
.rposition(|proj| proj.kind == ProjectionKind::Deref);
199+
self.closure_span.until(span)
200+
};
201+
let start_snip = snippet(self.cx, start_span, "..");
202+
let end_span = Span::new(span.hi(), self.closure_span.hi(), span.ctxt());
203+
let end_snip = snippet(self.cx, end_span, "..");
207204

208-
if let Some(pos) = last_deref {
209-
let mut projections = cmt.place.projections.clone();
210-
projections.truncate(pos);
205+
if cmt.place.projections.is_empty() {
206+
self.suggestion_start.push_str(&format!("{}&{}", start_snip, ident_str));
207+
} else {
208+
let parent_expr = get_parent_expr_for_hir(self.cx, cmt.hir_id);
209+
if let Some(Expr { hir_id: _, kind, .. }) = parent_expr {
210+
if let ExprKind::Call(_, args) | ExprKind::MethodCall(_, _, args, _) = kind {
211+
let args_to_handle = args.iter().filter(|arg| arg.hir_id == cmt.hir_id).collect::<Vec<_>>();
212+
if !args_to_handle.is_empty() {
213+
for arg in &args_to_handle {
214+
let arg_ty_kind = self.cx.typeck_results().expr_ty(arg).kind();
215+
if matches!(arg_ty_kind, ty::Ref(_, _, Mutability::Not)) {
216+
let start_span = if let Some(next_pos) = self.next_pos {
217+
Span::new(next_pos, span.lo(), span.ctxt())
218+
} else {
219+
self.closure_span.until(span)
220+
};
221+
let start_snip = snippet(self.cx, start_span, "..");
211222

212-
for item in projections {
213-
if item.kind == ProjectionKind::Deref {
214-
replacement_str = format!("*{}", replacement_str);
223+
self.suggestion_start.push_str(&format!("{}&{}", start_snip, ident_str));
224+
self.suggestion_end = end_snip.to_string();
225+
self.next_pos = Some(span.hi());
226+
}
227+
}
228+
return;
215229
}
216230
}
217-
218-
self.set.insert(cmt.hir_id);
219-
self.deref_suggs.insert(cmt.hir_id, replacement_str);
220231
}
221-
}
222-
}
223-
}
224232

225-
fn borrow(&mut self, cmt: &PlaceWithHirId<'tcx>, _: HirId, _: ty::BorrowKind) {
226-
if let PlaceBase::Local(id) = cmt.place.base {
227-
let map = self.cx.tcx.hir();
228-
if cmt.place.projections.is_empty() {
229-
let replacement_str = format!("&{}", map.name(id).to_string());
230-
self.set.insert(cmt.hir_id);
231-
self.borrow_suggs.insert(cmt.hir_id, replacement_str);
232-
} else {
233-
let mut replacement_str = map.name(id).to_string();
233+
let mut replacement_str = ident_str;
234234
let last_deref = cmt
235235
.place
236236
.projections
@@ -246,11 +246,13 @@ impl<'tcx> Delegate<'tcx> for DerefDelegate<'_, 'tcx> {
246246
replacement_str = format!("*{}", replacement_str);
247247
}
248248
}
249-
250-
self.set.insert(cmt.hir_id);
251-
self.deref_suggs.insert(cmt.hir_id, replacement_str);
252249
}
250+
251+
self.suggestion_start
252+
.push_str(&format!("{}{}", start_snip, replacement_str));
253+
self.suggestion_end = end_snip.to_string();
253254
}
255+
self.next_pos = Some(span.hi());
254256
}
255257
}
256258

tests/ui/search_is_some.rs

Lines changed: 0 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,6 @@ fn main() {
3636
// check that we don't lint if `find()` is called with
3737
// `Pattern` that is not a string
3838
let _ = "hello world".find(|c: char| c == 'o' || c == 'l').is_some();
39-
40-
// Check `find().is_some()`, single-line case.
41-
let _ = (0..1).find(|x| **y == *x).is_some(); // one dereference less
42-
let _ = (0..1).find(|x| *x == 0).is_some();
43-
let _ = v.iter().find(|x| **x == 0).is_some();
4439
}
4540

4641
#[rustfmt::skip]
@@ -75,44 +70,4 @@ fn is_none() {
7570
// check that we don't lint if `find()` is called with
7671
// `Pattern` that is not a string
7772
let _ = "hello world".find(|c: char| c == 'o' || c == 'l').is_none();
78-
79-
// Check `find().is_none()`, single-line case.
80-
let _ = (0..1).find(|x| **y == *x).is_none(); // one dereference less
81-
let _ = (0..1).find(|x| *x == 0).is_none();
82-
let _ = v.iter().find(|x| **x == 0).is_none();
83-
}
84-
85-
#[allow(clippy::clone_on_copy, clippy::map_clone)]
86-
mod issue7392 {
87-
struct Player {
88-
hand: Vec<usize>,
89-
}
90-
fn filter() {
91-
let p = Player {
92-
hand: vec![1, 2, 3, 4, 5],
93-
};
94-
let filter_hand = vec![5];
95-
let _ = p
96-
.hand
97-
.iter()
98-
.filter(|c| filter_hand.iter().find(|cc| c == cc).is_none())
99-
.map(|c| c.clone())
100-
.collect::<Vec<_>>();
101-
}
102-
103-
struct PlayerTuple {
104-
hand: Vec<(usize, char)>,
105-
}
106-
fn filter_tuple() {
107-
let p = PlayerTuple {
108-
hand: vec![(1, 'a'), (2, 'b'), (3, 'c'), (4, 'd'), (5, 'e')],
109-
};
110-
let filter_hand = vec![5];
111-
let _ = p
112-
.hand
113-
.iter()
114-
.filter(|(c, _)| filter_hand.iter().find(|cc| c == *cc).is_none())
115-
.map(|c| c.clone())
116-
.collect::<Vec<_>>();
117-
}
11873
}

0 commit comments

Comments
 (0)