-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add
manual_find
lint for function return case
- Loading branch information
Showing
10 changed files
with
619 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,189 @@ | ||
use super::utils::make_iterator_snippet; | ||
use super::MANUAL_FIND; | ||
use crate::rustc_hir::intravisit::Visitor; | ||
use clippy_utils::{ | ||
diagnostics::span_lint_and_sugg, | ||
higher, peel_blocks_with_stmt, | ||
source::snippet_with_applicability, | ||
ty::is_type_diagnostic_item, | ||
visitors::{expr_visitor, find_all_ret_expressions}, | ||
}; | ||
use if_chain::if_chain; | ||
use rustc_errors::Applicability; | ||
use rustc_hir::{BindingAnnotation, Expr, ExprKind, ItemKind, Node, Pat, PatKind, QPath, StmtKind}; | ||
use rustc_lint::LateContext; | ||
use rustc_span::source_map::Span; | ||
use rustc_span::sym; | ||
use rustc_span::symbol::Ident; | ||
|
||
pub(super) fn check<'tcx>( | ||
cx: &LateContext<'tcx>, | ||
pat: &'tcx Pat<'_>, | ||
arg: &'tcx Expr<'_>, | ||
body: &'tcx Expr<'_>, | ||
span: Span, | ||
) { | ||
let inner_expr = peel_blocks_with_stmt(body); | ||
if let Some(higher::If { | ||
cond, | ||
then, | ||
r#else: None, | ||
}) = higher::If::hir(inner_expr) | ||
{ | ||
let mut pat_expr = None; | ||
let mut ident_collector = PatIdentCollector::default(); | ||
ident_collector.visit_pat(pat); | ||
if let [ident] = &ident_collector.idents[..] { | ||
if ident_in_expr(cx, *ident, cond) { | ||
pat_expr = Some(*ident); | ||
} | ||
} | ||
// 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(ident) = pat_expr; | ||
if let ExprKind::Block(block, _) = then.kind; | ||
if let [stmt] = block.stmts; // For now, only deal with the case w/out side effects/mapping | ||
if let StmtKind::Semi(semi) = stmt.kind; | ||
if let ExprKind::Ret(Some(ret_value)) = semi.kind; | ||
let ret_ty = cx.typeck_results().expr_ty(ret_value); | ||
if is_type_diagnostic_item(cx, ret_ty, sym::Option); | ||
if let ExprKind::Call(_, [inner_ret]) = ret_value.kind; | ||
if let Some(i) = ident_of(inner_ret); | ||
if i == ident; | ||
if let Node::Item(parent_fn) | ||
= cx.tcx.hir().get_by_def_id(cx.tcx.hir().get_parent_item(body.hir_id)); | ||
if let ItemKind::Fn(_, _, body_id) = parent_fn.kind; | ||
let parent_fn = &cx.tcx.hir().body(body_id).value; | ||
let ret_exprs = collect_ret_exprs(cx, parent_fn); | ||
if ret_exprs.len() == 2; | ||
// Some(binding) | ||
if let ExprKind::Call(_, [binding]) = &ret_exprs[0].kind; | ||
// None | ||
if let ExprKind::Path(..) = &ret_exprs[1].kind; | ||
then { | ||
let mut applicability = Applicability::MachineApplicable; | ||
let mut snippet = make_iterator_snippet(cx, arg, &mut applicability); | ||
let is_ref_to_binding = | ||
matches!(pat.kind, PatKind::Ref(inner, _) if matches!(inner.kind, PatKind::Binding(..))); | ||
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, binding.span, "..", &mut applicability), | ||
)[..], | ||
); | ||
} | ||
let mut borrows = 0; | ||
match cond.kind { | ||
ExprKind::Binary(_, l, r) => { | ||
for side in [l, r] { | ||
if !matches!( | ||
side.kind, | ||
ExprKind::MethodCall(_, [caller, ..], _) if ident_in_expr(cx, ident, caller) | ||
) && ident_in_expr(cx, ident, side) | ||
{ | ||
borrows = 1; | ||
} | ||
} | ||
}, | ||
ExprKind::Call(..) => borrows = 1, | ||
ExprKind::MethodCall(_, [caller, ..], _) => { | ||
if !ident_in_expr(cx, ident, caller) { | ||
borrows = 1; | ||
} | ||
}, | ||
_ => {}, | ||
} | ||
if is_ref_to_binding { | ||
borrows += 1; | ||
} | ||
snippet.push_str( | ||
&format!( | ||
".find(|{}{}| {})", | ||
"&".repeat(borrows), | ||
snippet_with_applicability(cx, binding.span, "..", &mut applicability), | ||
snippet_with_applicability(cx, cond.span, "..", &mut applicability), | ||
)[..], | ||
); | ||
if is_ref_to_binding { | ||
snippet.push_str(".copied()"); | ||
} | ||
span_lint_and_sugg( | ||
cx, | ||
MANUAL_FIND, | ||
span.with_hi(ret_exprs[1].span.hi()), | ||
"manual implementation of Iterator::find", | ||
"replace with an iterator", | ||
snippet, | ||
applicability, | ||
); | ||
} | ||
} | ||
} | ||
} | ||
|
||
fn ident_of(expr: &Expr<'_>) -> Option<Ident> { | ||
if let ExprKind::Path(QPath::Resolved(_, path)) = expr.kind { | ||
if let [seg, ..] = path.segments { | ||
return Some(seg.ident); | ||
} | ||
} | ||
None | ||
} | ||
|
||
fn ident_in_expr<'tcx>(cx: &LateContext<'tcx>, ident: Ident, expr: &'tcx Expr<'_>) -> bool { | ||
let mut contains = false; | ||
expr_visitor(cx, |e| { | ||
contains |= ident_of(e).map_or(false, |i| i == ident); | ||
!contains | ||
}) | ||
.visit_expr(expr); | ||
contains | ||
} | ||
|
||
#[derive(Default)] | ||
struct PatIdentCollector { | ||
idents: Vec<Ident>, | ||
} | ||
|
||
impl PatIdentCollector { | ||
fn visit_pat(&mut self, pat: &Pat<'_>) { | ||
match pat.kind { | ||
PatKind::Binding(BindingAnnotation::Unannotated, _, ident, None) => self.idents.push(ident), | ||
PatKind::Struct(_, fields, _) => { | ||
for field in fields { | ||
self.visit_pat(field.pat); | ||
} | ||
}, | ||
PatKind::TupleStruct(_, pats, _) | PatKind::Or(pats) | PatKind::Tuple(pats, _) => { | ||
for pat in pats { | ||
self.visit_pat(pat); | ||
} | ||
}, | ||
PatKind::Box(p) | PatKind::Ref(p, _) => self.visit_pat(p), | ||
PatKind::Slice(pats_a, pat_b, pats_c) => { | ||
for pat in pats_a { | ||
self.visit_pat(pat); | ||
} | ||
if let Some(pat) = pat_b { | ||
self.visit_pat(pat); | ||
} | ||
for pat in pats_c { | ||
self.visit_pat(pat); | ||
} | ||
}, | ||
_ => (), | ||
} | ||
} | ||
} | ||
|
||
fn collect_ret_exprs<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Vec<&'tcx Expr<'tcx>> { | ||
let mut ret_exprs = Vec::new(); | ||
find_all_ret_expressions(cx, expr, |ret_expr| { | ||
ret_exprs.push(ret_expr); | ||
true | ||
}); | ||
ret_exprs | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.