diff options
| author | bors <bors@rust-lang.org> | 2022-06-27 17:15:25 +0000 |
|---|---|---|
| committer | bors <bors@rust-lang.org> | 2022-06-27 17:15:25 +0000 |
| commit | fdd0e727e2174a53ffca340734bd462f8ea865b8 (patch) | |
| tree | f4df74d482b4119334d6c84bf3e5dde3818a4e2a /clippy_lints | |
| parent | eaa03ea911662b282d3c7bc7c3a29f9fb370b933 (diff) | |
| parent | 430575b61a95fc559f5021e246c04c3468acd1d6 (diff) | |
| download | rust-fdd0e727e2174a53ffca340734bd462f8ea865b8.tar.gz rust-fdd0e727e2174a53ffca340734bd462f8ea865b8.zip | |
Auto merge of #8649 - ebobrow:imperative_find, r=flip1995
add [`manual_find`] lint for function return case part of the implementation discussed in #7143 changelog: add [`manual_find`] lint for function return case
Diffstat (limited to 'clippy_lints')
| -rw-r--r-- | clippy_lints/src/lib.register_all.rs | 1 | ||||
| -rw-r--r-- | clippy_lints/src/lib.register_complexity.rs | 1 | ||||
| -rw-r--r-- | clippy_lints/src/lib.register_lints.rs | 1 | ||||
| -rw-r--r-- | clippy_lints/src/loops/manual_find.rs | 158 | ||||
| -rw-r--r-- | clippy_lints/src/loops/mod.rs | 34 | ||||
| -rw-r--r-- | clippy_lints/src/non_expressive_names.rs | 10 |
6 files changed, 199 insertions, 6 deletions
diff --git a/clippy_lints/src/lib.register_all.rs b/clippy_lints/src/lib.register_all.rs index 696f0c6f6b3..b4edb8ea90e 100644 --- a/clippy_lints/src/lib.register_all.rs +++ b/clippy_lints/src/lib.register_all.rs @@ -119,6 +119,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![ LintId::of(loops::FOR_KV_MAP), LintId::of(loops::FOR_LOOPS_OVER_FALLIBLES), LintId::of(loops::ITER_NEXT_LOOP), + LintId::of(loops::MANUAL_FIND), LintId::of(loops::MANUAL_FLATTEN), LintId::of(loops::MANUAL_MEMCPY), LintId::of(loops::MISSING_SPIN_LOOP), diff --git a/clippy_lints/src/lib.register_complexity.rs b/clippy_lints/src/lib.register_complexity.rs index 6370264a12a..27de11bf1ae 100644 --- a/clippy_lints/src/lib.register_complexity.rs +++ b/clippy_lints/src/lib.register_complexity.rs @@ -21,6 +21,7 @@ store.register_group(true, "clippy::complexity", Some("clippy_complexity"), vec! LintId::of(lifetimes::EXTRA_UNUSED_LIFETIMES), LintId::of(lifetimes::NEEDLESS_LIFETIMES), LintId::of(loops::EXPLICIT_COUNTER_LOOP), + LintId::of(loops::MANUAL_FIND), LintId::of(loops::MANUAL_FLATTEN), LintId::of(loops::SINGLE_ELEMENT_LOOP), LintId::of(loops::WHILE_LET_LOOP), diff --git a/clippy_lints/src/lib.register_lints.rs b/clippy_lints/src/lib.register_lints.rs index 15b9405be3c..29a5f368f23 100644 --- a/clippy_lints/src/lib.register_lints.rs +++ b/clippy_lints/src/lib.register_lints.rs @@ -237,6 +237,7 @@ store.register_lints(&[ loops::FOR_KV_MAP, loops::FOR_LOOPS_OVER_FALLIBLES, loops::ITER_NEXT_LOOP, + loops::MANUAL_FIND, loops::MANUAL_FLATTEN, loops::MANUAL_MEMCPY, loops::MISSING_SPIN_LOOP, diff --git a/clippy_lints/src/loops/manual_find.rs b/clippy_lints/src/loops/manual_find.rs new file mode 100644 index 00000000000..33736d6d4e6 --- /dev/null +++ b/clippy_lints/src/loops/manual_find.rs @@ -0,0 +1,158 @@ +use super::utils::make_iterator_snippet; +use super::MANUAL_FIND; +use clippy_utils::{ + diagnostics::span_lint_and_then, higher, is_lang_ctor, path_res, peel_blocks_with_stmt, + source::snippet_with_applicability, ty::implements_trait, +}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir::{ + def::Res, lang_items::LangItem, BindingAnnotation, Block, Expr, ExprKind, HirId, Node, Pat, PatKind, Stmt, StmtKind, +}; +use rustc_lint::LateContext; +use rustc_span::source_map::Span; + +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + pat: &'tcx Pat<'_>, + arg: &'tcx Expr<'_>, + body: &'tcx Expr<'_>, + span: Span, + expr: &'tcx Expr<'_>, +) { + let inner_expr = peel_blocks_with_stmt(body); + // Check for the specific case that the result is returned and optimize suggestion for that (more + // cases can be added later) + if_chain! { + if let Some(higher::If { cond, then, r#else: None, }) = higher::If::hir(inner_expr); + if let Some(binding_id) = get_binding(pat); + if let ExprKind::Block(block, _) = then.kind; + if let [stmt] = block.stmts; + if let StmtKind::Semi(semi) = stmt.kind; + if let ExprKind::Ret(Some(ret_value)) = semi.kind; + if let ExprKind::Call(Expr { kind: ExprKind::Path(ctor), .. }, [inner_ret]) = ret_value.kind; + if is_lang_ctor(cx, ctor, LangItem::OptionSome); + if path_res(cx, inner_ret) == Res::Local(binding_id); + if let Some((last_stmt, last_ret)) = last_stmt_and_ret(cx, expr); + then { + let mut applicability = Applicability::MachineApplicable; + let mut snippet = make_iterator_snippet(cx, arg, &mut applicability); + // Checks if `pat` is a single reference to a binding (`&x`) + let is_ref_to_binding = + matches!(pat.kind, PatKind::Ref(inner, _) if matches!(inner.kind, PatKind::Binding(..))); + // If `pat` is not a binding or a reference to a binding (`x` or `&x`) + // we need to map it to the binding returned by the function (i.e. `.map(|(x, _)| x)`) + if !(matches!(pat.kind, PatKind::Binding(..)) || is_ref_to_binding) { + snippet.push_str( + &format!( + ".map(|{}| {})", + snippet_with_applicability(cx, pat.span, "..", &mut applicability), + snippet_with_applicability(cx, inner_ret.span, "..", &mut applicability), + )[..], + ); + } + let ty = cx.typeck_results().expr_ty(inner_ret); + if cx.tcx.lang_items().copy_trait().map_or(false, |id| implements_trait(cx, ty, id, &[])) { + snippet.push_str( + &format!( + ".find(|{}{}| {})", + "&".repeat(1 + usize::from(is_ref_to_binding)), + snippet_with_applicability(cx, inner_ret.span, "..", &mut applicability), + snippet_with_applicability(cx, cond.span, "..", &mut applicability), + )[..], + ); + if is_ref_to_binding { + snippet.push_str(".copied()"); + } + } else { + applicability = Applicability::MaybeIncorrect; + snippet.push_str( + &format!( + ".find(|{}| {})", + snippet_with_applicability(cx, inner_ret.span, "..", &mut applicability), + snippet_with_applicability(cx, cond.span, "..", &mut applicability), + )[..], + ); + } + // Extends to `last_stmt` to include semicolon in case of `return None;` + let lint_span = span.to(last_stmt.span).to(last_ret.span); + span_lint_and_then( + cx, + MANUAL_FIND, + lint_span, + "manual implementation of `Iterator::find`", + |diag| { + if applicability == Applicability::MaybeIncorrect { + diag.note("you may need to dereference some variables"); + } + diag.span_suggestion( + lint_span, + "replace with an iterator", + snippet, + applicability, + ); + }, + ); + } + } +} + +fn get_binding(pat: &Pat<'_>) -> Option<HirId> { + let mut hir_id = None; + let mut count = 0; + pat.each_binding(|annotation, id, _, _| { + count += 1; + if count > 1 { + hir_id = None; + return; + } + if let BindingAnnotation::Unannotated = annotation { + hir_id = Some(id); + } + }); + hir_id +} + +// Returns the last statement and last return if function fits format for lint +fn last_stmt_and_ret<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx Expr<'_>, +) -> Option<(&'tcx Stmt<'tcx>, &'tcx Expr<'tcx>)> { + // Returns last non-return statement and the last return + fn extract<'tcx>(block: &Block<'tcx>) -> Option<(&'tcx Stmt<'tcx>, &'tcx Expr<'tcx>)> { + if let [.., last_stmt] = block.stmts { + if let Some(ret) = block.expr { + return Some((last_stmt, ret)); + } + if_chain! { + if let [.., snd_last, _] = block.stmts; + if let StmtKind::Semi(last_expr) = last_stmt.kind; + if let ExprKind::Ret(Some(ret)) = last_expr.kind; + then { + return Some((snd_last, ret)); + } + } + } + None + } + let mut parent_iter = cx.tcx.hir().parent_iter(expr.hir_id); + if_chain! { + // This should be the loop + if let Some((node_hir, Node::Stmt(..))) = parent_iter.next(); + // This should be the funciton body + if let Some((_, Node::Block(block))) = parent_iter.next(); + if let Some((last_stmt, last_ret)) = extract(block); + if last_stmt.hir_id == node_hir; + if let ExprKind::Path(path) = &last_ret.kind; + if is_lang_ctor(cx, path, LangItem::OptionNone); + if let Some((_, Node::Expr(_block))) = parent_iter.next(); + // This includes the function header + if let Some((_, func)) = parent_iter.next(); + if func.fn_kind().is_some(); + then { + Some((block.stmts.last().unwrap(), last_ret)) + } else { + None + } + } +} diff --git a/clippy_lints/src/loops/mod.rs b/clippy_lints/src/loops/mod.rs index fa7a8b10033..ed270bd490d 100644 --- a/clippy_lints/src/loops/mod.rs +++ b/clippy_lints/src/loops/mod.rs @@ -5,6 +5,7 @@ mod explicit_iter_loop; mod for_kv_map; mod for_loops_over_fallibles; mod iter_next_loop; +mod manual_find; mod manual_flatten; mod manual_memcpy; mod missing_spin_loop; @@ -609,6 +610,37 @@ declare_clippy_lint! { "An empty busy waiting loop" } +declare_clippy_lint! { + /// ### What it does + /// Check for manual implementations of Iterator::find + /// + /// ### Why is this bad? + /// It doesn't affect performance, but using `find` is shorter and easier to read. + /// + /// ### Example + /// + /// ```rust + /// fn example(arr: Vec<i32>) -> Option<i32> { + /// for el in arr { + /// if el == 1 { + /// return Some(el); + /// } + /// } + /// None + /// } + /// ``` + /// Use instead: + /// ```rust + /// fn example(arr: Vec<i32>) -> Option<i32> { + /// arr.into_iter().find(|&el| el == 1) + /// } + /// ``` + #[clippy::version = "1.61.0"] + pub MANUAL_FIND, + complexity, + "manual implementation of `Iterator::find`" +} + declare_lint_pass!(Loops => [ MANUAL_MEMCPY, MANUAL_FLATTEN, @@ -629,6 +661,7 @@ declare_lint_pass!(Loops => [ SAME_ITEM_PUSH, SINGLE_ELEMENT_LOOP, MISSING_SPIN_LOOP, + MANUAL_FIND, ]); impl<'tcx> LateLintPass<'tcx> for Loops { @@ -703,6 +736,7 @@ fn check_for_loop<'tcx>( single_element_loop::check(cx, pat, arg, body, expr); same_item_push::check(cx, pat, arg, body, expr); manual_flatten::check(cx, pat, arg, body, span); + manual_find::check(cx, pat, arg, body, span, expr); } fn check_for_loop_arg(cx: &LateContext<'_>, pat: &Pat<'_>, arg: &Expr<'_>) { diff --git a/clippy_lints/src/non_expressive_names.rs b/clippy_lints/src/non_expressive_names.rs index 7f6b535c7b1..c93b4b2f205 100644 --- a/clippy_lints/src/non_expressive_names.rs +++ b/clippy_lints/src/non_expressive_names.rs @@ -159,12 +159,10 @@ impl<'a, 'tcx, 'b> Visitor<'tcx> for SimilarNamesNameVisitor<'a, 'tcx, 'b> { #[must_use] fn get_exemptions(interned_name: &str) -> Option<&'static [&'static str]> { - for &list in ALLOWED_TO_BE_SIMILAR { - if allowed_to_be_similar(interned_name, list) { - return Some(list); - } - } - None + ALLOWED_TO_BE_SIMILAR + .iter() + .find(|&&list| allowed_to_be_similar(interned_name, list)) + .copied() } #[must_use] |
