Skip to content

Commit fdd0e72

Browse files
committed
Auto merge of rust-lang#8649 - ebobrow:imperative_find, r=flip1995
add [`manual_find`] lint for function return case part of the implementation discussed in rust-lang#7143 changelog: add [`manual_find`] lint for function return case
2 parents eaa03ea + 430575b commit fdd0e72

15 files changed

+844
-31
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3520,6 +3520,7 @@ Released 2018-09-13
35203520
[`manual_async_fn`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_async_fn
35213521
[`manual_bits`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_bits
35223522
[`manual_filter_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_filter_map
3523+
[`manual_find`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_find
35233524
[`manual_find_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_find_map
35243525
[`manual_flatten`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_flatten
35253526
[`manual_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_map

clippy_lints/src/lib.register_all.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
119119
LintId::of(loops::FOR_KV_MAP),
120120
LintId::of(loops::FOR_LOOPS_OVER_FALLIBLES),
121121
LintId::of(loops::ITER_NEXT_LOOP),
122+
LintId::of(loops::MANUAL_FIND),
122123
LintId::of(loops::MANUAL_FLATTEN),
123124
LintId::of(loops::MANUAL_MEMCPY),
124125
LintId::of(loops::MISSING_SPIN_LOOP),

clippy_lints/src/lib.register_complexity.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ store.register_group(true, "clippy::complexity", Some("clippy_complexity"), vec!
2121
LintId::of(lifetimes::EXTRA_UNUSED_LIFETIMES),
2222
LintId::of(lifetimes::NEEDLESS_LIFETIMES),
2323
LintId::of(loops::EXPLICIT_COUNTER_LOOP),
24+
LintId::of(loops::MANUAL_FIND),
2425
LintId::of(loops::MANUAL_FLATTEN),
2526
LintId::of(loops::SINGLE_ELEMENT_LOOP),
2627
LintId::of(loops::WHILE_LET_LOOP),

clippy_lints/src/lib.register_lints.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,7 @@ store.register_lints(&[
237237
loops::FOR_KV_MAP,
238238
loops::FOR_LOOPS_OVER_FALLIBLES,
239239
loops::ITER_NEXT_LOOP,
240+
loops::MANUAL_FIND,
240241
loops::MANUAL_FLATTEN,
241242
loops::MANUAL_MEMCPY,
242243
loops::MISSING_SPIN_LOOP,

clippy_lints/src/loops/manual_find.rs

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
use super::utils::make_iterator_snippet;
2+
use super::MANUAL_FIND;
3+
use clippy_utils::{
4+
diagnostics::span_lint_and_then, higher, is_lang_ctor, path_res, peel_blocks_with_stmt,
5+
source::snippet_with_applicability, ty::implements_trait,
6+
};
7+
use if_chain::if_chain;
8+
use rustc_errors::Applicability;
9+
use rustc_hir::{
10+
def::Res, lang_items::LangItem, BindingAnnotation, Block, Expr, ExprKind, HirId, Node, Pat, PatKind, Stmt, StmtKind,
11+
};
12+
use rustc_lint::LateContext;
13+
use rustc_span::source_map::Span;
14+
15+
pub(super) fn check<'tcx>(
16+
cx: &LateContext<'tcx>,
17+
pat: &'tcx Pat<'_>,
18+
arg: &'tcx Expr<'_>,
19+
body: &'tcx Expr<'_>,
20+
span: Span,
21+
expr: &'tcx Expr<'_>,
22+
) {
23+
let inner_expr = peel_blocks_with_stmt(body);
24+
// Check for the specific case that the result is returned and optimize suggestion for that (more
25+
// cases can be added later)
26+
if_chain! {
27+
if let Some(higher::If { cond, then, r#else: None, }) = higher::If::hir(inner_expr);
28+
if let Some(binding_id) = get_binding(pat);
29+
if let ExprKind::Block(block, _) = then.kind;
30+
if let [stmt] = block.stmts;
31+
if let StmtKind::Semi(semi) = stmt.kind;
32+
if let ExprKind::Ret(Some(ret_value)) = semi.kind;
33+
if let ExprKind::Call(Expr { kind: ExprKind::Path(ctor), .. }, [inner_ret]) = ret_value.kind;
34+
if is_lang_ctor(cx, ctor, LangItem::OptionSome);
35+
if path_res(cx, inner_ret) == Res::Local(binding_id);
36+
if let Some((last_stmt, last_ret)) = last_stmt_and_ret(cx, expr);
37+
then {
38+
let mut applicability = Applicability::MachineApplicable;
39+
let mut snippet = make_iterator_snippet(cx, arg, &mut applicability);
40+
// Checks if `pat` is a single reference to a binding (`&x`)
41+
let is_ref_to_binding =
42+
matches!(pat.kind, PatKind::Ref(inner, _) if matches!(inner.kind, PatKind::Binding(..)));
43+
// If `pat` is not a binding or a reference to a binding (`x` or `&x`)
44+
// we need to map it to the binding returned by the function (i.e. `.map(|(x, _)| x)`)
45+
if !(matches!(pat.kind, PatKind::Binding(..)) || is_ref_to_binding) {
46+
snippet.push_str(
47+
&format!(
48+
".map(|{}| {})",
49+
snippet_with_applicability(cx, pat.span, "..", &mut applicability),
50+
snippet_with_applicability(cx, inner_ret.span, "..", &mut applicability),
51+
)[..],
52+
);
53+
}
54+
let ty = cx.typeck_results().expr_ty(inner_ret);
55+
if cx.tcx.lang_items().copy_trait().map_or(false, |id| implements_trait(cx, ty, id, &[])) {
56+
snippet.push_str(
57+
&format!(
58+
".find(|{}{}| {})",
59+
"&".repeat(1 + usize::from(is_ref_to_binding)),
60+
snippet_with_applicability(cx, inner_ret.span, "..", &mut applicability),
61+
snippet_with_applicability(cx, cond.span, "..", &mut applicability),
62+
)[..],
63+
);
64+
if is_ref_to_binding {
65+
snippet.push_str(".copied()");
66+
}
67+
} else {
68+
applicability = Applicability::MaybeIncorrect;
69+
snippet.push_str(
70+
&format!(
71+
".find(|{}| {})",
72+
snippet_with_applicability(cx, inner_ret.span, "..", &mut applicability),
73+
snippet_with_applicability(cx, cond.span, "..", &mut applicability),
74+
)[..],
75+
);
76+
}
77+
// Extends to `last_stmt` to include semicolon in case of `return None;`
78+
let lint_span = span.to(last_stmt.span).to(last_ret.span);
79+
span_lint_and_then(
80+
cx,
81+
MANUAL_FIND,
82+
lint_span,
83+
"manual implementation of `Iterator::find`",
84+
|diag| {
85+
if applicability == Applicability::MaybeIncorrect {
86+
diag.note("you may need to dereference some variables");
87+
}
88+
diag.span_suggestion(
89+
lint_span,
90+
"replace with an iterator",
91+
snippet,
92+
applicability,
93+
);
94+
},
95+
);
96+
}
97+
}
98+
}
99+
100+
fn get_binding(pat: &Pat<'_>) -> Option<HirId> {
101+
let mut hir_id = None;
102+
let mut count = 0;
103+
pat.each_binding(|annotation, id, _, _| {
104+
count += 1;
105+
if count > 1 {
106+
hir_id = None;
107+
return;
108+
}
109+
if let BindingAnnotation::Unannotated = annotation {
110+
hir_id = Some(id);
111+
}
112+
});
113+
hir_id
114+
}
115+
116+
// Returns the last statement and last return if function fits format for lint
117+
fn last_stmt_and_ret<'tcx>(
118+
cx: &LateContext<'tcx>,
119+
expr: &'tcx Expr<'_>,
120+
) -> Option<(&'tcx Stmt<'tcx>, &'tcx Expr<'tcx>)> {
121+
// Returns last non-return statement and the last return
122+
fn extract<'tcx>(block: &Block<'tcx>) -> Option<(&'tcx Stmt<'tcx>, &'tcx Expr<'tcx>)> {
123+
if let [.., last_stmt] = block.stmts {
124+
if let Some(ret) = block.expr {
125+
return Some((last_stmt, ret));
126+
}
127+
if_chain! {
128+
if let [.., snd_last, _] = block.stmts;
129+
if let StmtKind::Semi(last_expr) = last_stmt.kind;
130+
if let ExprKind::Ret(Some(ret)) = last_expr.kind;
131+
then {
132+
return Some((snd_last, ret));
133+
}
134+
}
135+
}
136+
None
137+
}
138+
let mut parent_iter = cx.tcx.hir().parent_iter(expr.hir_id);
139+
if_chain! {
140+
// This should be the loop
141+
if let Some((node_hir, Node::Stmt(..))) = parent_iter.next();
142+
// This should be the funciton body
143+
if let Some((_, Node::Block(block))) = parent_iter.next();
144+
if let Some((last_stmt, last_ret)) = extract(block);
145+
if last_stmt.hir_id == node_hir;
146+
if let ExprKind::Path(path) = &last_ret.kind;
147+
if is_lang_ctor(cx, path, LangItem::OptionNone);
148+
if let Some((_, Node::Expr(_block))) = parent_iter.next();
149+
// This includes the function header
150+
if let Some((_, func)) = parent_iter.next();
151+
if func.fn_kind().is_some();
152+
then {
153+
Some((block.stmts.last().unwrap(), last_ret))
154+
} else {
155+
None
156+
}
157+
}
158+
}

clippy_lints/src/loops/mod.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ mod explicit_iter_loop;
55
mod for_kv_map;
66
mod for_loops_over_fallibles;
77
mod iter_next_loop;
8+
mod manual_find;
89
mod manual_flatten;
910
mod manual_memcpy;
1011
mod missing_spin_loop;
@@ -609,6 +610,37 @@ declare_clippy_lint! {
609610
"An empty busy waiting loop"
610611
}
611612

613+
declare_clippy_lint! {
614+
/// ### What it does
615+
/// Check for manual implementations of Iterator::find
616+
///
617+
/// ### Why is this bad?
618+
/// It doesn't affect performance, but using `find` is shorter and easier to read.
619+
///
620+
/// ### Example
621+
///
622+
/// ```rust
623+
/// fn example(arr: Vec<i32>) -> Option<i32> {
624+
/// for el in arr {
625+
/// if el == 1 {
626+
/// return Some(el);
627+
/// }
628+
/// }
629+
/// None
630+
/// }
631+
/// ```
632+
/// Use instead:
633+
/// ```rust
634+
/// fn example(arr: Vec<i32>) -> Option<i32> {
635+
/// arr.into_iter().find(|&el| el == 1)
636+
/// }
637+
/// ```
638+
#[clippy::version = "1.61.0"]
639+
pub MANUAL_FIND,
640+
complexity,
641+
"manual implementation of `Iterator::find`"
642+
}
643+
612644
declare_lint_pass!(Loops => [
613645
MANUAL_MEMCPY,
614646
MANUAL_FLATTEN,
@@ -629,6 +661,7 @@ declare_lint_pass!(Loops => [
629661
SAME_ITEM_PUSH,
630662
SINGLE_ELEMENT_LOOP,
631663
MISSING_SPIN_LOOP,
664+
MANUAL_FIND,
632665
]);
633666

634667
impl<'tcx> LateLintPass<'tcx> for Loops {
@@ -703,6 +736,7 @@ fn check_for_loop<'tcx>(
703736
single_element_loop::check(cx, pat, arg, body, expr);
704737
same_item_push::check(cx, pat, arg, body, expr);
705738
manual_flatten::check(cx, pat, arg, body, span);
739+
manual_find::check(cx, pat, arg, body, span, expr);
706740
}
707741

708742
fn check_for_loop_arg(cx: &LateContext<'_>, pat: &Pat<'_>, arg: &Expr<'_>) {

clippy_lints/src/non_expressive_names.rs

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -159,12 +159,10 @@ impl<'a, 'tcx, 'b> Visitor<'tcx> for SimilarNamesNameVisitor<'a, 'tcx, 'b> {
159159

160160
#[must_use]
161161
fn get_exemptions(interned_name: &str) -> Option<&'static [&'static str]> {
162-
for &list in ALLOWED_TO_BE_SIMILAR {
163-
if allowed_to_be_similar(interned_name, list) {
164-
return Some(list);
165-
}
166-
}
167-
None
162+
ALLOWED_TO_BE_SIMILAR
163+
.iter()
164+
.find(|&&list| allowed_to_be_similar(interned_name, list))
165+
.copied()
168166
}
169167

170168
#[must_use]

tests/ui/manual_find.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#![allow(unused)]
2+
#![warn(clippy::manual_find)]
3+
4+
fn vec_string(strings: Vec<String>) -> Option<String> {
5+
for s in strings {
6+
if s == String::new() {
7+
return Some(s);
8+
}
9+
}
10+
None
11+
}
12+
13+
fn tuple(arr: Vec<(String, i32)>) -> Option<String> {
14+
for (s, _) in arr {
15+
if s == String::new() {
16+
return Some(s);
17+
}
18+
}
19+
None
20+
}
21+
22+
fn main() {}

tests/ui/manual_find.stderr

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
error: manual implementation of `Iterator::find`
2+
--> $DIR/manual_find.rs:5:5
3+
|
4+
LL | / for s in strings {
5+
LL | | if s == String::new() {
6+
LL | | return Some(s);
7+
LL | | }
8+
LL | | }
9+
LL | | None
10+
| |________^ help: replace with an iterator: `strings.into_iter().find(|s| s == String::new())`
11+
|
12+
= note: `-D clippy::manual-find` implied by `-D warnings`
13+
= note: you may need to dereference some variables
14+
15+
error: manual implementation of `Iterator::find`
16+
--> $DIR/manual_find.rs:14:5
17+
|
18+
LL | / for (s, _) in arr {
19+
LL | | if s == String::new() {
20+
LL | | return Some(s);
21+
LL | | }
22+
LL | | }
23+
LL | | None
24+
| |________^ help: replace with an iterator: `arr.into_iter().map(|(s, _)| s).find(|s| s == String::new())`
25+
|
26+
= note: you may need to dereference some variables
27+
28+
error: aborting due to 2 previous errors
29+

0 commit comments

Comments
 (0)