X-Git-Url: https://git.lizzy.rs/?a=blobdiff_plain;f=clippy_lints%2Fsrc%2Fmethods%2Fbind_instead_of_map.rs;h=ce958b8ac9f5991a82de380f4ce71575ac60381c;hb=82f613ee3b12a6a90bd9e99fbbab947674d6ec7a;hp=fcf7b509eadbf6cf0eb518e7e7a6196264c614d0;hpb=245b006a2e9453307443356d511e9e89967cfef9;p=rust.git diff --git a/clippy_lints/src/methods/bind_instead_of_map.rs b/clippy_lints/src/methods/bind_instead_of_map.rs index fcf7b509ead..ce958b8ac9f 100644 --- a/clippy_lints/src/methods/bind_instead_of_map.rs +++ b/clippy_lints/src/methods/bind_instead_of_map.rs @@ -1,100 +1,82 @@ use super::{contains_return, BIND_INSTEAD_OF_MAP}; -use crate::utils::{ - in_macro, match_qpath, match_type, method_calls, multispan_sugg_with_applicability, paths, remove_blocks, snippet, - snippet_with_macro_callsite, span_lint_and_sugg, span_lint_and_then, -}; +use clippy_utils::diagnostics::{multispan_sugg_with_applicability, span_lint_and_sugg, span_lint_and_then}; +use clippy_utils::source::{snippet, snippet_with_macro_callsite}; +use clippy_utils::{peel_blocks, visitors::find_all_ret_expressions}; use if_chain::if_chain; use rustc_errors::Applicability; use rustc_hir as hir; -use rustc_hir::intravisit::{self, Visitor}; +use rustc_hir::def::{CtorKind, CtorOf, DefKind, Res}; +use rustc_hir::{LangItem, QPath}; use rustc_lint::LateContext; -use rustc_middle::hir::map::Map; +use rustc_middle::ty::DefIdTree; use rustc_span::Span; pub(crate) struct OptionAndThenSome; -impl BindInsteadOfMap for OptionAndThenSome { - const TYPE_NAME: &'static str = "Option"; - const TYPE_QPATH: &'static [&'static str] = &paths::OPTION; +impl BindInsteadOfMap for OptionAndThenSome { + const VARIANT_LANG_ITEM: LangItem = LangItem::OptionSome; const BAD_METHOD_NAME: &'static str = "and_then"; - const BAD_VARIANT_NAME: &'static str = "Some"; - const BAD_VARIANT_QPATH: &'static [&'static str] = &paths::OPTION_SOME; - const GOOD_METHOD_NAME: &'static str = "map"; } pub(crate) struct ResultAndThenOk; -impl BindInsteadOfMap for ResultAndThenOk { - const TYPE_NAME: &'static str = "Result"; - const TYPE_QPATH: &'static [&'static str] = &paths::RESULT; +impl BindInsteadOfMap for ResultAndThenOk { + const VARIANT_LANG_ITEM: LangItem = LangItem::ResultOk; const BAD_METHOD_NAME: &'static str = "and_then"; - const BAD_VARIANT_NAME: &'static str = "Ok"; - const BAD_VARIANT_QPATH: &'static [&'static str] = &paths::RESULT_OK; - const GOOD_METHOD_NAME: &'static str = "map"; } pub(crate) struct ResultOrElseErrInfo; -impl BindInsteadOfMap for ResultOrElseErrInfo { - const TYPE_NAME: &'static str = "Result"; - const TYPE_QPATH: &'static [&'static str] = &paths::RESULT; +impl BindInsteadOfMap for ResultOrElseErrInfo { + const VARIANT_LANG_ITEM: LangItem = LangItem::ResultErr; const BAD_METHOD_NAME: &'static str = "or_else"; - const BAD_VARIANT_NAME: &'static str = "Err"; - const BAD_VARIANT_QPATH: &'static [&'static str] = &paths::RESULT_ERR; - const GOOD_METHOD_NAME: &'static str = "map_err"; } pub(crate) trait BindInsteadOfMap { - const TYPE_NAME: &'static str; - const TYPE_QPATH: &'static [&'static str]; - + const VARIANT_LANG_ITEM: LangItem; const BAD_METHOD_NAME: &'static str; - const BAD_VARIANT_NAME: &'static str; - const BAD_VARIANT_QPATH: &'static [&'static str]; - const GOOD_METHOD_NAME: &'static str; - fn no_op_msg() -> String { - format!( + fn no_op_msg(cx: &LateContext<'_>) -> Option { + let variant_id = cx.tcx.lang_items().require(Self::VARIANT_LANG_ITEM).ok()?; + let item_id = cx.tcx.parent(variant_id)?; + Some(format!( "using `{}.{}({})`, which is a no-op", - Self::TYPE_NAME, + cx.tcx.item_name(item_id), Self::BAD_METHOD_NAME, - Self::BAD_VARIANT_NAME - ) + cx.tcx.item_name(variant_id), + )) } - fn lint_msg() -> String { - format!( + fn lint_msg(cx: &LateContext<'_>) -> Option { + let variant_id = cx.tcx.lang_items().require(Self::VARIANT_LANG_ITEM).ok()?; + let item_id = cx.tcx.parent(variant_id)?; + Some(format!( "using `{}.{}(|x| {}(y))`, which is more succinctly expressed as `{}(|x| y)`", - Self::TYPE_NAME, + cx.tcx.item_name(item_id), Self::BAD_METHOD_NAME, - Self::BAD_VARIANT_NAME, + cx.tcx.item_name(variant_id), Self::GOOD_METHOD_NAME - ) + )) } fn lint_closure_autofixable( cx: &LateContext<'_>, expr: &hir::Expr<'_>, - args: &[hir::Expr<'_>], + recv: &hir::Expr<'_>, closure_expr: &hir::Expr<'_>, closure_args_span: Span, ) -> bool { if_chain! { - if let hir::ExprKind::Call(ref some_expr, ref some_args) = closure_expr.kind; - if let hir::ExprKind::Path(ref qpath) = some_expr.kind; - if match_qpath(qpath, Self::BAD_VARIANT_QPATH); - if some_args.len() == 1; + if let hir::ExprKind::Call(some_expr, [inner_expr]) = closure_expr.kind; + if let hir::ExprKind::Path(QPath::Resolved(_, path)) = some_expr.kind; + if Self::is_variant(cx, path.res); + if !contains_return(inner_expr); + if let Some(msg) = Self::lint_msg(cx); then { - let inner_expr = &some_args[0]; - - if contains_return(inner_expr) { - return false; - } - let some_inner_snip = if inner_expr.span.from_expansion() { snippet_with_macro_callsite(cx, inner_expr.span, "_") } else { @@ -102,13 +84,13 @@ fn lint_closure_autofixable( }; let closure_args_snip = snippet(cx, closure_args_span, ".."); - let option_snip = snippet(cx, args[0].span, ".."); + let option_snip = snippet(cx, recv.span, ".."); let note = format!("{}.{}({} {})", option_snip, Self::GOOD_METHOD_NAME, closure_args_snip, some_inner_snip); span_lint_and_sugg( cx, BIND_INSTEAD_OF_MAP, expr.span, - Self::lint_msg().as_ref(), + &msg, "try this", note, Applicability::MachineApplicable, @@ -120,190 +102,89 @@ fn lint_closure_autofixable( } } - fn lint_closure(cx: &LateContext<'_>, expr: &hir::Expr<'_>, closure_expr: &hir::Expr<'_>) { + fn lint_closure(cx: &LateContext<'_>, expr: &hir::Expr<'_>, closure_expr: &hir::Expr<'_>) -> bool { let mut suggs = Vec::new(); - let can_sugg = find_all_ret_expressions(cx, closure_expr, |ret_expr| { + let can_sugg: bool = find_all_ret_expressions(cx, closure_expr, |ret_expr| { if_chain! { - if !in_macro(ret_expr.span); - if let hir::ExprKind::Call(ref func_path, ref args) = ret_expr.kind; - if let hir::ExprKind::Path(ref qpath) = func_path.kind; - if match_qpath(qpath, Self::BAD_VARIANT_QPATH); - if args.len() == 1; - if !contains_return(&args[0]); + if !ret_expr.span.from_expansion(); + if let hir::ExprKind::Call(func_path, [arg]) = ret_expr.kind; + if let hir::ExprKind::Path(QPath::Resolved(_, path)) = func_path.kind; + if Self::is_variant(cx, path.res); + if !contains_return(arg); then { - suggs.push((ret_expr.span, args[0].span.source_callsite())); + suggs.push((ret_expr.span, arg.span.source_callsite())); true } else { false } } }); - - if can_sugg { - span_lint_and_then(cx, BIND_INSTEAD_OF_MAP, expr.span, Self::lint_msg().as_ref(), |diag| { - multispan_sugg_with_applicability( - diag, - "try this", - Applicability::MachineApplicable, - std::iter::once((*method_calls(expr, 1).2.get(0).unwrap(), Self::GOOD_METHOD_NAME.into())).chain( - suggs - .into_iter() - .map(|(span1, span2)| (span1, snippet(cx, span2, "_").into())), - ), - ) - }); - } + let (span, msg) = if_chain! { + if can_sugg; + if let hir::ExprKind::MethodCall(segment, ..) = expr.kind; + if let Some(msg) = Self::lint_msg(cx); + then { (segment.ident.span, msg) } else { return false; } + }; + span_lint_and_then(cx, BIND_INSTEAD_OF_MAP, expr.span, &msg, |diag| { + multispan_sugg_with_applicability( + diag, + "try this", + Applicability::MachineApplicable, + std::iter::once((span, Self::GOOD_METHOD_NAME.into())).chain( + suggs + .into_iter() + .map(|(span1, span2)| (span1, snippet(cx, span2, "_").into())), + ), + ); + }); + true } /// Lint use of `_.and_then(|x| Some(y))` for `Option`s - fn lint(cx: &LateContext<'_>, expr: &hir::Expr<'_>, args: &[hir::Expr<'_>]) { - if !match_type(cx, cx.tables().expr_ty(&args[0]), Self::TYPE_QPATH) { - return; + fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>, arg: &hir::Expr<'_>) -> bool { + if_chain! { + if let Some(adt) = cx.typeck_results().expr_ty(recv).ty_adt_def(); + if let Ok(vid) = cx.tcx.lang_items().require(Self::VARIANT_LANG_ITEM); + if Some(adt.did) == cx.tcx.parent(vid); + then {} else { return false; } } - match args[1].kind { + match arg.kind { hir::ExprKind::Closure(_, _, body_id, closure_args_span, _) => { let closure_body = cx.tcx.hir().body(body_id); - let closure_expr = remove_blocks(&closure_body.value); + let closure_expr = peel_blocks(&closure_body.value); - if !Self::lint_closure_autofixable(cx, expr, args, closure_expr, closure_args_span) { - Self::lint_closure(cx, expr, closure_expr); + if Self::lint_closure_autofixable(cx, expr, recv, closure_expr, closure_args_span) { + true + } else { + Self::lint_closure(cx, expr, closure_expr) } }, // `_.and_then(Some)` case, which is no-op. - hir::ExprKind::Path(ref qpath) if match_qpath(qpath, Self::BAD_VARIANT_QPATH) => { - span_lint_and_sugg( - cx, - BIND_INSTEAD_OF_MAP, - expr.span, - Self::no_op_msg().as_ref(), - "use the expression directly", - snippet(cx, args[0].span, "..").into(), - Applicability::MachineApplicable, - ); + hir::ExprKind::Path(QPath::Resolved(_, path)) if Self::is_variant(cx, path.res) => { + if let Some(msg) = Self::no_op_msg(cx) { + span_lint_and_sugg( + cx, + BIND_INSTEAD_OF_MAP, + expr.span, + &msg, + "use the expression directly", + snippet(cx, recv.span, "..").into(), + Applicability::MachineApplicable, + ); + } + true }, - _ => {}, - } - } -} - -/// returns `true` if expr contains match expr desugared from try -fn contains_try(expr: &hir::Expr<'_>) -> bool { - struct TryFinder { - found: bool, - } - - impl<'hir> intravisit::Visitor<'hir> for TryFinder { - type Map = Map<'hir>; - - fn nested_visit_map(&mut self) -> intravisit::NestedVisitorMap { - intravisit::NestedVisitorMap::None - } - - fn visit_expr(&mut self, expr: &'hir hir::Expr<'hir>) { - if self.found { - return; - } - match expr.kind { - hir::ExprKind::Match(_, _, hir::MatchSource::TryDesugar) => self.found = true, - _ => intravisit::walk_expr(self, expr), - } + _ => false, } } - let mut visitor = TryFinder { found: false }; - visitor.visit_expr(expr); - visitor.found -} - -fn find_all_ret_expressions<'hir, F>(_cx: &LateContext<'_>, expr: &'hir hir::Expr<'hir>, callback: F) -> bool -where - F: FnMut(&'hir hir::Expr<'hir>) -> bool, -{ - struct RetFinder { - in_stmt: bool, - failed: bool, - cb: F, - } - - struct WithStmtGuarg<'a, F> { - val: &'a mut RetFinder, - prev_in_stmt: bool, - } - - impl RetFinder { - fn inside_stmt(&mut self, in_stmt: bool) -> WithStmtGuarg<'_, F> { - let prev_in_stmt = std::mem::replace(&mut self.in_stmt, in_stmt); - WithStmtGuarg { - val: self, - prev_in_stmt, + fn is_variant(cx: &LateContext<'_>, res: Res) -> bool { + if let Res::Def(DefKind::Ctor(CtorOf::Variant, CtorKind::Fn), id) = res { + if let Ok(variant_id) = cx.tcx.lang_items().require(Self::VARIANT_LANG_ITEM) { + return cx.tcx.parent(id) == Some(variant_id); } } - } - - impl std::ops::Deref for WithStmtGuarg<'_, F> { - type Target = RetFinder; - - fn deref(&self) -> &Self::Target { - self.val - } - } - - impl std::ops::DerefMut for WithStmtGuarg<'_, F> { - fn deref_mut(&mut self) -> &mut Self::Target { - self.val - } - } - - impl Drop for WithStmtGuarg<'_, F> { - fn drop(&mut self) { - self.val.in_stmt = self.prev_in_stmt; - } - } - - impl<'hir, F: FnMut(&'hir hir::Expr<'hir>) -> bool> intravisit::Visitor<'hir> for RetFinder { - type Map = Map<'hir>; - - fn nested_visit_map(&mut self) -> intravisit::NestedVisitorMap { - intravisit::NestedVisitorMap::None - } - - fn visit_stmt(&mut self, stmt: &'hir hir::Stmt<'_>) { - intravisit::walk_stmt(&mut *self.inside_stmt(true), stmt) - } - - fn visit_expr(&mut self, expr: &'hir hir::Expr<'_>) { - if self.failed { - return; - } - if self.in_stmt { - match expr.kind { - hir::ExprKind::Ret(Some(expr)) => self.inside_stmt(false).visit_expr(expr), - _ => intravisit::walk_expr(self, expr), - } - } else { - match expr.kind { - hir::ExprKind::Match(cond, arms, _) => { - self.inside_stmt(true).visit_expr(cond); - for arm in arms { - self.visit_expr(arm.body); - } - }, - hir::ExprKind::Block(..) => intravisit::walk_expr(self, expr), - hir::ExprKind::Ret(Some(expr)) => self.visit_expr(expr), - _ => self.failed |= !(self.cb)(expr), - } - } - } - } - - !contains_try(expr) && { - let mut ret_finder = RetFinder { - in_stmt: false, - failed: false, - cb: callback, - }; - ret_finder.visit_expr(expr); - !ret_finder.failed + false } }