diff options
Diffstat (limited to 'clippy_lints/src/methods')
45 files changed, 826 insertions, 677 deletions
diff --git a/clippy_lints/src/methods/bind_instead_of_map.rs b/clippy_lints/src/methods/bind_instead_of_map.rs index 0ba8a98a018..287bff886bf 100644 --- a/clippy_lints/src/methods/bind_instead_of_map.rs +++ b/clippy_lints/src/methods/bind_instead_of_map.rs @@ -1,101 +1,82 @@ use super::{contains_return, BIND_INSTEAD_OF_MAP}; 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::ty::match_type; -use clippy_utils::{in_macro, match_qpath, method_calls, paths, remove_blocks, visitors::find_all_ret_expressions}; +use clippy_utils::{in_macro, remove_blocks, visitors::find_all_ret_expressions}; use if_chain::if_chain; use rustc_errors::Applicability; use rustc_hir as hir; +use rustc_hir::def::{CtorKind, CtorOf, DefKind, Res}; +use rustc_hir::{LangItem, QPath}; use rustc_lint::LateContext; +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; - + 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; - + 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; - + 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<String> { + 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<String> { + 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 { @@ -103,13 +84,13 @@ pub(crate) trait BindInsteadOfMap { }; 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, @@ -126,68 +107,84 @@ pub(crate) trait BindInsteadOfMap { 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 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())), - ), - ) - }); - } - can_sugg + let (span, msg) = if_chain! { + if can_sugg; + if let hir::ExprKind::MethodCall(_, span, ..) = expr.kind; + if let Some(msg) = Self::lint_msg(cx); + then { (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 check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, args: &[hir::Expr<'_>]) -> bool { - if !match_type(cx, cx.typeck_results().expr_ty(&args[0]), Self::TYPE_QPATH) { - return false; + 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); - if Self::lint_closure_autofixable(cx, expr, args, closure_expr, closure_args_span) { + 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 }, _ => false, } } + + 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); + } + } + false + } } diff --git a/clippy_lints/src/methods/bytes_nth.rs b/clippy_lints/src/methods/bytes_nth.rs index 4f88f80a304..2ad3e673c57 100644 --- a/clippy_lints/src/methods/bytes_nth.rs +++ b/clippy_lints/src/methods/bytes_nth.rs @@ -1,41 +1,34 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::snippet_with_applicability; use clippy_utils::ty::is_type_diagnostic_item; -use if_chain::if_chain; use rustc_errors::Applicability; -use rustc_hir::{Expr, ExprKind}; +use rustc_hir::Expr; use rustc_lint::LateContext; use rustc_span::sym; use super::BYTES_NTH; -pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>, iter_args: &'tcx [Expr<'tcx>]) { - if_chain! { - if let ExprKind::MethodCall(_, _, ref args, _) = expr.kind; - let ty = cx.typeck_results().expr_ty(&iter_args[0]).peel_refs(); - let caller_type = if is_type_diagnostic_item(cx, ty, sym::string_type) { - Some("String") - } else if ty.is_str() { - Some("str") - } else { - None - }; - if let Some(caller_type) = caller_type; - then { - let mut applicability = Applicability::MachineApplicable; - span_lint_and_sugg( - cx, - BYTES_NTH, - expr.span, - &format!("called `.byte().nth()` on a `{}`", caller_type), - "try", - format!( - "{}.as_bytes().get({})", - snippet_with_applicability(cx, iter_args[0].span, "..", &mut applicability), - snippet_with_applicability(cx, args[1].span, "..", &mut applicability) - ), - applicability, - ); - } - } +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>, recv: &'tcx Expr<'tcx>, n_arg: &'tcx Expr<'tcx>) { + let ty = cx.typeck_results().expr_ty(recv).peel_refs(); + let caller_type = if ty.is_str() { + "str" + } else if is_type_diagnostic_item(cx, ty, sym::string_type) { + "String" + } else { + return; + }; + let mut applicability = Applicability::MachineApplicable; + span_lint_and_sugg( + cx, + BYTES_NTH, + expr.span, + &format!("called `.byte().nth()` on a `{}`", caller_type), + "try", + format!( + "{}.as_bytes().get({})", + snippet_with_applicability(cx, recv.span, "..", &mut applicability), + snippet_with_applicability(cx, n_arg.span, "..", &mut applicability) + ), + applicability, + ); } diff --git a/clippy_lints/src/methods/chars_cmp.rs b/clippy_lints/src/methods/chars_cmp.rs index c668fe52781..514c4118765 100644 --- a/clippy_lints/src/methods/chars_cmp.rs +++ b/clippy_lints/src/methods/chars_cmp.rs @@ -19,7 +19,7 @@ pub(super) fn check( ) -> bool { if_chain! { if let Some(args) = method_chain_args(info.chain, chain_methods); - if let hir::ExprKind::Call(ref fun, ref arg_char) = info.other.kind; + if let hir::ExprKind::Call(fun, arg_char) = info.other.kind; if arg_char.len() == 1; if let hir::ExprKind::Path(ref qpath) = fun.kind; if let Some(segment) = single_segment_path(qpath); diff --git a/clippy_lints/src/methods/clone_on_copy.rs b/clippy_lints/src/methods/clone_on_copy.rs index edb6649b87b..ce2e8fa8b10 100644 --- a/clippy_lints/src/methods/clone_on_copy.rs +++ b/clippy_lints/src/methods/clone_on_copy.rs @@ -1,10 +1,12 @@ -use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then}; +use clippy_utils::get_parent_node; +use clippy_utils::source::snippet_with_context; use clippy_utils::sugg; use clippy_utils::ty::is_copy; use rustc_errors::Applicability; -use rustc_hir as hir; +use rustc_hir::{BindingAnnotation, Expr, ExprKind, MatchSource, Node, PatKind}; use rustc_lint::LateContext; -use rustc_middle::ty; +use rustc_middle::ty::{self, adjustment::Adjust}; use rustc_span::symbol::{sym, Symbol}; use std::iter; @@ -12,12 +14,26 @@ use super::CLONE_DOUBLE_REF; use super::CLONE_ON_COPY; /// Checks for the `CLONE_ON_COPY` lint. -pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, method_name: Symbol, args: &[hir::Expr<'_>]) { - if !(args.len() == 1 && method_name == sym::clone) { +#[allow(clippy::too_many_lines)] +pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, method_name: Symbol, args: &[Expr<'_>]) { + let arg = match args { + [arg] if method_name == sym::clone => arg, + _ => return, + }; + if cx + .typeck_results() + .type_dependent_def_id(expr.hir_id) + .and_then(|id| cx.tcx.trait_of_item(id)) + .zip(cx.tcx.lang_items().clone_trait()) + .map_or(true, |(x, y)| x != y) + { return; } - let arg = &args[0]; - let arg_ty = cx.typeck_results().expr_ty_adjusted(&args[0]); + let arg_adjustments = cx.typeck_results().expr_adjustments(arg); + let arg_ty = arg_adjustments + .last() + .map_or_else(|| cx.typeck_results().expr_ty(arg), |a| a.target); + let ty = cx.typeck_results().expr_ty(expr); if let ty::Ref(_, inner, _) = arg_ty.kind() { if let ty::Ref(_, innermost, _) = inner.kind() { @@ -61,57 +77,57 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, method_name: Sym } if is_copy(cx, ty) { - let snip; - if let Some(snippet) = sugg::Sugg::hir_opt(cx, arg) { - let parent = cx.tcx.hir().get_parent_node(expr.hir_id); - match &cx.tcx.hir().get(parent) { - hir::Node::Expr(parent) => match parent.kind { - // &*x is a nop, &x.clone() is not - hir::ExprKind::AddrOf(..) => return, - // (*x).func() is useless, x.clone().func() can work in case func borrows mutably - hir::ExprKind::MethodCall(_, _, parent_args, _) if expr.hir_id == parent_args[0].hir_id => { - return; - }, - - _ => {}, - }, - hir::Node::Stmt(stmt) => { - if let hir::StmtKind::Local(ref loc) = stmt.kind { - if let hir::PatKind::Ref(..) = loc.pat.kind { - // let ref y = *x borrows x, let ref y = x.clone() does not - return; - } - } - }, - _ => {}, + let parent_is_suffix_expr = match get_parent_node(cx.tcx, expr.hir_id) { + Some(Node::Expr(parent)) => match parent.kind { + // &*x is a nop, &x.clone() is not + ExprKind::AddrOf(..) => return, + // (*x).func() is useless, x.clone().func() can work in case func borrows self + ExprKind::MethodCall(_, _, [self_arg, ..], _) + if expr.hir_id == self_arg.hir_id && ty != cx.typeck_results().expr_ty_adjusted(expr) => + { + return; + } + ExprKind::MethodCall(_, _, [self_arg, ..], _) if expr.hir_id == self_arg.hir_id => true, + ExprKind::Match(_, _, MatchSource::TryDesugar | MatchSource::AwaitDesugar) + | ExprKind::Field(..) + | ExprKind::Index(..) => true, + _ => false, + }, + // local binding capturing a reference + Some(Node::Local(l)) + if matches!( + l.pat.kind, + PatKind::Binding(BindingAnnotation::Ref | BindingAnnotation::RefMut, ..) + ) => + { + return; } + _ => false, + }; - // x.clone() might have dereferenced x, possibly through Deref impls - if cx.typeck_results().expr_ty(arg) == ty { - snip = Some(("try removing the `clone` call", format!("{}", snippet))); - } else { - let deref_count = cx - .typeck_results() - .expr_adjustments(arg) - .iter() - .filter(|adj| matches!(adj.kind, ty::adjustment::Adjust::Deref(_))) - .count(); - let derefs: String = iter::repeat('*').take(deref_count).collect(); - snip = Some(("try dereferencing it", format!("{}{}", derefs, snippet))); - } + let mut app = Applicability::MachineApplicable; + let snip = snippet_with_context(cx, arg.span, expr.span.ctxt(), "_", &mut app).0; + + let deref_count = arg_adjustments + .iter() + .take_while(|adj| matches!(adj.kind, Adjust::Deref(_))) + .count(); + let (help, sugg) = if deref_count == 0 { + ("try removing the `clone` call", snip.into()) + } else if parent_is_suffix_expr { + ("try dereferencing it", format!("({}{})", "*".repeat(deref_count), snip)) } else { - snip = None; - } - span_lint_and_then( + ("try dereferencing it", format!("{}{}", "*".repeat(deref_count), snip)) + }; + + span_lint_and_sugg( cx, CLONE_ON_COPY, expr.span, &format!("using `clone` on type `{}` which implements the `Copy` trait", ty), - |diag| { - if let Some((text, snip)) = snip { - diag.span_suggestion(expr.span, text, snip, Applicability::MachineApplicable); - } - }, + help, + sugg, + app, ); } } diff --git a/clippy_lints/src/methods/expect_fun_call.rs b/clippy_lints/src/methods/expect_fun_call.rs index e7bffa66b3f..03cb41697d5 100644 --- a/clippy_lints/src/methods/expect_fun_call.rs +++ b/clippy_lints/src/methods/expect_fun_call.rs @@ -100,9 +100,9 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, method_span: Spa applicability: &mut Applicability, ) -> Vec<String> { if_chain! { - if let hir::ExprKind::AddrOf(hir::BorrowKind::Ref, _, ref format_arg) = a.kind; - if let hir::ExprKind::Match(ref format_arg_expr, _, _) = format_arg.kind; - if let hir::ExprKind::Tup(ref format_arg_expr_tup) = format_arg_expr.kind; + if let hir::ExprKind::AddrOf(hir::BorrowKind::Ref, _, format_arg) = a.kind; + if let hir::ExprKind::Match(format_arg_expr, _, _) = format_arg.kind; + if let hir::ExprKind::Tup(format_arg_expr_tup) = format_arg_expr.kind; then { format_arg_expr_tup @@ -155,7 +155,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, method_span: Spa if block.stmts.len() == 1; if let hir::StmtKind::Local(local) = &block.stmts[0].kind; if let Some(arg_root) = &local.init; - if let hir::ExprKind::Call(ref inner_fun, ref inner_args) = arg_root.kind; + if let hir::ExprKind::Call(inner_fun, inner_args) = arg_root.kind; if is_expn_of(inner_fun.span, "format").is_some() && inner_args.len() == 1; if let hir::ExprKind::Call(_, format_args) = &inner_args[0].kind; then { diff --git a/clippy_lints/src/methods/expect_used.rs b/clippy_lints/src/methods/expect_used.rs index 64531b29ade..63a834fdce0 100644 --- a/clippy_lints/src/methods/expect_used.rs +++ b/clippy_lints/src/methods/expect_used.rs @@ -7,8 +7,8 @@ use rustc_span::sym; use super::EXPECT_USED; /// lint use of `expect()` for `Option`s and `Result`s -pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, expect_args: &[hir::Expr<'_>]) { - let obj_ty = cx.typeck_results().expr_ty(&expect_args[0]).peel_refs(); +pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>) { + let obj_ty = cx.typeck_results().expr_ty(recv).peel_refs(); let mess = if is_type_diagnostic_item(cx, obj_ty, sym::option_type) { Some((EXPECT_USED, "an Option", "None")) diff --git a/clippy_lints/src/methods/filetype_is_file.rs b/clippy_lints/src/methods/filetype_is_file.rs index 39d2f15dbc8..7b2967feb0f 100644 --- a/clippy_lints/src/methods/filetype_is_file.rs +++ b/clippy_lints/src/methods/filetype_is_file.rs @@ -8,8 +8,8 @@ use rustc_span::source_map::Span; use super::FILETYPE_IS_FILE; -pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, args: &[hir::Expr<'_>]) { - let ty = cx.typeck_results().expr_ty(&args[0]); +pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>) { + let ty = cx.typeck_results().expr_ty(recv); if !match_type(cx, ty, &paths::FILE_TYPE) { return; diff --git a/clippy_lints/src/methods/filter_map.rs b/clippy_lints/src/methods/filter_map.rs index 2cb476acb2b..35fae450eeb 100644 --- a/clippy_lints/src/methods/filter_map.rs +++ b/clippy_lints/src/methods/filter_map.rs @@ -1,87 +1,174 @@ use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::source::snippet; -use clippy_utils::{is_trait_method, path_to_local_id, SpanlessEq}; +use clippy_utils::source::{indent_of, reindent_multiline, snippet}; +use clippy_utils::ty::is_type_diagnostic_item; +use clippy_utils::{is_trait_method, path_to_local_id, remove_blocks, SpanlessEq}; use if_chain::if_chain; use rustc_errors::Applicability; use rustc_hir as hir; -use rustc_hir::{Expr, ExprKind, PatKind, UnOp}; +use rustc_hir::def::Res; +use rustc_hir::{Expr, ExprKind, PatKind, QPath, UnOp}; use rustc_lint::LateContext; use rustc_middle::ty::TyS; -use rustc_span::symbol::sym; +use rustc_span::source_map::Span; +use rustc_span::symbol::{sym, Symbol}; +use std::borrow::Cow; use super::MANUAL_FILTER_MAP; use super::MANUAL_FIND_MAP; +use super::OPTION_FILTER_MAP; + +fn is_method<'tcx>(cx: &LateContext<'tcx>, expr: &hir::Expr<'_>, method_name: Symbol) -> bool { + match &expr.kind { + hir::ExprKind::Path(QPath::TypeRelative(_, mname)) => mname.ident.name == method_name, + hir::ExprKind::Path(QPath::Resolved(_, segments)) => { + segments.segments.last().unwrap().ident.name == method_name + }, + hir::ExprKind::Closure(_, _, c, _, _) => { + let body = cx.tcx.hir().body(*c); + let closure_expr = remove_blocks(&body.value); + let arg_id = body.params[0].pat.hir_id; + match closure_expr.kind { + hir::ExprKind::MethodCall(hir::PathSegment { ident, .. }, _, args, _) => { + if_chain! { + if ident.name == method_name; + if let hir::ExprKind::Path(path) = &args[0].kind; + if let Res::Local(ref local) = cx.qpath_res(path, args[0].hir_id); + then { + return arg_id == *local + } + } + false + }, + _ => false, + } + }, + _ => false, + } +} + +fn is_option_filter_map<'tcx>(cx: &LateContext<'tcx>, filter_arg: &hir::Expr<'_>, map_arg: &hir::Expr<'_>) -> bool { + is_method(cx, map_arg, sym::unwrap) && is_method(cx, filter_arg, sym!(is_some)) +} + +/// lint use of `filter().map()` for `Iterators` +fn lint_filter_some_map_unwrap( + cx: &LateContext<'_>, + expr: &hir::Expr<'_>, + filter_recv: &hir::Expr<'_>, + filter_arg: &hir::Expr<'_>, + map_arg: &hir::Expr<'_>, + target_span: Span, + methods_span: Span, +) { + let iterator = is_trait_method(cx, expr, sym::Iterator); + let option = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(filter_recv), sym::option_type); + if (iterator || option) && is_option_filter_map(cx, filter_arg, map_arg) { + let msg = "`filter` for `Some` followed by `unwrap`"; + let help = "consider using `flatten` instead"; + let sugg = format!( + "{}", + reindent_multiline(Cow::Borrowed("flatten()"), true, indent_of(cx, target_span),) + ); + span_lint_and_sugg( + cx, + OPTION_FILTER_MAP, + methods_span, + msg, + help, + sugg, + Applicability::MachineApplicable, + ); + } +} /// lint use of `filter().map()` or `find().map()` for `Iterators` -pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>, is_find: bool) { +#[allow(clippy::too_many_arguments)] +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + expr: &hir::Expr<'_>, + filter_recv: &hir::Expr<'_>, + filter_arg: &hir::Expr<'_>, + filter_span: Span, + map_recv: &hir::Expr<'_>, + map_arg: &hir::Expr<'_>, + map_span: Span, + is_find: bool, +) { + lint_filter_some_map_unwrap( + cx, + expr, + filter_recv, + filter_arg, + map_arg, + map_span, + filter_span.with_hi(expr.span.hi()), + ); if_chain! { - if let ExprKind::MethodCall(_, _, [map_recv, map_arg], map_span) = expr.kind; - if let ExprKind::MethodCall(_, _, [_, filter_arg], filter_span) = map_recv.kind; - if is_trait_method(cx, map_recv, sym::Iterator); + if is_trait_method(cx, map_recv, sym::Iterator); - // filter(|x| ...is_some())... - if let ExprKind::Closure(_, _, filter_body_id, ..) = filter_arg.kind; - let filter_body = cx.tcx.hir().body(filter_body_id); - if let [filter_param] = filter_body.params; - // optional ref pattern: `filter(|&x| ..)` - let (filter_pat, is_filter_param_ref) = if let PatKind::Ref(ref_pat, _) = filter_param.pat.kind { - (ref_pat, true) - } else { - (filter_param.pat, false) - }; - // closure ends with is_some() or is_ok() - if let PatKind::Binding(_, filter_param_id, _, None) = filter_pat.kind; - if let ExprKind::MethodCall(path, _, [filter_arg], _) = filter_body.value.kind; - if let Some(opt_ty) = cx.typeck_results().expr_ty(filter_arg).ty_adt_def(); - if let Some(is_result) = if cx.tcx.is_diagnostic_item(sym::option_type, opt_ty.did) { - Some(false) - } else if cx.tcx.is_diagnostic_item(sym::result_type, opt_ty.did) { - Some(true) - } else { - None - }; - if path.ident.name.as_str() == if is_result { "is_ok" } else { "is_some" }; + // filter(|x| ...is_some())... + if let ExprKind::Closure(_, _, filter_body_id, ..) = filter_arg.kind; + let filter_body = cx.tcx.hir().body(filter_body_id); + if let [filter_param] = filter_body.params; + // optional ref pattern: `filter(|&x| ..)` + let (filter_pat, is_filter_param_ref) = if let PatKind::Ref(ref_pat, _) = filter_param.pat.kind { + (ref_pat, true) + } else { + (filter_param.pat, false) + }; + // closure ends with is_some() or is_ok() + if let PatKind::Binding(_, filter_param_id, _, None) = filter_pat.kind; + if let ExprKind::MethodCall(path, _, [filter_arg], _) = filter_body.value.kind; + if let Some(opt_ty) = cx.typeck_results().expr_ty(filter_arg).ty_adt_def(); + if let Some(is_result) = if cx.tcx.is_diagnostic_item(sym::option_type, opt_ty.did) { + Some(false) + } else if cx.tcx.is_diagnostic_item(sym::result_type, opt_ty.did) { + Some(true) + } else { + None + }; + if path.ident.name.as_str() == if is_result { "is_ok" } else { "is_some" }; - // ...map(|x| ...unwrap()) - if let ExprKind::Closure(_, _, map_body_id, ..) = map_arg.kind; - let map_body = cx.tcx.hir().body(map_body_id); - if let [map_param] = map_body.params; - if let PatKind::Binding(_, map_param_id, map_param_ident, None) = map_param.pat.kind; - // closure ends with expect() or unwrap() - if let ExprKind::MethodCall(seg, _, [map_arg, ..], _) = map_body.value.kind; - if matches!(seg.ident.name, sym::expect | sym::unwrap | sym::unwrap_or); + // ...map(|x| ...unwrap()) + if let ExprKind::Closure(_, _, map_body_id, ..) = map_arg.kind; + let map_body = cx.tcx.hir().body(map_body_id); + if let [map_param] = map_body.params; + if let PatKind::Binding(_, map_param_id, map_param_ident, None) = map_param.pat.kind; + // closure ends with expect() or unwrap() + if let ExprKind::MethodCall(seg, _, [map_arg, ..], _) = map_body.value.kind; + if matches!(seg.ident.name, sym::expect | sym::unwrap | sym::unwrap_or); - let eq_fallback = |a: &Expr<'_>, b: &Expr<'_>| { - // in `filter(|x| ..)`, replace `*x` with `x` - let a_path = if_chain! { - if !is_filter_param_ref; - if let ExprKind::Unary(UnOp::Deref, expr_path) = a.kind; - then { expr_path } else { a } - }; - // let the filter closure arg and the map closure arg be equal - if_chain! { - if path_to_local_id(a_path, filter_param_id); - if path_to_local_id(b, map_param_id); - if TyS::same_type(cx.typeck_results().expr_ty_adjusted(a), cx.typeck_results().expr_ty_adjusted(b)); - then { - return true; + let eq_fallback = |a: &Expr<'_>, b: &Expr<'_>| { + // in `filter(|x| ..)`, replace `*x` with `x` + let a_path = if_chain! { + if !is_filter_param_ref; + if let ExprKind::Unary(UnOp::Deref, expr_path) = a.kind; + then { expr_path } else { a } + }; + // let the filter closure arg and the map closure arg be equal + if_chain! { + if path_to_local_id(a_path, filter_param_id); + if path_to_local_id(b, map_param_id); + if TyS::same_type(cx.typeck_results().expr_ty_adjusted(a), cx.typeck_results().expr_ty_adjusted(b)); + then { + return true; + } } - } - false - }; - if SpanlessEq::new(cx).expr_fallback(eq_fallback).eq_expr(filter_arg, map_arg); - then { - let span = filter_span.to(map_span); - let (filter_name, lint) = if is_find { - ("find", MANUAL_FIND_MAP) - } else { - ("filter", MANUAL_FILTER_MAP) + false }; - let msg = format!("`{}(..).map(..)` can be simplified as `{0}_map(..)`", filter_name); - let to_opt = if is_result { ".ok()" } else { "" }; - let sugg = format!("{}_map(|{}| {}{})", filter_name, map_param_ident, - snippet(cx, map_arg.span, ".."), to_opt); - span_lint_and_sugg(cx, lint, span, &msg, "try", sugg, Applicability::MachineApplicable); - } + if SpanlessEq::new(cx).expr_fallback(eq_fallback).eq_expr(filter_arg, map_arg); + then { + let span = filter_span.with_hi(expr.span.hi()); + let (filter_name, lint) = if is_find { + ("find", MANUAL_FIND_MAP) + } else { + ("filter", MANUAL_FILTER_MAP) + }; + let msg = format!("`{}(..).map(..)` can be simplified as `{0}_map(..)`", filter_name); + let to_opt = if is_result { ".ok()" } else { "" }; + let sugg = format!("{}_map(|{}| {}{})", filter_name, map_param_ident, + snippet(cx, map_arg.span, ".."), to_opt); + span_lint_and_sugg(cx, lint, span, &msg, "try", sugg, Applicability::MachineApplicable); + } } } diff --git a/clippy_lints/src/methods/filter_map_identity.rs b/clippy_lints/src/methods/filter_map_identity.rs index 80598d88508..3a61f4ccad7 100644 --- a/clippy_lints/src/methods/filter_map_identity.rs +++ b/clippy_lints/src/methods/filter_map_identity.rs @@ -8,15 +8,8 @@ use rustc_span::{source_map::Span, sym}; use super::FILTER_MAP_IDENTITY; -pub(super) fn check( - cx: &LateContext<'_>, - expr: &hir::Expr<'_>, - filter_map_args: &[hir::Expr<'_>], - filter_map_span: Span, -) { +pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, filter_map_arg: &hir::Expr<'_>, filter_map_span: Span) { if is_trait_method(cx, expr, sym::Iterator) { - let arg_node = &filter_map_args[1].kind; - let apply_lint = |message: &str| { span_lint_and_sugg( cx, @@ -30,8 +23,8 @@ pub(super) fn check( }; if_chain! { - if let hir::ExprKind::Closure(_, _, body_id, _, _) = arg_node; - let body = cx.tcx.hir().body(*body_id); + if let hir::ExprKind::Closure(_, _, body_id, _, _) = filter_map_arg.kind; + let body = cx.tcx.hir().body(body_id); if let hir::PatKind::Binding(_, binding_id, ..) = body.params[0].pat.kind; if path_to_local_id(&body.value, binding_id); @@ -41,7 +34,7 @@ pub(super) fn check( } if_chain! { - if let hir::ExprKind::Path(ref qpath) = arg_node; + if let hir::ExprKind::Path(ref qpath) = filter_map_arg.kind; if match_qpath(qpath, &paths::STD_CONVERT_IDENTITY); diff --git a/clippy_lints/src/methods/filter_map_next.rs b/clippy_lints/src/methods/filter_map_next.rs index ba57abd16c9..2b19e4ee8c0 100644 --- a/clippy_lints/src/methods/filter_map_next.rs +++ b/clippy_lints/src/methods/filter_map_next.rs @@ -14,7 +14,8 @@ const FILTER_MAP_NEXT_MSRV: RustcVersion = RustcVersion::new(1, 30, 0); pub(super) fn check<'tcx>( cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>, - filter_args: &'tcx [hir::Expr<'_>], + recv: &'tcx hir::Expr<'_>, + arg: &'tcx hir::Expr<'_>, msrv: Option<&RustcVersion>, ) { if is_trait_method(cx, expr, sym::Iterator) { @@ -24,9 +25,9 @@ pub(super) fn check<'tcx>( let msg = "called `filter_map(..).next()` on an `Iterator`. This is more succinctly expressed by calling \ `.find_map(..)` instead"; - let filter_snippet = snippet(cx, filter_args[1].span, ".."); + let filter_snippet = snippet(cx, arg.span, ".."); if filter_snippet.lines().count() <= 1 { - let iter_snippet = snippet(cx, filter_args[0].span, ".."); + let iter_snippet = snippet(cx, recv.span, ".."); span_lint_and_sugg( cx, FILTER_MAP_NEXT, diff --git a/clippy_lints/src/methods/filter_next.rs b/clippy_lints/src/methods/filter_next.rs index 6cd24334414..172714f6b01 100644 --- a/clippy_lints/src/methods/filter_next.rs +++ b/clippy_lints/src/methods/filter_next.rs @@ -9,14 +9,19 @@ use rustc_span::sym; use super::FILTER_NEXT; /// lint use of `filter().next()` for `Iterators` -pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>, filter_args: &'tcx [hir::Expr<'_>]) { +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx hir::Expr<'_>, + recv: &'tcx hir::Expr<'_>, + filter_arg: &'tcx hir::Expr<'_>, +) { // lint if caller of `.filter().next()` is an Iterator if is_trait_method(cx, expr, sym::Iterator) { let msg = "called `filter(..).next()` on an `Iterator`. This is more succinctly expressed by calling \ `.find(..)` instead"; - let filter_snippet = snippet(cx, filter_args[1].span, ".."); + let filter_snippet = snippet(cx, filter_arg.span, ".."); if filter_snippet.lines().count() <= 1 { - let iter_snippet = snippet(cx, filter_args[0].span, ".."); + let iter_snippet = snippet(cx, recv.span, ".."); // add note if not multi-line span_lint_and_sugg( cx, diff --git a/clippy_lints/src/methods/flat_map_identity.rs b/clippy_lints/src/methods/flat_map_identity.rs index 034ea6c6562..dd613d0cd63 100644 --- a/clippy_lints/src/methods/flat_map_identity.rs +++ b/clippy_lints/src/methods/flat_map_identity.rs @@ -12,11 +12,11 @@ use super::FLAT_MAP_IDENTITY; pub(super) fn check<'tcx>( cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>, - flat_map_args: &'tcx [hir::Expr<'_>], + flat_map_arg: &'tcx hir::Expr<'_>, flat_map_span: Span, ) { if is_trait_method(cx, expr, sym::Iterator) { - let arg_node = &flat_map_args[1].kind; + let arg_node = &flat_map_arg.kind; let apply_lint = |message: &str| { span_lint_and_sugg( @@ -35,7 +35,7 @@ pub(super) fn check<'tcx>( let body = cx.tcx.hir().body(*body_id); if let hir::PatKind::Binding(_, _, binding_ident, _) = body.params[0].pat.kind; - if let hir::ExprKind::Path(hir::QPath::Resolved(_, ref path)) = body.value.kind; + if let hir::ExprKind::Path(hir::QPath::Resolved(_, path)) = body.value.kind; if path.segments.len() == 1; if path.segments[0].ident.name == binding_ident.name; diff --git a/clippy_lints/src/methods/from_iter_instead_of_collect.rs b/clippy_lints/src/methods/from_iter_instead_of_collect.rs index 15cf5674313..707c54f7a3c 100644 --- a/clippy_lints/src/methods/from_iter_instead_of_collect.rs +++ b/clippy_lints/src/methods/from_iter_instead_of_collect.rs @@ -40,8 +40,8 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, args: &[hir::Exp } fn extract_turbofish(cx: &LateContext<'_>, expr: &hir::Expr<'_>, ty: Ty<'tcx>) -> String { + let call_site = expr.span.source_callsite(); if_chain! { - let call_site = expr.span.source_callsite(); if let Ok(snippet) = cx.sess().source_map().span_to_snippet(call_site); let snippet_split = snippet.split("::").collect::<Vec<_>>(); if let Some((_, elements)) = snippet_split.split_last(); diff --git a/clippy_lints/src/methods/get_unwrap.rs b/clippy_lints/src/methods/get_unwrap.rs index b3a9743c614..54f28064384 100644 --- a/clippy_lints/src/methods/get_unwrap.rs +++ b/clippy_lints/src/methods/get_unwrap.rs @@ -11,18 +11,20 @@ use rustc_span::sym; use super::GET_UNWRAP; -pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &hir::Expr<'_>, get_args: &'tcx [hir::Expr<'_>], is_mut: bool) { +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + expr: &hir::Expr<'_>, + recv: &'tcx hir::Expr<'tcx>, + get_arg: &'tcx hir::Expr<'_>, + is_mut: bool, +) { // Note: we don't want to lint `get_mut().unwrap` for `HashMap` or `BTreeMap`, // because they do not implement `IndexMut` let mut applicability = Applicability::MachineApplicable; - let expr_ty = cx.typeck_results().expr_ty(&get_args[0]); - let get_args_str = if get_args.len() > 1 { - snippet_with_applicability(cx, get_args[1].span, "..", &mut applicability) - } else { - return; // not linting on a .get().unwrap() chain or variant - }; + let expr_ty = cx.typeck_results().expr_ty(recv); + let get_args_str = snippet_with_applicability(cx, get_arg.span, "..", &mut applicability); let mut needs_ref; - let caller_type = if derefs_to_slice(cx, &get_args[0], expr_ty).is_some() { + let caller_type = if derefs_to_slice(cx, recv, expr_ty).is_some() { needs_ref = get_args_str.parse::<usize>().is_ok(); "slice" } else if is_type_diagnostic_item(cx, expr_ty, sym::vec_type) { @@ -77,7 +79,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &hir::Expr<'_>, get_args format!( "{}{}[{}]", borrow_str, - snippet_with_applicability(cx, get_args[0].span, "..", &mut applicability), + snippet_with_applicability(cx, recv.span, "..", &mut applicability), get_args_str ), applicability, diff --git a/clippy_lints/src/methods/implicit_clone.rs b/clippy_lints/src/methods/implicit_clone.rs index 04461ad5c3a..1211e2f2bf7 100644 --- a/clippy_lints/src/methods/implicit_clone.rs +++ b/clippy_lints/src/methods/implicit_clone.rs @@ -13,7 +13,7 @@ use clippy_utils::is_diagnostic_assoc_item; pub fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, trait_diagnostic: Symbol) { if_chain! { if let ExprKind::MethodCall(method_path, _, [arg], _) = &expr.kind; - let return_type = cx.typeck_results().expr_ty(&expr); + let return_type = cx.typeck_results().expr_ty(expr); let input_type = cx.typeck_results().expr_ty(arg).peel_refs(); if let Some(expr_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id); if let Some(ty_name) = input_type.ty_adt_def().map(|adt_def| cx.tcx.item_name(adt_def.did)); diff --git a/clippy_lints/src/methods/iter_cloned_collect.rs b/clippy_lints/src/methods/iter_cloned_collect.rs index 848f47e39f6..739f313716e 100644 --- a/clippy_lints/src/methods/iter_cloned_collect.rs +++ b/clippy_lints/src/methods/iter_cloned_collect.rs @@ -9,10 +9,10 @@ use rustc_span::sym; use super::ITER_CLONED_COLLECT; -pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &hir::Expr<'_>, iter_args: &'tcx [hir::Expr<'_>]) { +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &hir::Expr<'_>, recv: &'tcx hir::Expr<'_>) { if_chain! { if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(expr), sym::vec_type); - if let Some(slice) = derefs_to_slice(cx, &iter_args[0], cx.typeck_results().expr_ty(&iter_args[0])); + if let Some(slice) = derefs_to_slice(cx, recv, cx.typeck_results().expr_ty(recv)); if let Some(to_replace) = expr.span.trim_start(slice.span.source_callsite()); then { diff --git a/clippy_lints/src/methods/iter_count.rs b/clippy_lints/src/methods/iter_count.rs index e394a8fe819..c6b7c7cd179 100644 --- a/clippy_lints/src/methods/iter_count.rs +++ b/clippy_lints/src/methods/iter_count.rs @@ -10,9 +10,9 @@ use rustc_span::sym; use super::ITER_COUNT; -pub(crate) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>, iter_args: &'tcx [Expr<'tcx>], iter_method: &str) { - let ty = cx.typeck_results().expr_ty(&iter_args[0]); - let caller_type = if derefs_to_slice(cx, &iter_args[0], ty).is_some() { +pub(crate) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>, recv: &'tcx Expr<'tcx>, iter_method: &str) { + let ty = cx.typeck_results().expr_ty(recv); + let caller_type = if derefs_to_slice(cx, recv, ty).is_some() { "slice" } else if is_type_diagnostic_item(cx, ty, sym::vec_type) { "Vec" @@ -42,7 +42,7 @@ pub(crate) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>, iter_args: &' "try", format!( "{}.len()", - snippet_with_applicability(cx, iter_args[0].span, "..", &mut applicability), + snippet_with_applicability(cx, recv.span, "..", &mut applicability), ), applicability, ); diff --git a/clippy_lints/src/methods/iter_next_slice.rs b/clippy_lints/src/methods/iter_next_slice.rs index e9b37b6f2bd..a49851de38e 100644 --- a/clippy_lints/src/methods/iter_next_slice.rs +++ b/clippy_lints/src/methods/iter_next_slice.rs @@ -13,9 +13,7 @@ use rustc_span::symbol::sym; use super::ITER_NEXT_SLICE; -pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>, iter_args: &'tcx [hir::Expr<'_>]) { - let caller_expr = &iter_args[0]; - +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>, caller_expr: &'tcx hir::Expr<'_>) { // Skip lint if the `iter().next()` expression is a for loop argument, // since it is already covered by `&loops::ITER_NEXT_LOOP` let mut parent_expr_opt = get_parent_expr(cx, expr); @@ -29,7 +27,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>, ite if derefs_to_slice(cx, caller_expr, cx.typeck_results().expr_ty(caller_expr)).is_some() { // caller is a Slice if_chain! { - if let hir::ExprKind::Index(ref caller_var, ref index_expr) = &caller_expr.kind; + if let hir::ExprKind::Index(caller_var, index_expr) = &caller_expr.kind; if let Some(higher::Range { start: Some(start_expr), end: None, limits: ast::RangeLimits::HalfOpen }) = higher::range(index_expr); if let hir::ExprKind::Lit(ref start_lit) = &start_expr.kind; diff --git a/clippy_lints/src/methods/iter_nth.rs b/clippy_lints/src/methods/iter_nth.rs index c46af427b3c..c2232239fe4 100644 --- a/clippy_lints/src/methods/iter_nth.rs +++ b/clippy_lints/src/methods/iter_nth.rs @@ -11,20 +11,20 @@ use super::ITER_NTH; pub(super) fn check<'tcx>( cx: &LateContext<'tcx>, expr: &hir::Expr<'_>, - nth_and_iter_args: &[&'tcx [hir::Expr<'tcx>]], + iter_recv: &'tcx hir::Expr<'tcx>, + nth_recv: &hir::Expr<'_>, + nth_arg: &hir::Expr<'_>, is_mut: bool, ) { - let iter_args = nth_and_iter_args[1]; let mut_str = if is_mut { "_mut" } else { "" }; - let caller_type = if derefs_to_slice(cx, &iter_args[0], cx.typeck_results().expr_ty(&iter_args[0])).is_some() { + let caller_type = if derefs_to_slice(cx, iter_recv, cx.typeck_results().expr_ty(iter_recv)).is_some() { "slice" - } else if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(&iter_args[0]), sym::vec_type) { + } else if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(iter_recv), sym::vec_type) { "Vec" - } else if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(&iter_args[0]), sym::vecdeque_type) { + } else if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(iter_recv), sym::vecdeque_type) { "VecDeque" } else { - let nth_args = nth_and_iter_args[0]; - iter_nth_zero::check(cx, expr, &nth_args); + iter_nth_zero::check(cx, expr, nth_recv, nth_arg); return; // caller is not a type that we want to lint }; diff --git a/clippy_lints/src/methods/iter_nth_zero.rs b/clippy_lints/src/methods/iter_nth_zero.rs index a12f672739c..52d7c15332e 100644 --- a/clippy_lints/src/methods/iter_nth_zero.rs +++ b/clippy_lints/src/methods/iter_nth_zero.rs @@ -10,10 +10,10 @@ use rustc_span::sym; use super::ITER_NTH_ZERO; -pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &hir::Expr<'_>, nth_args: &'tcx [hir::Expr<'_>]) { +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>, arg: &hir::Expr<'_>) { if_chain! { if is_trait_method(cx, expr, sym::Iterator); - if let Some((Constant::Int(0), _)) = constant(cx, cx.typeck_results(), &nth_args[1]); + if let Some((Constant::Int(0), _)) = constant(cx, cx.typeck_results(), arg); then { let mut applicability = Applicability::MachineApplicable; span_lint_and_sugg( @@ -22,7 +22,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &hir::Expr<'_>, nth_args expr.span, "called `.nth(0)` on a `std::iter::Iterator`, when `.next()` is equivalent", "try calling `.next()` instead of `.nth(0)`", - format!("{}.next()", snippet_with_applicability(cx, nth_args[0].span, "..", &mut applicability)), + format!("{}.next()", snippet_with_applicability(cx, recv.span, "..", &mut applicability)), applicability, ); } diff --git a/clippy_lints/src/methods/iter_skip_next.rs b/clippy_lints/src/methods/iter_skip_next.rs index b1d398876d3..e32594757d0 100644 --- a/clippy_lints/src/methods/iter_skip_next.rs +++ b/clippy_lints/src/methods/iter_skip_next.rs @@ -8,19 +8,17 @@ use rustc_span::sym; use super::ITER_SKIP_NEXT; -pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, skip_args: &[hir::Expr<'_>]) { +pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>, arg: &hir::Expr<'_>) { // lint if caller of skip is an Iterator if is_trait_method(cx, expr, sym::Iterator) { - if let [caller, n] = skip_args { - span_lint_and_sugg( - cx, - ITER_SKIP_NEXT, - expr.span.trim_start(caller.span).unwrap(), - "called `skip(..).next()` on an iterator", - "use `nth` instead", - format!(".nth({})", snippet(cx, n.span, "..")), - Applicability::MachineApplicable, - ); - } + span_lint_and_sugg( + cx, + ITER_SKIP_NEXT, + expr.span.trim_start(recv.span).unwrap(), + "called `skip(..).next()` on an iterator", + "use `nth` instead", + format!(".nth({})", snippet(cx, arg.span, "..")), + Applicability::MachineApplicable, + ); } } diff --git a/clippy_lints/src/methods/iterator_step_by_zero.rs b/clippy_lints/src/methods/iterator_step_by_zero.rs index 3baa580314f..06b12998b1a 100644 --- a/clippy_lints/src/methods/iterator_step_by_zero.rs +++ b/clippy_lints/src/methods/iterator_step_by_zero.rs @@ -7,9 +7,9 @@ use rustc_span::sym; use super::ITERATOR_STEP_BY_ZERO; -pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &hir::Expr<'_>, args: &'tcx [hir::Expr<'_>]) { +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &hir::Expr<'_>, arg: &'tcx hir::Expr<'_>) { if is_trait_method(cx, expr, sym::Iterator) { - if let Some((Constant::Int(0), _)) = constant(cx, cx.typeck_results(), &args[1]) { + if let Some((Constant::Int(0), _)) = constant(cx, cx.typeck_results(), arg) { span_lint( cx, ITERATOR_STEP_BY_ZERO, diff --git a/clippy_lints/src/methods/manual_saturating_arithmetic.rs b/clippy_lints/src/methods/manual_saturating_arithmetic.rs index f16699322d1..ecb8b72ef46 100644 --- a/clippy_lints/src/methods/manual_saturating_arithmetic.rs +++ b/clippy_lints/src/methods/manual_saturating_arithmetic.rs @@ -8,11 +8,14 @@ use rustc_hir as hir; use rustc_lint::LateContext; use rustc_target::abi::LayoutOf; -pub fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, args: &[&[hir::Expr<'_>]], arith: &str) { - let unwrap_arg = &args[0][1]; - let arith_lhs = &args[1][0]; - let arith_rhs = &args[1][1]; - +pub fn check( + cx: &LateContext<'_>, + expr: &hir::Expr<'_>, + arith_lhs: &hir::Expr<'_>, + arith_rhs: &hir::Expr<'_>, + unwrap_arg: &hir::Expr<'_>, + arith: &str, +) { let ty = cx.typeck_results().expr_ty(arith_lhs); if !ty.is_integral() { return; @@ -41,44 +44,28 @@ pub fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, args: &[&[hir::Expr<'_> // "mul" is omitted because lhs can be negative. _ => return, } - - let mut applicability = Applicability::MachineApplicable; - span_lint_and_sugg( - cx, - super::MANUAL_SATURATING_ARITHMETIC, - expr.span, - "manual saturating arithmetic", - &format!("try using `saturating_{}`", arith), - format!( - "{}.saturating_{}({})", - snippet_with_applicability(cx, arith_lhs.span, "..", &mut applicability), - arith, - snippet_with_applicability(cx, arith_rhs.span, "..", &mut applicability), - ), - applicability, - ); } else { match (mm, arith) { (MinMax::Max, "add" | "mul") | (MinMax::Min, "sub") => (), _ => return, } - - let mut applicability = Applicability::MachineApplicable; - span_lint_and_sugg( - cx, - super::MANUAL_SATURATING_ARITHMETIC, - expr.span, - "manual saturating arithmetic", - &format!("try using `saturating_{}`", arith), - format!( - "{}.saturating_{}({})", - snippet_with_applicability(cx, arith_lhs.span, "..", &mut applicability), - arith, - snippet_with_applicability(cx, arith_rhs.span, "..", &mut applicability), - ), - applicability, - ); } + + let mut applicability = Applicability::MachineApplicable; + span_lint_and_sugg( + cx, + super::MANUAL_SATURATING_ARITHMETIC, + expr.span, + "manual saturating arithmetic", + &format!("try using `saturating_{}`", arith), + format!( + "{}.saturating_{}({})", + snippet_with_applicability(cx, arith_lhs.span, "..", &mut applicability), + arith, + snippet_with_applicability(cx, arith_rhs.span, "..", &mut applicability), + ), + applicability, + ); } #[derive(PartialEq, Eq)] diff --git a/clippy_lints/src/methods/map_collect_result_unit.rs b/clippy_lints/src/methods/map_collect_result_unit.rs index e4402b2da21..82063ad70b5 100644 --- a/clippy_lints/src/methods/map_collect_result_unit.rs +++ b/clippy_lints/src/methods/map_collect_result_unit.rs @@ -14,13 +14,13 @@ use super::MAP_COLLECT_RESULT_UNIT; pub(super) fn check( cx: &LateContext<'_>, expr: &hir::Expr<'_>, - map_args: &[hir::Expr<'_>], - collect_args: &[hir::Expr<'_>], + iter: &hir::Expr<'_>, + map_fn: &hir::Expr<'_>, + collect_recv: &hir::Expr<'_>, ) { if_chain! { // called on Iterator - if let [map_expr] = collect_args; - if is_trait_method(cx, map_expr, sym::Iterator); + if is_trait_method(cx, collect_recv, sym::Iterator); // return of collect `Result<(),_>` let collect_ret_ty = cx.typeck_results().expr_ty(expr); if is_type_diagnostic_item(cx, collect_ret_ty, sym::result_type); @@ -28,7 +28,6 @@ pub(super) fn check( if let Some(result_t) = substs.types().next(); if result_t.is_unit(); // get parts for snippet - if let [iter, map_fn] = map_args; then { span_lint_and_sugg( cx, diff --git a/clippy_lints/src/methods/map_flatten.rs b/clippy_lints/src/methods/map_flatten.rs index 4bc52b036a8..e8ad16bc0de 100644 --- a/clippy_lints/src/methods/map_flatten.rs +++ b/clippy_lints/src/methods/map_flatten.rs @@ -11,10 +11,15 @@ use rustc_span::symbol::sym; use super::MAP_FLATTEN; /// lint use of `map().flatten()` for `Iterators` and 'Options' -pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>, map_args: &'tcx [hir::Expr<'_>]) { +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx hir::Expr<'_>, + recv: &'tcx hir::Expr<'_>, + map_arg: &'tcx hir::Expr<'_>, +) { // lint if caller of `.map().flatten()` is an Iterator if is_trait_method(cx, expr, sym::Iterator) { - let map_closure_ty = cx.typeck_results().expr_ty(&map_args[1]); + let map_closure_ty = cx.typeck_results().expr_ty(map_arg); let is_map_to_option = match map_closure_ty.kind() { ty::Closure(_, _) | ty::FnDef(_, _) | ty::FnPtr(_) => { let map_closure_sig = match map_closure_ty.kind() { @@ -34,12 +39,12 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>, map // `(...).map(...)` has type `impl Iterator<Item=impl Iterator<...>> "flat_map" }; - let func_snippet = snippet(cx, map_args[1].span, ".."); + let func_snippet = snippet(cx, map_arg.span, ".."); let hint = format!(".{0}({1})", method_to_use, func_snippet); span_lint_and_sugg( cx, MAP_FLATTEN, - expr.span.with_lo(map_args[0].span.hi()), + expr.span.with_lo(recv.span.hi()), "called `map(..).flatten()` on an `Iterator`", &format!("try using `{}` instead", method_to_use), hint, @@ -48,13 +53,13 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>, map } // lint if caller of `.map().flatten()` is an Option - if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(&map_args[0]), sym::option_type) { - let func_snippet = snippet(cx, map_args[1].span, ".."); + if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::option_type) { + let func_snippet = snippet(cx, map_arg.span, ".."); let hint = format!(".and_then({})", func_snippet); span_lint_and_sugg( cx, MAP_FLATTEN, - expr.span.with_lo(map_args[0].span.hi()), + expr.span.with_lo(recv.span.hi()), "called `map(..).flatten()` on an `Option`", "try using `and_then` instead", hint, diff --git a/clippy_lints/src/methods/map_unwrap_or.rs b/clippy_lints/src/methods/map_unwrap_or.rs index deb4b4492b5..4330fea727b 100644 --- a/clippy_lints/src/methods/map_unwrap_or.rs +++ b/clippy_lints/src/methods/map_unwrap_or.rs @@ -18,22 +18,23 @@ const MAP_UNWRAP_OR_MSRV: RustcVersion = RustcVersion::new(1, 41, 0); pub(super) fn check<'tcx>( cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>, - map_args: &'tcx [hir::Expr<'_>], - unwrap_args: &'tcx [hir::Expr<'_>], + recv: &'tcx hir::Expr<'_>, + map_arg: &'tcx hir::Expr<'_>, + unwrap_arg: &'tcx hir::Expr<'_>, msrv: Option<&RustcVersion>, ) -> bool { if !meets_msrv(msrv, &MAP_UNWRAP_OR_MSRV) { return false; } // lint if the caller of `map()` is an `Option` - let is_option = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(&map_args[0]), sym::option_type); - let is_result = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(&map_args[0]), sym::result_type); + let is_option = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::option_type); + let is_result = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::result_type); if is_option || is_result { // Don't make a suggestion that may fail to compile due to mutably borrowing // the same variable twice. - let map_mutated_vars = mutated_variables(&map_args[0], cx); - let unwrap_mutated_vars = mutated_variables(&unwrap_args[1], cx); + let map_mutated_vars = mutated_variables(recv, cx); + let unwrap_mutated_vars = mutated_variables(unwrap_arg, cx); if let (Some(map_mutated_vars), Some(unwrap_mutated_vars)) = (map_mutated_vars, unwrap_mutated_vars) { if map_mutated_vars.intersection(&unwrap_mutated_vars).next().is_some() { return false; @@ -51,14 +52,14 @@ pub(super) fn check<'tcx>( `.map_or_else(<g>, <f>)` instead" }; // get snippets for args to map() and unwrap_or_else() - let map_snippet = snippet(cx, map_args[1].span, ".."); - let unwrap_snippet = snippet(cx, unwrap_args[1].span, ".."); + let map_snippet = snippet(cx, map_arg.span, ".."); + let unwrap_snippet = snippet(cx, unwrap_arg.span, ".."); // lint, with note if neither arg is > 1 line and both map() and // unwrap_or_else() have the same span let multiline = map_snippet.lines().count() > 1 || unwrap_snippet.lines().count() > 1; - let same_span = map_args[1].span.ctxt() == unwrap_args[1].span.ctxt(); + let same_span = map_arg.span.ctxt() == unwrap_arg.span.ctxt(); if same_span && !multiline { - let var_snippet = snippet(cx, map_args[0].span, ".."); + let var_snippet = snippet(cx, recv.span, ".."); span_lint_and_sugg( cx, MAP_UNWRAP_OR, diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index fccdee07877..b1ade5addd6 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -62,17 +62,18 @@ mod zst_offset; use bind_instead_of_map::BindInsteadOfMap; use clippy_utils::diagnostics::{span_lint, span_lint_and_help}; use clippy_utils::ty::{contains_adt_constructor, contains_ty, implements_trait, is_copy, is_type_diagnostic_item}; -use clippy_utils::{contains_return, get_trait_def_id, in_macro, iter_input_pats, method_calls, paths, return_ty}; +use clippy_utils::{contains_return, get_trait_def_id, in_macro, iter_input_pats, paths, return_ty}; use if_chain::if_chain; use rustc_hir as hir; use rustc_hir::def::Res; -use rustc_hir::{PrimTy, QPath, TraitItem, TraitItemKind}; +use rustc_hir::{Expr, ExprKind, PrimTy, QPath, TraitItem, TraitItemKind}; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_middle::lint::in_external_macro; use rustc_middle::ty::{self, TraitRef, Ty, TyS}; use rustc_semver::RustcVersion; use rustc_session::{declare_tool_lint, impl_lint_pass}; -use rustc_span::symbol::{sym, SymbolStr}; +use rustc_span::symbol::SymbolStr; +use rustc_span::{sym, Span}; use rustc_typeck::hir_ty_to_ty; declare_clippy_lint! { @@ -205,6 +206,13 @@ declare_clippy_lint! { /// |`to_` | not `_mut` |`self` | `Copy` | /// |`to_` | not `_mut` |`&self` | not `Copy` | /// + /// Note: Clippy doesn't trigger methods with `to_` prefix in: + /// - Traits definition. + /// Clippy can not tell if a type that implements a trait is `Copy` or not. + /// - Traits implementation, when `&self` is taken. + /// The method signature is controlled by the trait and often `&self` is required for all types that implement the trait + /// (see e.g. the `std::string::ToString` trait). + /// /// Please find more info here: /// https://rust-lang.github.io/api-guidelines/naming.html#ad-hoc-conversions-follow-as_-to_-into_-conventions-c-conv /// @@ -897,6 +905,28 @@ declare_clippy_lint! { } declare_clippy_lint! { + /// **What it does:** Checks for indirect collection of populated `Option` + /// + /// **Why is this bad?** `Option` is like a collection of 0-1 things, so `flatten` + /// automatically does this without suspicious-looking `unwrap` calls. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```rust + /// let _ = std::iter::empty::<Option<i32>>().filter(Option::is_some).map(Option::unwrap); + /// ``` + /// Use instead: + /// ```rust + /// let _ = std::iter::empty::<Option<i32>>().flatten(); + /// ``` + pub OPTION_FILTER_MAP, + complexity, + "filtering `Option` for `Some` then force-unwrapping, which can be one type-safe operation" +} + +declare_clippy_lint! { /// **What it does:** Checks for the use of `iter.nth(0)`. /// /// **Why is this bad?** `iter.next()` is equivalent to @@ -1651,6 +1681,7 @@ impl_lint_pass!(Methods => [ FILTER_MAP_IDENTITY, MANUAL_FILTER_MAP, MANUAL_FIND_MAP, + OPTION_FILTER_MAP, FILTER_MAP_NEXT, FLAT_MAP_IDENTITY, MAP_FLATTEN, @@ -1681,140 +1712,38 @@ impl_lint_pass!(Methods => [ IMPLICIT_CLONE ]); +/// Extracts a method call name, args, and `Span` of the method name. +fn method_call<'tcx>(recv: &'tcx hir::Expr<'tcx>) -> Option<(SymbolStr, &'tcx [hir::Expr<'tcx>], Span)> { + if let ExprKind::MethodCall(path, span, args, _) = recv.kind { + if !args.iter().any(|e| e.span.from_expansion()) { + return Some((path.ident.name.as_str(), args, span)); + } + } + None +} + +/// Same as `method_call` but the `SymbolStr` is dereferenced into a temporary `&str` +macro_rules! method_call { + ($expr:expr) => { + method_call($expr) + .as_ref() + .map(|&(ref name, args, span)| (&**name, args, span)) + }; +} + impl<'tcx> LateLintPass<'tcx> for Methods { - #[allow(clippy::too_many_lines)] fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) { if in_macro(expr.span) { return; } - let (method_names, arg_lists, method_spans) = method_calls(expr, 2); - let method_names: Vec<SymbolStr> = method_names.iter().map(|s| s.as_str()).collect(); - let method_names: Vec<&str> = method_names.iter().map(|s| &**s).collect(); - - match method_names.as_slice() { - ["unwrap", "get"] => get_unwrap::check(cx, expr, arg_lists[1], false), - ["unwrap", "get_mut"] => get_unwrap::check(cx, expr, arg_lists[1], true), - ["unwrap", ..] => unwrap_used::check(cx, expr, arg_lists[0]), - ["expect", "ok"] => ok_expect::check(cx, expr, arg_lists[1]), - ["expect", ..] => expect_used::check(cx, expr, arg_lists[0]), - ["unwrap_or", "map"] => option_map_unwrap_or::check(cx, expr, arg_lists[1], arg_lists[0], method_spans[1]), - ["unwrap_or_else", "map"] => { - if !map_unwrap_or::check(cx, expr, arg_lists[1], arg_lists[0], self.msrv.as_ref()) { - unnecessary_lazy_eval::check(cx, expr, arg_lists[0], "unwrap_or"); - } - }, - ["map_or", ..] => option_map_or_none::check(cx, expr, arg_lists[0]), - ["and_then", ..] => { - let biom_option_linted = bind_instead_of_map::OptionAndThenSome::check(cx, expr, arg_lists[0]); - let biom_result_linted = bind_instead_of_map::ResultAndThenOk::check(cx, expr, arg_lists[0]); - if !biom_option_linted && !biom_result_linted { - unnecessary_lazy_eval::check(cx, expr, arg_lists[0], "and"); - } - }, - ["or_else", ..] => { - if !bind_instead_of_map::ResultOrElseErrInfo::check(cx, expr, arg_lists[0]) { - unnecessary_lazy_eval::check(cx, expr, arg_lists[0], "or"); - } - }, - ["next", "filter"] => filter_next::check(cx, expr, arg_lists[1]), - ["next", "skip_while"] => skip_while_next::check(cx, expr, arg_lists[1]), - ["next", "iter"] => iter_next_slice::check(cx, expr, arg_lists[1]), - ["map", "filter"] => filter_map::check(cx, expr, false), - ["map", "filter_map"] => filter_map_map::check(cx, expr), - ["next", "filter_map"] => filter_map_next::check(cx, expr, arg_lists[1], self.msrv.as_ref()), - ["map", "find"] => filter_map::check(cx, expr, true), - ["flat_map", "filter"] => filter_flat_map::check(cx, expr), - ["flat_map", "filter_map"] => filter_map_flat_map::check(cx, expr), - ["flat_map", ..] => flat_map_identity::check(cx, expr, arg_lists[0], method_spans[0]), - ["flatten", "map"] => map_flatten::check(cx, expr, arg_lists[1]), - [option_check_method, "find"] if "is_some" == *option_check_method || "is_none" == *option_check_method => { - search_is_some::check( - cx, - expr, - "find", - option_check_method, - arg_lists[1], - arg_lists[0], - method_spans[1], - ) - }, - [option_check_method, "position"] - if "is_some" == *option_check_method || "is_none" == *option_check_method => - { - search_is_some::check( - cx, - expr, - "position", - option_check_method, - arg_lists[1], - arg_lists[0], - method_spans[1], - ) - }, - [option_check_method, "rposition"] - if "is_some" == *option_check_method || "is_none" == *option_check_method => - { - search_is_some::check( - cx, - expr, - "rposition", - option_check_method, - arg_lists[1], - arg_lists[0], - method_spans[1], - ) - }, - ["extend", ..] => string_extend_chars::check(cx, expr, arg_lists[0]), - ["count", "into_iter"] => iter_count::check(cx, expr, &arg_lists[1], "into_iter"), - ["count", "iter"] => iter_count::check(cx, expr, &arg_lists[1], "iter"), - ["count", "iter_mut"] => iter_count::check(cx, expr, &arg_lists[1], "iter_mut"), - ["nth", "iter"] => iter_nth::check(cx, expr, &arg_lists, false), - ["nth", "iter_mut"] => iter_nth::check(cx, expr, &arg_lists, true), - ["nth", "bytes"] => bytes_nth::check(cx, expr, &arg_lists[1]), - ["nth", ..] => iter_nth_zero::check(cx, expr, arg_lists[0]), - ["step_by", ..] => iterator_step_by_zero::check(cx, expr, arg_lists[0]), - ["next", "skip"] => iter_skip_next::check(cx, expr, arg_lists[1]), - ["collect", "cloned"] => iter_cloned_collect::check(cx, expr, arg_lists[1]), - ["as_ref"] => useless_asref::check(cx, expr, "as_ref", arg_lists[0]), - ["as_mut"] => useless_asref::check(cx, expr, "as_mut", arg_lists[0]), - ["fold", ..] => unnecessary_fold::check(cx, expr, arg_lists[0], method_spans[0]), - ["filter_map", ..] => { - unnecessary_filter_map::check(cx, expr, arg_lists[0]); - filter_map_identity::check(cx, expr, arg_lists[0], method_spans[0]); - }, - ["count", "map"] => suspicious_map::check(cx, expr, arg_lists[1], arg_lists[0]), - ["assume_init"] => uninit_assumed_init::check(cx, &arg_lists[0][0], expr), - ["unwrap_or", arith @ ("checked_add" | "checked_sub" | "checked_mul")] => { - manual_saturating_arithmetic::check(cx, expr, &arg_lists, &arith["checked_".len()..]) - }, - ["add" | "offset" | "sub" | "wrapping_offset" | "wrapping_add" | "wrapping_sub"] => { - zst_offset::check(cx, expr, arg_lists[0]) - }, - ["is_file", ..] => filetype_is_file::check(cx, expr, arg_lists[0]), - ["map", "as_ref"] => { - option_as_ref_deref::check(cx, expr, arg_lists[1], arg_lists[0], false, self.msrv.as_ref()) - }, - ["map", "as_mut"] => { - option_as_ref_deref::check(cx, expr, arg_lists[1], arg_lists[0], true, self.msrv.as_ref()) - }, - ["unwrap_or_else", ..] => unnecessary_lazy_eval::check(cx, expr, arg_lists[0], "unwrap_or"), - ["get_or_insert_with", ..] => unnecessary_lazy_eval::check(cx, expr, arg_lists[0], "get_or_insert"), - ["ok_or_else", ..] => unnecessary_lazy_eval::check(cx, expr, arg_lists[0], "ok_or"), - ["collect", "map"] => map_collect_result_unit::check(cx, expr, arg_lists[1], arg_lists[0]), - ["for_each", "inspect"] => inspect_for_each::check(cx, expr, method_spans[1]), - ["to_owned", ..] => implicit_clone::check(cx, expr, sym::ToOwned), - ["to_os_string", ..] => implicit_clone::check(cx, expr, sym::OsStr), - ["to_path_buf", ..] => implicit_clone::check(cx, expr, sym::Path), - ["to_vec", ..] => implicit_clone::check(cx, expr, sym::slice), - _ => {}, - } + check_methods(cx, expr, self.msrv.as_ref()); match expr.kind { - hir::ExprKind::Call(ref func, ref args) => { + hir::ExprKind::Call(func, args) => { from_iter_instead_of_collect::check(cx, expr, args, &func.kind); }, - hir::ExprKind::MethodCall(ref method_call, ref method_span, ref args, _) => { + hir::ExprKind::MethodCall(method_call, ref method_span, args, _) => { or_fun_call::check(cx, expr, *method_span, &method_call.ident.as_str(), args); expect_fun_call::check(cx, expr, *method_span, &method_call.ident.as_str(), args); clone_on_copy::check(cx, expr, method_call.ident.name, args); @@ -1824,9 +1753,7 @@ impl<'tcx> LateLintPass<'tcx> for Methods { into_iter_on_ref::check(cx, expr, *method_span, method_call.ident.name, args); single_char_pattern::check(cx, expr, method_call.ident.name, args); }, - hir::ExprKind::Binary(op, ref lhs, ref rhs) - if op.node == hir::BinOpKind::Eq || op.node == hir::BinOpKind::Ne => - { + hir::ExprKind::Binary(op, lhs, rhs) if op.node == hir::BinOpKind::Eq || op.node == hir::BinOpKind::Ne => { let mut info = BinaryExprInfo { expr, chain: lhs, @@ -1834,7 +1761,7 @@ impl<'tcx> LateLintPass<'tcx> for Methods { eq: op.node == hir::BinOpKind::Eq, }; lint_binary_expr_with_method_call(cx, &mut info); - } + }, _ => (), } } @@ -1850,10 +1777,9 @@ impl<'tcx> LateLintPass<'tcx> for Methods { let self_ty = cx.tcx.type_of(item.def_id); let implements_trait = matches!(item.kind, hir::ItemKind::Impl(hir::Impl { of_trait: Some(_), .. })); - if_chain! { if let hir::ImplItemKind::Fn(ref sig, id) = impl_item.kind; - if let Some(first_arg) = iter_input_pats(&sig.decl, cx.tcx.hir().body(id)).next(); + if let Some(first_arg) = iter_input_pats(sig.decl, cx.tcx.hir().body(id)).next(); let method_sig = cx.tcx.fn_sig(impl_item.def_id); let method_sig = cx.tcx.erase_late_bound_regions(method_sig); @@ -1873,7 +1799,7 @@ impl<'tcx> LateLintPass<'tcx> for Methods { method_config.output_type.matches(&sig.decl.output) && method_config.self_kind.matches(cx, self_ty, first_arg_ty) && fn_header_equals(method_config.fn_header, sig.header) && - method_config.lifetime_param_cond(&impl_item) + method_config.lifetime_param_cond(impl_item) { span_lint_and_help( cx, @@ -1902,6 +1828,7 @@ impl<'tcx> LateLintPass<'tcx> for Methods { self_ty, first_arg_ty, first_arg.pat.span, + implements_trait, false ); } @@ -1960,11 +1887,10 @@ impl<'tcx> LateLintPass<'tcx> for Methods { if_chain! { if let TraitItemKind::Fn(ref sig, _) = item.kind; if let Some(first_arg_ty) = sig.decl.inputs.iter().next(); - let first_arg_span = first_arg_ty.span; - let first_arg_ty = hir_ty_to_ty(cx.tcx, first_arg_ty); - let self_ty = TraitRef::identity(cx.tcx, item.def_id.to_def_id()).self_ty(); - then { + let first_arg_span = first_arg_ty.span; + let first_arg_ty = hir_ty_to_ty(cx.tcx, first_arg_ty); + let self_ty = TraitRef::identity(cx.tcx, item.def_id.to_def_id()).self_ty(); wrong_self_convention::check( cx, &item.ident.name.as_str(), @@ -1972,6 +1898,7 @@ impl<'tcx> LateLintPass<'tcx> for Methods { self_ty, first_arg_ty, first_arg_span, + false, true ); } @@ -1997,6 +1924,140 @@ impl<'tcx> LateLintPass<'tcx> for Methods { extract_msrv_attr!(LateContext); } + +#[allow(clippy::too_many_lines)] +fn check_methods<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, msrv: Option<&RustcVersion>) { + if let Some((name, [recv, args @ ..], span)) = method_call!(expr) { + match (name, args) { + ("add" | "offset" | "sub" | "wrapping_offset" | "wrapping_add" | "wrapping_sub", [recv, _]) => { + zst_offset::check(cx, expr, recv) + }, + ("and_then", [arg]) => { + let biom_option_linted = bind_instead_of_map::OptionAndThenSome::check(cx, expr, recv, arg); + let biom_result_linted = bind_instead_of_map::ResultAndThenOk::check(cx, expr, recv, arg); + if !biom_option_linted && !biom_result_linted { + unnecessary_lazy_eval::check(cx, expr, recv, arg, "and"); + } + }, + ("as_mut", []) => useless_asref::check(cx, expr, "as_mut", recv), + ("as_ref", []) => useless_asref::check(cx, expr, "as_ref", recv), + ("assume_init", []) => uninit_assumed_init::check(cx, expr, recv), + ("collect", []) => match method_call!(recv) { + Some(("cloned", [recv2], _)) => iter_cloned_collect::check(cx, expr, recv2), + Some(("map", [m_recv, m_arg], _)) => { + map_collect_result_unit::check(cx, expr, m_recv, m_arg, recv); + }, + _ => {}, + }, + ("count", []) => match method_call!(recv) { + Some((name @ ("into_iter" | "iter" | "iter_mut"), [recv2], _)) => { + iter_count::check(cx, expr, recv2, name); + }, + Some(("map", [_, arg], _)) => suspicious_map::check(cx, expr, recv, arg), + _ => {}, + }, + ("expect", [_]) => match method_call!(recv) { + Some(("ok", [recv], _)) => ok_expect::check(cx, expr, recv), + _ => expect_used::check(cx, expr, recv), + }, + ("extend", [arg]) => string_extend_chars::check(cx, expr, recv, arg), + ("filter_map", [arg]) => { + unnecessary_filter_map::check(cx, expr, arg); + filter_map_identity::check(cx, expr, arg, span); + }, + ("flat_map", [flm_arg]) => match method_call!(recv) { + Some(("filter", [_, _], _)) => filter_flat_map::check(cx, expr), + Some(("filter_map", [_, _], _)) => filter_map_flat_map::check(cx, expr), + _ => flat_map_identity::check(cx, expr, flm_arg, span), + }, + ("flatten", []) => { + if let Some(("map", [recv, map_arg], _)) = method_call!(recv) { + map_flatten::check(cx, expr, recv, map_arg); + } + }, + ("fold", [init, acc]) => unnecessary_fold::check(cx, expr, init, acc, span), + ("for_each", [_]) => { + if let Some(("inspect", [_, _], span2)) = method_call!(recv) { + inspect_for_each::check(cx, expr, span2); + } + }, + ("get_or_insert_with", [arg]) => unnecessary_lazy_eval::check(cx, expr, recv, arg, "get_or_insert"), + ("is_file", []) => filetype_is_file::check(cx, expr, recv), + ("is_none", []) => check_is_some_is_none(cx, expr, recv, false), + ("is_some", []) => check_is_some_is_none(cx, expr, recv, true), + ("map", [m_arg]) => { + if let Some((name, [recv2, args @ ..], span2)) = method_call!(recv) { + match (name, args) { + ("as_mut", []) => option_as_ref_deref::check(cx, expr, recv2, m_arg, true, msrv), + ("as_ref", []) => option_as_ref_deref::check(cx, expr, recv2, m_arg, false, msrv), + ("filter", [f_arg]) => { + filter_map::check(cx, expr, recv2, f_arg, span2, recv, m_arg, span, false) + }, + ("filter_map", [_]) => filter_map_map::check(cx, expr), + ("find", [f_arg]) => filter_map::check(cx, expr, recv2, f_arg, span2, recv, m_arg, span, true), + _ => {}, + } + } + }, + ("map_or", [def, map]) => option_map_or_none::check(cx, expr, recv, def, map), + ("next", []) => { + if let Some((name, [recv, args @ ..], _)) = method_call!(recv) { + match (name, args) { + ("filter", [arg]) => filter_next::check(cx, expr, recv, arg), + ("filter_map", [arg]) => filter_map_next::check(cx, expr, recv, arg, msrv), + ("iter", []) => iter_next_slice::check(cx, expr, recv), + ("skip", [arg]) => iter_skip_next::check(cx, expr, recv, arg), + ("skip_while", [_]) => skip_while_next::check(cx, expr), + _ => {}, + } + } + }, + ("nth", [n_arg]) => match method_call!(recv) { + Some(("bytes", [recv2], _)) => bytes_nth::check(cx, expr, recv2, n_arg), + Some(("iter", [recv2], _)) => iter_nth::check(cx, expr, recv2, recv, n_arg, false), + Some(("iter_mut", [recv2], _)) => iter_nth::check(cx, expr, recv2, recv, n_arg, true), + _ => iter_nth_zero::check(cx, expr, recv, n_arg), + }, + ("ok_or_else", [arg]) => unnecessary_lazy_eval::check(cx, expr, recv, arg, "ok_or"), + ("or_else", [arg]) => { + if !bind_instead_of_map::ResultOrElseErrInfo::check(cx, expr, recv, arg) { + unnecessary_lazy_eval::check(cx, expr, recv, arg, "or"); + } + }, + ("step_by", [arg]) => iterator_step_by_zero::check(cx, expr, arg), + ("to_os_string", []) => implicit_clone::check(cx, expr, sym::OsStr), + ("to_owned", []) => implicit_clone::check(cx, expr, sym::ToOwned), + ("to_path_buf", []) => implicit_clone::check(cx, expr, sym::Path), + ("to_vec", []) => implicit_clone::check(cx, expr, sym::slice), + ("unwrap", []) => match method_call!(recv) { + Some(("get", [recv, get_arg], _)) => get_unwrap::check(cx, expr, recv, get_arg, false), + Some(("get_mut", [recv, get_arg], _)) => get_unwrap::check(cx, expr, recv, get_arg, true), + _ => unwrap_used::check(cx, expr, recv), + }, + ("unwrap_or", [u_arg]) => match method_call!(recv) { + Some((arith @ ("checked_add" | "checked_sub" | "checked_mul"), [lhs, rhs], _)) => { + manual_saturating_arithmetic::check(cx, expr, lhs, rhs, u_arg, &arith["checked_".len()..]); + }, + Some(("map", [m_recv, m_arg], span)) => { + option_map_unwrap_or::check(cx, expr, m_recv, m_arg, recv, u_arg, span) + }, + _ => {}, + }, + ("unwrap_or_else", [u_arg]) => match method_call!(recv) { + Some(("map", [recv, map_arg], _)) if map_unwrap_or::check(cx, expr, recv, map_arg, u_arg, msrv) => {}, + _ => unnecessary_lazy_eval::check(cx, expr, recv, u_arg, "unwrap_or"), + }, + _ => {}, + } + } +} + +fn check_is_some_is_none(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, is_some: bool) { + if let Some((name @ ("find" | "position" | "rposition"), [f_recv, arg], span)) = method_call!(recv) { + search_is_some::check(cx, expr, name, is_some, f_recv, arg, recv, span) + } +} + /// Used for `lint_binary_expr_with_method_call`. #[derive(Copy, Clone)] struct BinaryExprInfo<'a> { @@ -2209,10 +2270,10 @@ impl OutType { let is_unit = |ty: &hir::Ty<'_>| matches!(ty.kind, hir::TyKind::Tup(&[])); match (self, ty) { (Self::Unit, &hir::FnRetTy::DefaultReturn(_)) => true, - (Self::Unit, &hir::FnRetTy::Return(ref ty)) if is_unit(ty) => true, - (Self::Bool, &hir::FnRetTy::Return(ref ty)) if is_bool(ty) => true, - (Self::Any, &hir::FnRetTy::Return(ref ty)) if !is_unit(ty) => true, - (Self::Ref, &hir::FnRetTy::Return(ref ty)) => matches!(ty.kind, hir::TyKind::Rptr(_, _)), + (Self::Unit, &hir::FnRetTy::Return(ty)) if is_unit(ty) => true, + (Self::Bool, &hir::FnRetTy::Return(ty)) if is_bool(ty) => true, + (Self::Any, &hir::FnRetTy::Return(ty)) if !is_unit(ty) => true, + (Self::Ref, &hir::FnRetTy::Return(ty)) => matches!(ty.kind, hir::TyKind::Rptr(_, _)), _ => false, } } diff --git a/clippy_lints/src/methods/ok_expect.rs b/clippy_lints/src/methods/ok_expect.rs index e6ce9cac397..d0b1b4b84be 100644 --- a/clippy_lints/src/methods/ok_expect.rs +++ b/clippy_lints/src/methods/ok_expect.rs @@ -9,11 +9,11 @@ use rustc_span::sym; use super::OK_EXPECT; /// lint use of `ok().expect()` for `Result`s -pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, ok_args: &[hir::Expr<'_>]) { +pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>) { if_chain! { // lint if the caller of `ok()` is a `Result` - if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(&ok_args[0]), sym::result_type); - let result_type = cx.typeck_results().expr_ty(&ok_args[0]); + if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::result_type); + let result_type = cx.typeck_results().expr_ty(recv); if let Some(error_type) = get_error_type(cx, result_type); if has_debug_impl(error_type, cx); diff --git a/clippy_lints/src/methods/option_as_ref_deref.rs b/clippy_lints/src/methods/option_as_ref_deref.rs index d11ede080dc..7e9c8fa829d 100644 --- a/clippy_lints/src/methods/option_as_ref_deref.rs +++ b/clippy_lints/src/methods/option_as_ref_deref.rs @@ -18,8 +18,8 @@ const OPTION_AS_REF_DEREF_MSRV: RustcVersion = RustcVersion::new(1, 40, 0); pub(super) fn check<'tcx>( cx: &LateContext<'tcx>, expr: &hir::Expr<'_>, - as_ref_args: &[hir::Expr<'_>], - map_args: &[hir::Expr<'_>], + as_ref_recv: &hir::Expr<'_>, + map_arg: &hir::Expr<'_>, is_mut: bool, msrv: Option<&RustcVersion>, ) { @@ -29,7 +29,7 @@ pub(super) fn check<'tcx>( let same_mutability = |m| (is_mut && m == &hir::Mutability::Mut) || (!is_mut && m == &hir::Mutability::Not); - let option_ty = cx.typeck_results().expr_ty(&as_ref_args[0]); + let option_ty = cx.typeck_results().expr_ty(as_ref_recv); if !is_type_diagnostic_item(cx, option_ty, sym::option_type) { return; } @@ -46,9 +46,9 @@ pub(super) fn check<'tcx>( &paths::VEC_AS_MUT_SLICE, ]; - let is_deref = match map_args[1].kind { + let is_deref = match map_arg.kind { hir::ExprKind::Path(ref expr_qpath) => cx - .qpath_res(expr_qpath, map_args[1].hir_id) + .qpath_res(expr_qpath, map_arg.hir_id) .opt_def_id() .map_or(false, |fun_def_id| { deref_aliases.iter().any(|path| match_def_path(cx, fun_def_id, path)) @@ -77,10 +77,10 @@ pub(super) fn check<'tcx>( } } }, - hir::ExprKind::AddrOf(hir::BorrowKind::Ref, m, ref inner) if same_mutability(m) => { + hir::ExprKind::AddrOf(hir::BorrowKind::Ref, m, inner) if same_mutability(m) => { if_chain! { - if let hir::ExprKind::Unary(hir::UnOp::Deref, ref inner1) = inner.kind; - if let hir::ExprKind::Unary(hir::UnOp::Deref, ref inner2) = inner1.kind; + if let hir::ExprKind::Unary(hir::UnOp::Deref, inner1) = inner.kind; + if let hir::ExprKind::Unary(hir::UnOp::Deref, inner2) = inner1.kind; then { path_to_local_id(inner2, closure_body.params[0].pat.hir_id) } else { @@ -96,12 +96,12 @@ pub(super) fn check<'tcx>( if is_deref { let current_method = if is_mut { - format!(".as_mut().map({})", snippet(cx, map_args[1].span, "..")) + format!(".as_mut().map({})", snippet(cx, map_arg.span, "..")) } else { - format!(".as_ref().map({})", snippet(cx, map_args[1].span, "..")) + format!(".as_ref().map({})", snippet(cx, map_arg.span, "..")) }; let method_hint = if is_mut { "as_deref_mut" } else { "as_deref" }; - let hint = format!("{}.{}()", snippet(cx, as_ref_args[0].span, ".."), method_hint); + let hint = format!("{}.{}()", snippet(cx, as_ref_recv.span, ".."), method_hint); let suggestion = format!("try using {} instead", method_hint); let msg = format!( diff --git a/clippy_lints/src/methods/option_map_or_none.rs b/clippy_lints/src/methods/option_map_or_none.rs index d93db2c22e4..013a6f90ac9 100644 --- a/clippy_lints/src/methods/option_map_or_none.rs +++ b/clippy_lints/src/methods/option_map_or_none.rs @@ -11,9 +11,15 @@ use super::OPTION_MAP_OR_NONE; use super::RESULT_MAP_OR_INTO_OPTION; /// lint use of `_.map_or(None, _)` for `Option`s and `Result`s -pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>, map_or_args: &'tcx [hir::Expr<'_>]) { - let is_option = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(&map_or_args[0]), sym::option_type); - let is_result = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(&map_or_args[0]), sym::result_type); +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx hir::Expr<'_>, + recv: &'tcx hir::Expr<'_>, + def_arg: &'tcx hir::Expr<'_>, + map_arg: &'tcx hir::Expr<'_>, +) { + let is_option = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::option_type); + let is_result = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::result_type); // There are two variants of this `map_or` lint: // (1) using `map_or` as an adapter from `Result<T,E>` to `Option<T>` @@ -25,7 +31,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>, map } let (lint_name, msg, instead, hint) = { - let default_arg_is_none = if let hir::ExprKind::Path(ref qpath) = map_or_args[1].kind { + let default_arg_is_none = if let hir::ExprKind::Path(ref qpath) = def_arg.kind { match_qpath(qpath, &paths::OPTION_NONE) } else { return; @@ -36,15 +42,15 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>, map return; } - let f_arg_is_some = if let hir::ExprKind::Path(ref qpath) = map_or_args[2].kind { + let f_arg_is_some = if let hir::ExprKind::Path(ref qpath) = map_arg.kind { match_qpath(qpath, &paths::OPTION_SOME) } else { false }; if is_option { - let self_snippet = snippet(cx, map_or_args[0].span, ".."); - let func_snippet = snippet(cx, map_or_args[2].span, ".."); + let self_snippet = snippet(cx, recv.span, ".."); + let func_snippet = snippet(cx, map_arg.span, ".."); let msg = "called `map_or(None, ..)` on an `Option` value. This can be done more directly by calling \ `and_then(..)` instead"; ( @@ -56,7 +62,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>, map } else if f_arg_is_some { let msg = "called `map_or(None, Some)` on a `Result` value. This can be done more directly by calling \ `ok()` instead"; - let self_snippet = snippet(cx, map_or_args[0].span, ".."); + let self_snippet = snippet(cx, recv.span, ".."); ( RESULT_MAP_OR_INTO_OPTION, msg, diff --git a/clippy_lints/src/methods/option_map_unwrap_or.rs b/clippy_lints/src/methods/option_map_unwrap_or.rs index e252abc177a..5bca49dec24 100644 --- a/clippy_lints/src/methods/option_map_unwrap_or.rs +++ b/clippy_lints/src/methods/option_map_unwrap_or.rs @@ -18,13 +18,15 @@ use super::MAP_UNWRAP_OR; pub(super) fn check<'tcx>( cx: &LateContext<'tcx>, expr: &rustc_hir::Expr<'_>, - map_args: &'tcx [rustc_hir::Expr<'_>], - unwrap_args: &'tcx [rustc_hir::Expr<'_>], + recv: &rustc_hir::Expr<'_>, + map_arg: &'tcx rustc_hir::Expr<'_>, + unwrap_recv: &rustc_hir::Expr<'_>, + unwrap_arg: &'tcx rustc_hir::Expr<'_>, map_span: Span, ) { // lint if the caller of `map()` is an `Option` - if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(&map_args[0]), sym::option_type) { - if !is_copy(cx, cx.typeck_results().expr_ty(&unwrap_args[1])) { + if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::option_type) { + if !is_copy(cx, cx.typeck_results().expr_ty(unwrap_arg)) { // Do not lint if the `map` argument uses identifiers in the `map` // argument that are also used in the `unwrap_or` argument @@ -32,27 +34,27 @@ pub(super) fn check<'tcx>( cx, identifiers: FxHashSet::default(), }; - unwrap_visitor.visit_expr(&unwrap_args[1]); + unwrap_visitor.visit_expr(unwrap_arg); let mut map_expr_visitor = MapExprVisitor { cx, identifiers: unwrap_visitor.identifiers, found_identifier: false, }; - map_expr_visitor.visit_expr(&map_args[1]); + map_expr_visitor.visit_expr(map_arg); if map_expr_visitor.found_identifier { return; } } - if differing_macro_contexts(unwrap_args[1].span, map_span) { + if differing_macro_contexts(unwrap_arg.span, map_span) { return; } let mut applicability = Applicability::MachineApplicable; // get snippet for unwrap_or() - let unwrap_snippet = snippet_with_applicability(cx, unwrap_args[1].span, "..", &mut applicability); + let unwrap_snippet = snippet_with_applicability(cx, unwrap_arg.span, "..", &mut applicability); // lint message // comparing the snippet from source to raw text ("None") below is safe // because we already have checked the type. @@ -70,14 +72,14 @@ pub(super) fn check<'tcx>( ); span_lint_and_then(cx, MAP_UNWRAP_OR, expr.span, msg, |diag| { - let map_arg_span = map_args[1].span; + let map_arg_span = map_arg.span; let mut suggestion = vec![ ( map_span, String::from(if unwrap_snippet_none { "and_then" } else { "map_or" }), ), - (expr.span.with_lo(unwrap_args[0].span.hi()), String::from("")), + (expr.span.with_lo(unwrap_recv.span.hi()), String::from("")), ]; if !unwrap_snippet_none { diff --git a/clippy_lints/src/methods/or_fun_call.rs b/clippy_lints/src/methods/or_fun_call.rs index 89dedc5f0d8..800172f4cf3 100644 --- a/clippy_lints/src/methods/or_fun_call.rs +++ b/clippy_lints/src/methods/or_fun_call.rs @@ -10,7 +10,7 @@ use rustc_hir::{BlockCheckMode, UnsafeSource}; use rustc_lint::LateContext; use rustc_middle::ty; use rustc_span::source_map::Span; -use rustc_span::symbol::sym; +use rustc_span::symbol::{kw, sym}; use std::borrow::Cow; use super::OR_FUN_CALL; @@ -38,8 +38,8 @@ pub(super) fn check<'tcx>( if !or_has_args; if name == "unwrap_or"; if let hir::ExprKind::Path(ref qpath) = fun.kind; - let path = &*last_path_segment(qpath).ident.as_str(); - if ["default", "new"].contains(&path); + let path = last_path_segment(qpath).ident.name; + if matches!(path, kw::Default | sym::new); let arg_ty = cx.typeck_results().expr_ty(arg); if let Some(default_trait_id) = get_trait_def_id(cx, &paths::DEFAULT_TRAIT); if implements_trait(cx, arg_ty, default_trait_id, &[]); @@ -86,7 +86,7 @@ pub(super) fn check<'tcx>( (&paths::RESULT, true, &["or", "unwrap_or"], "else"), ]; - if let hir::ExprKind::MethodCall(ref path, _, ref args, _) = &arg.kind { + if let hir::ExprKind::MethodCall(path, _, args, _) = &arg.kind { if path.ident.as_str() == "len" { let ty = cx.typeck_results().expr_ty(&args[0]).peel_refs(); @@ -105,7 +105,7 @@ pub(super) fn check<'tcx>( if KNOW_TYPES.iter().any(|k| k.2.contains(&name)); if is_lazyness_candidate(cx, arg); - if !contains_return(&arg); + if !contains_return(arg); let self_ty = cx.typeck_results().expr_ty(self_expr); @@ -158,7 +158,7 @@ pub(super) fn check<'tcx>( if args.len() == 2 { match args[1].kind { - hir::ExprKind::Call(ref fun, ref or_args) => { + hir::ExprKind::Call(fun, or_args) => { let or_has_args = !or_args.is_empty(); if !check_unwrap_or_default(cx, name, fun, &args[0], &args[1], or_has_args, expr.span) { let fun_span = if or_has_args { None } else { Some(fun.span) }; diff --git a/clippy_lints/src/methods/search_is_some.rs b/clippy_lints/src/methods/search_is_some.rs index de7d168295f..ecec6fc3bb7 100644 --- a/clippy_lints/src/methods/search_is_some.rs +++ b/clippy_lints/src/methods/search_is_some.rs @@ -15,35 +15,37 @@ use super::SEARCH_IS_SOME; /// lint searching an Iterator followed by `is_some()` /// or calling `find()` on a string followed by `is_some()` or `is_none()` -#[allow(clippy::too_many_lines)] +#[allow(clippy::too_many_arguments, clippy::too_many_lines)] pub(super) fn check<'tcx>( - cx: &LateContext<'tcx>, + cx: &LateContext<'_>, expr: &'tcx hir::Expr<'_>, search_method: &str, - option_check_method: &str, - search_args: &'tcx [hir::Expr<'_>], - is_some_args: &'tcx [hir::Expr<'_>], + is_some: bool, + search_recv: &hir::Expr<'_>, + search_arg: &'tcx hir::Expr<'_>, + is_some_recv: &hir::Expr<'_>, method_span: Span, ) { + let option_check_method = if is_some { "is_some" } else { "is_none" }; // lint if caller of search is an Iterator - if is_trait_method(cx, &is_some_args[0], sym::Iterator) { + if is_trait_method(cx, is_some_recv, sym::Iterator) { let msg = format!( "called `{}()` after searching an `Iterator` with `{}`", option_check_method, search_method ); - let search_snippet = snippet(cx, search_args[1].span, ".."); + let search_snippet = snippet(cx, search_arg.span, ".."); if search_snippet.lines().count() <= 1 { // suggest `any(|x| ..)` instead of `any(|&x| ..)` for `find(|&x| ..).is_some()` // suggest `any(|..| *..)` instead of `any(|..| **..)` for `find(|..| **..).is_some()` let any_search_snippet = if_chain! { if search_method == "find"; - if let hir::ExprKind::Closure(_, _, body_id, ..) = search_args[1].kind; + if let hir::ExprKind::Closure(_, _, body_id, ..) = search_arg.kind; let closure_body = cx.tcx.hir().body(body_id); if let Some(closure_arg) = closure_body.params.get(0); then { if let hir::PatKind::Ref(..) = closure_arg.pat.kind { Some(search_snippet.replacen('&', "", 1)) - } else if let PatKind::Binding(_, _, ident, _) = strip_pat_refs(&closure_arg.pat).kind { + } else if let PatKind::Binding(_, _, ident, _) = strip_pat_refs(closure_arg.pat).kind { let name = &*ident.name.as_str(); Some(search_snippet.replace(&format!("*{}", name), name)) } else { @@ -54,38 +56,34 @@ pub(super) fn check<'tcx>( } }; // add note if not multi-line - match option_check_method { - "is_some" => { - span_lint_and_sugg( - cx, - SEARCH_IS_SOME, - method_span.with_hi(expr.span.hi()), - &msg, - "use `any()` instead", - format!( - "any({})", - any_search_snippet.as_ref().map_or(&*search_snippet, String::as_str) - ), - Applicability::MachineApplicable, - ); - }, - "is_none" => { - let iter = snippet(cx, search_args[0].span, ".."); - span_lint_and_sugg( - cx, - SEARCH_IS_SOME, - expr.span, - &msg, - "use `!_.any()` instead", - format!( - "!{}.any({})", - iter, - any_search_snippet.as_ref().map_or(&*search_snippet, String::as_str) - ), - Applicability::MachineApplicable, - ); - }, - _ => (), + if is_some { + span_lint_and_sugg( + cx, + SEARCH_IS_SOME, + method_span.with_hi(expr.span.hi()), + &msg, + "use `any()` instead", + format!( + "any({})", + any_search_snippet.as_ref().map_or(&*search_snippet, String::as_str) + ), + Applicability::MachineApplicable, + ); + } else { + let iter = snippet(cx, search_recv.span, ".."); + span_lint_and_sugg( + cx, + SEARCH_IS_SOME, + expr.span, + &msg, + "use `!_.any()` instead", + format!( + "!{}.any({})", + iter, + any_search_snippet.as_ref().map_or(&*search_snippet, String::as_str) + ), + Applicability::MachineApplicable, + ); } } else { let hint = format!( @@ -110,14 +108,14 @@ pub(super) fn check<'tcx>( } }; if_chain! { - if is_string_or_str_slice(&search_args[0]); - if is_string_or_str_slice(&search_args[1]); + if is_string_or_str_slice(search_recv); + if is_string_or_str_slice(search_arg); then { let msg = format!("called `{}()` after calling `find()` on a string", option_check_method); match option_check_method { "is_some" => { let mut applicability = Applicability::MachineApplicable; - let find_arg = snippet_with_applicability(cx, search_args[1].span, "..", &mut applicability); + let find_arg = snippet_with_applicability(cx, search_arg.span, "..", &mut applicability); span_lint_and_sugg( cx, SEARCH_IS_SOME, @@ -129,9 +127,9 @@ pub(super) fn check<'tcx>( ); }, "is_none" => { - let string = snippet(cx, search_args[0].span, ".."); + let string = snippet(cx, search_recv.span, ".."); let mut applicability = Applicability::MachineApplicable; - let find_arg = snippet_with_applicability(cx, search_args[1].span, "..", &mut applicability); + let find_arg = snippet_with_applicability(cx, search_arg.span, "..", &mut applicability); span_lint_and_sugg( cx, SEARCH_IS_SOME, diff --git a/clippy_lints/src/methods/skip_while_next.rs b/clippy_lints/src/methods/skip_while_next.rs index 3db83785b59..9f0b6c34ea2 100644 --- a/clippy_lints/src/methods/skip_while_next.rs +++ b/clippy_lints/src/methods/skip_while_next.rs @@ -7,7 +7,7 @@ use rustc_span::sym; use super::SKIP_WHILE_NEXT; /// lint use of `skip_while().next()` for `Iterators` -pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>, _skip_while_args: &'tcx [hir::Expr<'_>]) { +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) { // lint if caller of `.skip_while().next()` is an Iterator if is_trait_method(cx, expr, sym::Iterator) { span_lint_and_help( diff --git a/clippy_lints/src/methods/string_extend_chars.rs b/clippy_lints/src/methods/string_extend_chars.rs index 5c688ac5621..6e7890a3080 100644 --- a/clippy_lints/src/methods/string_extend_chars.rs +++ b/clippy_lints/src/methods/string_extend_chars.rs @@ -10,12 +10,11 @@ use rustc_span::symbol::sym; use super::STRING_EXTEND_CHARS; -pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, args: &[hir::Expr<'_>]) { - let obj_ty = cx.typeck_results().expr_ty(&args[0]).peel_refs(); +pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>, arg: &hir::Expr<'_>) { + let obj_ty = cx.typeck_results().expr_ty(recv).peel_refs(); if !is_type_diagnostic_item(cx, obj_ty, sym::string_type) { return; } - let arg = &args[1]; if let Some(arglists) = method_chain_args(arg, &["chars"]) { let target = &arglists[0][0]; let self_ty = cx.typeck_results().expr_ty(target).peel_refs(); @@ -36,7 +35,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, args: &[hir::Exp "try this", format!( "{}.push_str({}{})", - snippet_with_applicability(cx, args[0].span, "..", &mut applicability), + snippet_with_applicability(cx, recv.span, "..", &mut applicability), ref_str, snippet_with_applicability(cx, target.span, "..", &mut applicability) ), diff --git a/clippy_lints/src/methods/suspicious_map.rs b/clippy_lints/src/methods/suspicious_map.rs index 7015bd54c35..0fd0668c734 100644 --- a/clippy_lints/src/methods/suspicious_map.rs +++ b/clippy_lints/src/methods/suspicious_map.rs @@ -8,15 +8,8 @@ use rustc_span::sym; use super::SUSPICIOUS_MAP; -pub fn check<'tcx>( - cx: &LateContext<'tcx>, - expr: &hir::Expr<'_>, - map_args: &[hir::Expr<'_>], - count_args: &[hir::Expr<'_>], -) { +pub fn check<'tcx>(cx: &LateContext<'tcx>, expr: &hir::Expr<'_>, count_recv: &hir::Expr<'_>, map_arg: &hir::Expr<'_>) { if_chain! { - if let [count_recv] = count_args; - if let [_, map_arg] = map_args; if is_trait_method(cx, count_recv, sym::Iterator); let closure = expr_or_init(cx, map_arg); if let Some(body_id) = cx.tcx.hir().maybe_body_owned_by(closure.hir_id); diff --git a/clippy_lints/src/methods/uninit_assumed_init.rs b/clippy_lints/src/methods/uninit_assumed_init.rs index f2f6ef4be6c..0ae65c0c01d 100644 --- a/clippy_lints/src/methods/uninit_assumed_init.rs +++ b/clippy_lints/src/methods/uninit_assumed_init.rs @@ -8,18 +8,18 @@ use rustc_middle::ty::{self, Ty}; use super::UNINIT_ASSUMED_INIT; /// lint for `MaybeUninit::uninit().assume_init()` (we already have the latter) -pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, outer: &hir::Expr<'_>) { +pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>) { if_chain! { - if let hir::ExprKind::Call(ref callee, ref args) = expr.kind; + if let hir::ExprKind::Call(callee, args) = recv.kind; if args.is_empty(); if let hir::ExprKind::Path(ref path) = callee.kind; if match_qpath(path, &paths::MEM_MAYBEUNINIT_UNINIT); - if !is_maybe_uninit_ty_valid(cx, cx.typeck_results().expr_ty_adjusted(outer)); + if !is_maybe_uninit_ty_valid(cx, cx.typeck_results().expr_ty_adjusted(expr)); then { span_lint( cx, UNINIT_ASSUMED_INIT, - outer.span, + expr.span, "this call for this type may be undefined behavior" ); } @@ -28,9 +28,9 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, outer: &hir::Exp fn is_maybe_uninit_ty_valid(cx: &LateContext<'_>, ty: Ty<'_>) -> bool { match ty.kind() { - ty::Array(ref component, _) => is_maybe_uninit_ty_valid(cx, component), - ty::Tuple(ref types) => types.types().all(|ty| is_maybe_uninit_ty_valid(cx, ty)), - ty::Adt(ref adt, _) => match_def_path(cx, adt.did, &paths::MEM_MAYBEUNINIT), + ty::Array(component, _) => is_maybe_uninit_ty_valid(cx, component), + ty::Tuple(types) => types.types().all(|ty| is_maybe_uninit_ty_valid(cx, ty)), + ty::Adt(adt, _) => match_def_path(cx, adt.did, &paths::MEM_MAYBEUNINIT), _ => false, } } diff --git a/clippy_lints/src/methods/unnecessary_filter_map.rs b/clippy_lints/src/methods/unnecessary_filter_map.rs index 48d905ab833..0f28bfdf09e 100644 --- a/clippy_lints/src/methods/unnecessary_filter_map.rs +++ b/clippy_lints/src/methods/unnecessary_filter_map.rs @@ -1,7 +1,6 @@ use clippy_utils::diagnostics::span_lint; use clippy_utils::usage::mutated_variables; use clippy_utils::{is_trait_method, match_qpath, path_to_local_id, paths}; -use if_chain::if_chain; use rustc_hir as hir; use rustc_hir::intravisit::{walk_expr, NestedVisitorMap, Visitor}; use rustc_lint::LateContext; @@ -10,20 +9,20 @@ use rustc_span::sym; use super::UNNECESSARY_FILTER_MAP; -pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, args: &[hir::Expr<'_>]) { +pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, arg: &hir::Expr<'_>) { if !is_trait_method(cx, expr, sym::Iterator) { return; } - if let hir::ExprKind::Closure(_, _, body_id, ..) = args[1].kind { + if let hir::ExprKind::Closure(_, _, body_id, ..) = arg.kind { let body = cx.tcx.hir().body(body_id); let arg_id = body.params[0].pat.hir_id; let mutates_arg = mutated_variables(&body.value, cx).map_or(true, |used_mutably| used_mutably.contains(&arg_id)); - let (mut found_mapping, mut found_filtering) = check_expression(&cx, arg_id, &body.value); + let (mut found_mapping, mut found_filtering) = check_expression(cx, arg_id, &body.value); - let mut return_visitor = ReturnVisitor::new(&cx, arg_id); + let mut return_visitor = ReturnVisitor::new(cx, arg_id); return_visitor.visit_expr(&body.value); found_mapping |= return_visitor.found_mapping; found_filtering |= return_visitor.found_filtering; @@ -53,38 +52,35 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, args: &[hir::Exp // returns (found_mapping, found_filtering) fn check_expression<'tcx>(cx: &LateContext<'tcx>, arg_id: hir::HirId, expr: &'tcx hir::Expr<'_>) -> (bool, bool) { match &expr.kind { - hir::ExprKind::Call(ref func, ref args) => { - if_chain! { - if let hir::ExprKind::Path(ref path) = func.kind; - then { - if match_qpath(path, &paths::OPTION_SOME) { - if path_to_local_id(&args[0], arg_id) { - return (false, false) - } - return (true, false); + hir::ExprKind::Call(func, args) => { + if let hir::ExprKind::Path(ref path) = func.kind { + if match_qpath(path, &paths::OPTION_SOME) { + if path_to_local_id(&args[0], arg_id) { + return (false, false); } - // We don't know. It might do anything. - return (true, true); + return (true, false); } + // We don't know. It might do anything. + return (true, true); } (true, true) }, - hir::ExprKind::Block(ref block, _) => block + hir::ExprKind::Block(block, _) => block .expr .as_ref() - .map_or((false, false), |expr| check_expression(cx, arg_id, &expr)), + .map_or((false, false), |expr| check_expression(cx, arg_id, expr)), hir::ExprKind::Match(_, arms, _) => { let mut found_mapping = false; let mut found_filtering = false; for arm in *arms { - let (m, f) = check_expression(cx, arg_id, &arm.body); + let (m, f) = check_expression(cx, arg_id, arm.body); found_mapping |= m; found_filtering |= f; } (found_mapping, found_filtering) }, // There must be an else_arm or there will be a type error - hir::ExprKind::If(_, ref if_arm, Some(ref else_arm)) => { + hir::ExprKind::If(_, if_arm, Some(else_arm)) => { let if_check = check_expression(cx, arg_id, if_arm); let else_check = check_expression(cx, arg_id, else_arm); (if_check.0 | else_check.0, if_check.1 | else_check.1) diff --git a/clippy_lints/src/methods/unnecessary_fold.rs b/clippy_lints/src/methods/unnecessary_fold.rs index 1268fd4bda9..75517c48a21 100644 --- a/clippy_lints/src/methods/unnecessary_fold.rs +++ b/clippy_lints/src/methods/unnecessary_fold.rs @@ -11,11 +11,17 @@ use rustc_span::{source_map::Span, sym}; use super::UNNECESSARY_FOLD; -pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, fold_args: &[hir::Expr<'_>], fold_span: Span) { +pub(super) fn check( + cx: &LateContext<'_>, + expr: &hir::Expr<'_>, + init: &hir::Expr<'_>, + acc: &hir::Expr<'_>, + fold_span: Span, +) { fn check_fold_with_op( cx: &LateContext<'_>, expr: &hir::Expr<'_>, - fold_args: &[hir::Expr<'_>], + acc: &hir::Expr<'_>, fold_span: Span, op: hir::BinOpKind, replacement_method_name: &str, @@ -23,18 +29,18 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, fold_args: &[hir ) { if_chain! { // Extract the body of the closure passed to fold - if let hir::ExprKind::Closure(_, _, body_id, _, _) = fold_args[2].kind; + if let hir::ExprKind::Closure(_, _, body_id, _, _) = acc.kind; let closure_body = cx.tcx.hir().body(body_id); let closure_expr = remove_blocks(&closure_body.value); // Check if the closure body is of the form `acc <op> some_expr(x)` - if let hir::ExprKind::Binary(ref bin_op, ref left_expr, ref right_expr) = closure_expr.kind; + if let hir::ExprKind::Binary(ref bin_op, left_expr, right_expr) = closure_expr.kind; if bin_op.node == op; // Extract the names of the two arguments to the closure if let [param_a, param_b] = closure_body.params; - if let PatKind::Binding(_, first_arg_id, ..) = strip_pat_refs(¶m_a.pat).kind; - if let PatKind::Binding(_, second_arg_id, second_arg_ident, _) = strip_pat_refs(¶m_b.pat).kind; + if let PatKind::Binding(_, first_arg_id, ..) = strip_pat_refs(param_a.pat).kind; + if let PatKind::Binding(_, second_arg_id, second_arg_ident, _) = strip_pat_refs(param_b.pat).kind; if path_to_local_id(left_expr, first_arg_id); if replacement_has_args || path_to_local_id(right_expr, second_arg_id); @@ -74,25 +80,14 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, fold_args: &[hir return; } - assert!( - fold_args.len() == 3, - "Expected fold_args to have three entries - the receiver, the initial value and the closure" - ); - // Check if the first argument to .fold is a suitable literal - if let hir::ExprKind::Lit(ref lit) = fold_args[1].kind { + if let hir::ExprKind::Lit(ref lit) = init.kind { match lit.node { - ast::LitKind::Bool(false) => { - check_fold_with_op(cx, expr, fold_args, fold_span, hir::BinOpKind::Or, "any", true) - }, - ast::LitKind::Bool(true) => { - check_fold_with_op(cx, expr, fold_args, fold_span, hir::BinOpKind::And, "all", true) - }, - ast::LitKind::Int(0, _) => { - check_fold_with_op(cx, expr, fold_args, fold_span, hir::BinOpKind::Add, "sum", false) - }, + ast::LitKind::Bool(false) => check_fold_with_op(cx, expr, acc, fold_span, hir::BinOpKind::Or, "any", true), + ast::LitKind::Bool(true) => check_fold_with_op(cx, expr, acc, fold_span, hir::BinOpKind::And, "all", true), + ast::LitKind::Int(0, _) => check_fold_with_op(cx, expr, acc, fold_span, hir::BinOpKind::Add, "sum", false), ast::LitKind::Int(1, _) => { - check_fold_with_op(cx, expr, fold_args, fold_span, hir::BinOpKind::Mul, "product", false) + check_fold_with_op(cx, expr, acc, fold_span, hir::BinOpKind::Mul, "product", false) }, _ => (), } diff --git a/clippy_lints/src/methods/unnecessary_lazy_eval.rs b/clippy_lints/src/methods/unnecessary_lazy_eval.rs index a86185bf0a6..b7380883a5e 100644 --- a/clippy_lints/src/methods/unnecessary_lazy_eval.rs +++ b/clippy_lints/src/methods/unnecessary_lazy_eval.rs @@ -14,14 +14,15 @@ use super::UNNECESSARY_LAZY_EVALUATIONS; pub(super) fn check<'tcx>( cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>, - args: &'tcx [hir::Expr<'_>], + recv: &'tcx hir::Expr<'_>, + arg: &'tcx hir::Expr<'_>, simplify_using: &str, ) { - let is_option = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(&args[0]), sym::option_type); - let is_result = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(&args[0]), sym::result_type); + let is_option = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::option_type); + let is_result = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::result_type); if is_option || is_result { - if let hir::ExprKind::Closure(_, _, eid, _, _) = args[1].kind { + if let hir::ExprKind::Closure(_, _, eid, _, _) = arg.kind { let body = cx.tcx.hir().body(eid); let body_expr = &body.value; @@ -55,7 +56,7 @@ pub(super) fn check<'tcx>( &format!("use `{}` instead", simplify_using), format!( "{0}.{1}({2})", - snippet(cx, args[0].span, ".."), + snippet(cx, recv.span, ".."), simplify_using, snippet(cx, body_expr.span, ".."), ), diff --git a/clippy_lints/src/methods/unwrap_used.rs b/clippy_lints/src/methods/unwrap_used.rs index 2f5806115bd..7fd1948594d 100644 --- a/clippy_lints/src/methods/unwrap_used.rs +++ b/clippy_lints/src/methods/unwrap_used.rs @@ -7,8 +7,8 @@ use rustc_span::sym; use super::UNWRAP_USED; /// lint use of `unwrap()` for `Option`s and `Result`s -pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, unwrap_args: &[hir::Expr<'_>]) { - let obj_ty = cx.typeck_results().expr_ty(&unwrap_args[0]).peel_refs(); +pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>) { + let obj_ty = cx.typeck_results().expr_ty(recv).peel_refs(); let mess = if is_type_diagnostic_item(cx, obj_ty, sym::option_type) { Some((UNWRAP_USED, "an Option", "None")) diff --git a/clippy_lints/src/methods/useless_asref.rs b/clippy_lints/src/methods/useless_asref.rs index b5505af0f7e..e0b1de68b37 100644 --- a/clippy_lints/src/methods/useless_asref.rs +++ b/clippy_lints/src/methods/useless_asref.rs @@ -10,12 +10,11 @@ use rustc_lint::LateContext; use super::USELESS_ASREF; /// Checks for the `USELESS_ASREF` lint. -pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, call_name: &str, as_ref_args: &[hir::Expr<'_>]) { +pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, call_name: &str, recvr: &hir::Expr<'_>) { // when we get here, we've already checked that the call name is "as_ref" or "as_mut" // check if the call is to the actual `AsRef` or `AsMut` trait if match_trait_method(cx, expr, &paths::ASREF_TRAIT) || match_trait_method(cx, expr, &paths::ASMUT_TRAIT) { // check if the type after `as_ref` or `as_mut` is the same as before - let recvr = &as_ref_args[0]; let rcv_ty = cx.typeck_results().expr_ty(recvr); let res_ty = cx.typeck_results().expr_ty(expr); let (base_res_ty, res_depth) = walk_ptrs_ty_depth(res_ty); diff --git a/clippy_lints/src/methods/utils.rs b/clippy_lints/src/methods/utils.rs index ac6b55396da..f6bf37e08b9 100644 --- a/clippy_lints/src/methods/utils.rs +++ b/clippy_lints/src/methods/utils.rs @@ -26,7 +26,7 @@ pub(super) fn derefs_to_slice<'tcx>( } } - if let hir::ExprKind::MethodCall(ref path, _, ref args, _) = expr.kind { + if let hir::ExprKind::MethodCall(path, _, args, _) = expr.kind { if path.ident.name == sym::iter && may_slice(cx, cx.typeck_results().expr_ty(&args[0])) { Some(&args[0]) } else { diff --git a/clippy_lints/src/methods/wrong_self_convention.rs b/clippy_lints/src/methods/wrong_self_convention.rs index 59e683aa9a7..1e0de249a91 100644 --- a/clippy_lints/src/methods/wrong_self_convention.rs +++ b/clippy_lints/src/methods/wrong_self_convention.rs @@ -21,8 +21,10 @@ const CONVENTIONS: [(&[Convention], &[SelfKind]); 9] = [ // Conversion using `to_` can use borrowed (non-Copy types) or owned (Copy types). // Source: https://rust-lang.github.io/api-guidelines/naming.html#ad-hoc-conversions-follow-as_-to_-into_-conventions-c-conv - (&[Convention::StartsWith("to_"), Convention::NotEndsWith("_mut"), Convention::IsSelfTypeCopy(false), Convention::ImplementsTrait(false)], &[SelfKind::Ref]), - (&[Convention::StartsWith("to_"), Convention::NotEndsWith("_mut"), Convention::IsSelfTypeCopy(true), Convention::ImplementsTrait(false)], &[SelfKind::Value]), + (&[Convention::StartsWith("to_"), Convention::NotEndsWith("_mut"), Convention::IsSelfTypeCopy(false), + Convention::IsTraitItem(false)], &[SelfKind::Ref]), + (&[Convention::StartsWith("to_"), Convention::NotEndsWith("_mut"), Convention::IsSelfTypeCopy(true), + Convention::IsTraitItem(false), Convention::ImplementsTrait(false)], &[SelfKind::Value]), ]; enum Convention { @@ -32,18 +34,27 @@ enum Convention { NotEndsWith(&'static str), IsSelfTypeCopy(bool), ImplementsTrait(bool), + IsTraitItem(bool), } impl Convention { #[must_use] - fn check<'tcx>(&self, cx: &LateContext<'tcx>, self_ty: &'tcx TyS<'tcx>, other: &str, is_trait_def: bool) -> bool { + fn check<'tcx>( + &self, + cx: &LateContext<'tcx>, + self_ty: &'tcx TyS<'tcx>, + other: &str, + implements_trait: bool, + is_trait_item: bool, + ) -> bool { match *self { Self::Eq(this) => this == other, Self::StartsWith(this) => other.starts_with(this) && this != other, Self::EndsWith(this) => other.ends_with(this) && this != other, - Self::NotEndsWith(this) => !Self::EndsWith(this).check(cx, self_ty, other, is_trait_def), + Self::NotEndsWith(this) => !Self::EndsWith(this).check(cx, self_ty, other, implements_trait, is_trait_item), Self::IsSelfTypeCopy(is_true) => is_true == is_copy(cx, self_ty), - Self::ImplementsTrait(is_true) => is_true == is_trait_def, + Self::ImplementsTrait(is_true) => is_true == implements_trait, + Self::IsTraitItem(is_true) => is_true == is_trait_item, } } } @@ -60,12 +71,17 @@ impl fmt::Display for Convention { }, Self::ImplementsTrait(is_true) => { let (negation, s_suffix) = if is_true { ("", "s") } else { (" does not", "") }; - format!("Method{} implement{} a trait", negation, s_suffix).fmt(f) + format!("method{} implement{} a trait", negation, s_suffix).fmt(f) + }, + Self::IsTraitItem(is_true) => { + let suffix = if is_true { " is" } else { " is not" }; + format!("method{} a trait item", suffix).fmt(f) }, } } } +#[allow(clippy::too_many_arguments)] pub(super) fn check<'tcx>( cx: &LateContext<'tcx>, item_name: &str, @@ -73,6 +89,7 @@ pub(super) fn check<'tcx>( self_ty: &'tcx TyS<'tcx>, first_arg_ty: &'tcx TyS<'tcx>, first_arg_span: Span, + implements_trait: bool, is_trait_item: bool, ) { let lint = if is_pub { @@ -83,7 +100,7 @@ pub(super) fn check<'tcx>( if let Some((conventions, self_kinds)) = &CONVENTIONS.iter().find(|(convs, _)| { convs .iter() - .all(|conv| conv.check(cx, self_ty, item_name, is_trait_item)) + .all(|conv| conv.check(cx, self_ty, item_name, implements_trait, is_trait_item)) }) { if !self_kinds.iter().any(|k| k.matches(cx, self_ty, first_arg_ty)) { let suggestion = { @@ -99,6 +116,7 @@ pub(super) fn check<'tcx>( .filter_map(|conv| { if (cut_ends_with_conv && matches!(conv, Convention::NotEndsWith(_))) || matches!(conv, Convention::ImplementsTrait(_)) + || matches!(conv, Convention::IsTraitItem(_)) { None } else { diff --git a/clippy_lints/src/methods/zst_offset.rs b/clippy_lints/src/methods/zst_offset.rs index 9f6a7c4db17..866cf616679 100644 --- a/clippy_lints/src/methods/zst_offset.rs +++ b/clippy_lints/src/methods/zst_offset.rs @@ -6,10 +6,9 @@ use rustc_middle::ty; use super::ZST_OFFSET; -pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, args: &[hir::Expr<'_>]) { +pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>) { if_chain! { - if args.len() == 2; - if let ty::RawPtr(ty::TypeAndMut { ref ty, .. }) = cx.typeck_results().expr_ty(&args[0]).kind(); + if let ty::RawPtr(ty::TypeAndMut { ty, .. }) = cx.typeck_results().expr_ty(recv).kind(); if let Ok(layout) = cx.tcx.layout_of(cx.param_env.and(ty)); if layout.is_zst(); then { |
