diff options
| author | flip1995 <philipp.krones@embecosm.com> | 2021-12-17 13:40:22 +0100 |
|---|---|---|
| committer | flip1995 <philipp.krones@embecosm.com> | 2021-12-17 13:40:22 +0100 |
| commit | ece0946d7f60bdee8b9544753a3f1a3acddcb1f7 (patch) | |
| tree | 78cfdf1e08b149bae2122b496dd4cfd6fb55c4d4 /clippy_lints/src/methods | |
| parent | b2f8a27ff2662b440d0dd65cdb609d303b2fa280 (diff) | |
Merge commit '23d11428de3e973b34a5090a78d62887f821c90e' into clippyup
Diffstat (limited to 'clippy_lints/src/methods')
| -rw-r--r-- | clippy_lints/src/methods/bind_instead_of_map.rs | 4 | ||||
| -rw-r--r-- | clippy_lints/src/methods/filter_map.rs | 4 | ||||
| -rw-r--r-- | clippy_lints/src/methods/implicit_clone.rs | 29 | ||||
| -rw-r--r-- | clippy_lints/src/methods/mod.rs | 32 | ||||
| -rw-r--r-- | clippy_lints/src/methods/option_as_ref_deref.rs | 4 | ||||
| -rw-r--r-- | clippy_lints/src/methods/unnecessary_fold.rs | 4 | ||||
| -rw-r--r-- | clippy_lints/src/methods/unnecessary_iter_cloned.rs | 177 | ||||
| -rw-r--r-- | clippy_lints/src/methods/unnecessary_to_owned.rs | 397 |
8 files changed, 633 insertions, 18 deletions
diff --git a/clippy_lints/src/methods/bind_instead_of_map.rs b/clippy_lints/src/methods/bind_instead_of_map.rs index b7690cf9222..150bafc0f5d 100644 --- a/clippy_lints/src/methods/bind_instead_of_map.rs +++ b/clippy_lints/src/methods/bind_instead_of_map.rs @@ -1,7 +1,7 @@ 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::{remove_blocks, visitors::find_all_ret_expressions}; +use clippy_utils::{peel_blocks, visitors::find_all_ret_expressions}; use if_chain::if_chain; use rustc_errors::Applicability; use rustc_hir as hir; @@ -152,7 +152,7 @@ pub(crate) trait BindInsteadOfMap { match arg.kind { hir::ExprKind::Closure(_, _, body_id, closure_args_span, _) => { let closure_body = cx.tcx.hir().body(body_id); - let closure_expr = remove_blocks(&closure_body.value); + let closure_expr = peel_blocks(&closure_body.value); if Self::lint_closure_autofixable(cx, expr, recv, closure_expr, closure_args_span) { true diff --git a/clippy_lints/src/methods/filter_map.rs b/clippy_lints/src/methods/filter_map.rs index c96c817bb8b..6d8733c08b4 100644 --- a/clippy_lints/src/methods/filter_map.rs +++ b/clippy_lints/src/methods/filter_map.rs @@ -1,7 +1,7 @@ use clippy_utils::diagnostics::span_lint_and_sugg; 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 clippy_utils::{is_trait_method, path_to_local_id, peel_blocks, SpanlessEq}; use if_chain::if_chain; use rustc_errors::Applicability; use rustc_hir as hir; @@ -25,7 +25,7 @@ fn is_method<'tcx>(cx: &LateContext<'tcx>, expr: &hir::Expr<'_>, method_name: Sy }, hir::ExprKind::Closure(_, _, c, _, _) => { let body = cx.tcx.hir().body(*c); - let closure_expr = remove_blocks(&body.value); + let closure_expr = peel_blocks(&body.value); let arg_id = body.params[0].pat.hir_id; match closure_expr.kind { hir::ExprKind::MethodCall(hir::PathSegment { ident, .. }, _, args, _) => { diff --git a/clippy_lints/src/methods/implicit_clone.rs b/clippy_lints/src/methods/implicit_clone.rs index 81c42de145f..90492ffda3c 100644 --- a/clippy_lints/src/methods/implicit_clone.rs +++ b/clippy_lints/src/methods/implicit_clone.rs @@ -12,15 +12,7 @@ use super::IMPLICIT_CLONE; pub fn check(cx: &LateContext<'_>, method_name: &str, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>, span: Span) { if_chain! { if let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id); - if match method_name { - "to_os_string" => is_diag_item_method(cx, method_def_id, sym::OsStr), - "to_owned" => is_diag_trait_item(cx, method_def_id, sym::ToOwned), - "to_path_buf" => is_diag_item_method(cx, method_def_id, sym::Path), - "to_vec" => cx.tcx.impl_of_method(method_def_id) - .map(|impl_did| Some(impl_did) == cx.tcx.lang_items().slice_alloc_impl()) - == Some(true), - _ => false, - }; + if is_clone_like(cx, method_name, method_def_id); let return_type = cx.typeck_results().expr_ty(expr); let input_type = cx.typeck_results().expr_ty(recv).peel_refs(); if let Some(ty_name) = input_type.ty_adt_def().map(|adt_def| cx.tcx.item_name(adt_def.did)); @@ -38,3 +30,22 @@ pub fn check(cx: &LateContext<'_>, method_name: &str, expr: &hir::Expr<'_>, recv } } } + +/// Returns true if the named method can be used to clone the receiver. +/// Note that `to_string` is not flagged by `implicit_clone`. So other lints that call +/// `is_clone_like` and that do flag `to_string` must handle it separately. See, e.g., +/// `is_to_owned_like` in `unnecessary_to_owned.rs`. +pub fn is_clone_like(cx: &LateContext<'_>, method_name: &str, method_def_id: hir::def_id::DefId) -> bool { + match method_name { + "to_os_string" => is_diag_item_method(cx, method_def_id, sym::OsStr), + "to_owned" => is_diag_trait_item(cx, method_def_id, sym::ToOwned), + "to_path_buf" => is_diag_item_method(cx, method_def_id, sym::Path), + "to_vec" => { + cx.tcx + .impl_of_method(method_def_id) + .map(|impl_did| Some(impl_did) == cx.tcx.lang_items().slice_alloc_impl()) + == Some(true) + }, + _ => false, + } +} diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index 58ec2213535..3c43671dd34 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -56,7 +56,9 @@ mod suspicious_splitn; mod uninit_assumed_init; mod unnecessary_filter_map; mod unnecessary_fold; +mod unnecessary_iter_cloned; mod unnecessary_lazy_eval; +mod unnecessary_to_owned; mod unwrap_or_else_default; mod unwrap_used; mod useless_asref; @@ -1885,6 +1887,32 @@ declare_clippy_lint! { "usages of `str::splitn` that can be replaced with `str::split`" } +declare_clippy_lint! { + /// ### What it does + /// Checks for unnecessary calls to [`ToOwned::to_owned`](https://doc.rust-lang.org/std/borrow/trait.ToOwned.html#tymethod.to_owned) + /// and other `to_owned`-like functions. + /// + /// ### Why is this bad? + /// The unnecessary calls result in useless allocations. + /// + /// ### Example + /// ```rust + /// let path = std::path::Path::new("x"); + /// foo(&path.to_string_lossy().to_string()); + /// fn foo(s: &str) {} + /// ``` + /// Use instead: + /// ```rust + /// let path = std::path::Path::new("x"); + /// foo(&path.to_string_lossy()); + /// fn foo(s: &str) {} + /// ``` + #[clippy::version = "1.58.0"] + pub UNNECESSARY_TO_OWNED, + perf, + "unnecessary calls to `to_owned`-like functions" +} + pub struct Methods { avoid_breaking_exported_api: bool, msrv: Option<RustcVersion>, @@ -1964,7 +1992,8 @@ impl_lint_pass!(Methods => [ MANUAL_STR_REPEAT, EXTEND_WITH_DRAIN, MANUAL_SPLIT_ONCE, - NEEDLESS_SPLITN + NEEDLESS_SPLITN, + UNNECESSARY_TO_OWNED, ]); /// Extracts a method call name, args, and `Span` of the method name. @@ -2007,6 +2036,7 @@ impl<'tcx> LateLintPass<'tcx> for Methods { single_char_add_str::check(cx, expr, args); 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); + unnecessary_to_owned::check(cx, expr, method_call.ident.name, args); }, hir::ExprKind::Binary(op, lhs, rhs) if op.node == hir::BinOpKind::Eq || op.node == hir::BinOpKind::Ne => { let mut info = BinaryExprInfo { diff --git a/clippy_lints/src/methods/option_as_ref_deref.rs b/clippy_lints/src/methods/option_as_ref_deref.rs index d3f40d26208..fa74a8f3dc3 100644 --- a/clippy_lints/src/methods/option_as_ref_deref.rs +++ b/clippy_lints/src/methods/option_as_ref_deref.rs @@ -1,7 +1,7 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::snippet; use clippy_utils::ty::is_type_diagnostic_item; -use clippy_utils::{match_def_path, meets_msrv, msrvs, path_to_local_id, paths, remove_blocks}; +use clippy_utils::{match_def_path, meets_msrv, msrvs, path_to_local_id, paths, peel_blocks}; use if_chain::if_chain; use rustc_errors::Applicability; use rustc_hir as hir; @@ -53,7 +53,7 @@ pub(super) fn check<'tcx>( }), hir::ExprKind::Closure(_, _, body_id, _, _) => { let closure_body = cx.tcx.hir().body(body_id); - let closure_expr = remove_blocks(&closure_body.value); + let closure_expr = peel_blocks(&closure_body.value); match &closure_expr.kind { hir::ExprKind::MethodCall(_, _, args, _) => { diff --git a/clippy_lints/src/methods/unnecessary_fold.rs b/clippy_lints/src/methods/unnecessary_fold.rs index 4c4034437da..47a81199608 100644 --- a/clippy_lints/src/methods/unnecessary_fold.rs +++ b/clippy_lints/src/methods/unnecessary_fold.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::snippet_with_applicability; -use clippy_utils::{is_trait_method, path_to_local_id, remove_blocks, strip_pat_refs}; +use clippy_utils::{is_trait_method, path_to_local_id, peel_blocks, strip_pat_refs}; use if_chain::if_chain; use rustc_ast::ast; use rustc_errors::Applicability; @@ -31,7 +31,7 @@ pub(super) fn check( // Extract the body of the closure passed to fold 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); + let closure_expr = peel_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, left_expr, right_expr) = closure_expr.kind; diff --git a/clippy_lints/src/methods/unnecessary_iter_cloned.rs b/clippy_lints/src/methods/unnecessary_iter_cloned.rs new file mode 100644 index 00000000000..8300df03e99 --- /dev/null +++ b/clippy_lints/src/methods/unnecessary_iter_cloned.rs @@ -0,0 +1,177 @@ +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::higher::ForLoop; +use clippy_utils::source::snippet_opt; +use clippy_utils::ty::{get_associated_type, get_iterator_item_ty, implements_trait}; +use clippy_utils::{fn_def_id, get_parent_expr, path_to_local_id, usage}; +use rustc_errors::Applicability; +use rustc_hir::intravisit::{walk_expr, NestedVisitorMap, Visitor}; +use rustc_hir::{def_id::DefId, BorrowKind, Expr, ExprKind, HirId, LangItem, Mutability, Pat}; +use rustc_lint::LateContext; +use rustc_middle::{hir::map::Map, ty}; +use rustc_span::{sym, Symbol}; + +use super::UNNECESSARY_TO_OWNED; + +pub fn check(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, method_name: Symbol, receiver: &'tcx Expr<'tcx>) -> bool { + if_chain! { + if let Some(parent) = get_parent_expr(cx, expr); + if let Some(callee_def_id) = fn_def_id(cx, parent); + if is_into_iter(cx, callee_def_id); + then { + check_for_loop_iter(cx, parent, method_name, receiver) + } else { + false + } + } +} + +/// Checks whether `expr` is an iterator in a `for` loop and, if so, determines whether the +/// iterated-over items could be iterated over by reference. The reason why `check` above does not +/// include this code directly is so that it can be called from +/// `unnecessary_into_owned::check_into_iter_call_arg`. +pub fn check_for_loop_iter( + cx: &LateContext<'tcx>, + expr: &'tcx Expr<'tcx>, + method_name: Symbol, + receiver: &'tcx Expr<'tcx>, +) -> bool { + if_chain! { + if let Some(grandparent) = get_parent_expr(cx, expr).and_then(|parent| get_parent_expr(cx, parent)); + if let Some(ForLoop { pat, body, .. }) = ForLoop::hir(grandparent); + let (clone_or_copy_needed, addr_of_exprs) = clone_or_copy_needed(cx, pat, body); + if !clone_or_copy_needed; + if let Some(receiver_snippet) = snippet_opt(cx, receiver.span); + then { + let snippet = if_chain! { + if let ExprKind::MethodCall(maybe_iter_method_name, _, [collection], _) = receiver.kind; + if maybe_iter_method_name.ident.name == sym::iter; + + if let Some(iterator_trait_id) = cx.tcx.get_diagnostic_item(sym::Iterator); + let receiver_ty = cx.typeck_results().expr_ty(receiver); + if implements_trait(cx, receiver_ty, iterator_trait_id, &[]); + if let Some(iter_item_ty) = get_iterator_item_ty(cx, receiver_ty); + + if let Some(into_iterator_trait_id) = cx.tcx.get_diagnostic_item(sym::IntoIterator); + let collection_ty = cx.typeck_results().expr_ty(collection); + if implements_trait(cx, collection_ty, into_iterator_trait_id, &[]); + if let Some(into_iter_item_ty) = get_associated_type(cx, collection_ty, into_iterator_trait_id, "Item"); + + if iter_item_ty == into_iter_item_ty; + if let Some(collection_snippet) = snippet_opt(cx, collection.span); + then { + collection_snippet + } else { + receiver_snippet + } + }; + span_lint_and_then( + cx, + UNNECESSARY_TO_OWNED, + expr.span, + &format!("unnecessary use of `{}`", method_name), + |diag| { + diag.span_suggestion(expr.span, "use", snippet, Applicability::MachineApplicable); + for addr_of_expr in addr_of_exprs { + match addr_of_expr.kind { + ExprKind::AddrOf(_, _, referent) => { + let span = addr_of_expr.span.with_hi(referent.span.lo()); + diag.span_suggestion(span, "remove this `&`", String::new(), Applicability::MachineApplicable); + } + _ => unreachable!(), + } + } + } + ); + return true; + } + } + false +} + +/// The core logic of `check_for_loop_iter` above, this function wraps a use of +/// `CloneOrCopyVisitor`. +fn clone_or_copy_needed( + cx: &LateContext<'tcx>, + pat: &Pat<'tcx>, + body: &'tcx Expr<'tcx>, +) -> (bool, Vec<&'tcx Expr<'tcx>>) { + let mut visitor = CloneOrCopyVisitor { + cx, + binding_hir_ids: pat_bindings(pat), + clone_or_copy_needed: false, + addr_of_exprs: Vec::new(), + }; + visitor.visit_expr(body); + (visitor.clone_or_copy_needed, visitor.addr_of_exprs) +} + +/// Returns a vector of all `HirId`s bound by the pattern. +fn pat_bindings(pat: &Pat<'_>) -> Vec<HirId> { + let mut collector = usage::ParamBindingIdCollector { + binding_hir_ids: Vec::new(), + }; + collector.visit_pat(pat); + collector.binding_hir_ids +} + +/// `clone_or_copy_needed` will be false when `CloneOrCopyVisitor` is done visiting if the only +/// operations performed on `binding_hir_ids` are: +/// * to take non-mutable references to them +/// * to use them as non-mutable `&self` in method calls +/// If any of `binding_hir_ids` is used in any other way, then `clone_or_copy_needed` will be true +/// when `CloneOrCopyVisitor` is done visiting. +struct CloneOrCopyVisitor<'cx, 'tcx> { + cx: &'cx LateContext<'tcx>, + binding_hir_ids: Vec<HirId>, + clone_or_copy_needed: bool, + addr_of_exprs: Vec<&'tcx Expr<'tcx>>, +} + +impl<'cx, 'tcx> Visitor<'tcx> for CloneOrCopyVisitor<'cx, 'tcx> { + type Map = Map<'tcx>; + + fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> { + NestedVisitorMap::OnlyBodies(self.cx.tcx.hir()) + } + + fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) { + walk_expr(self, expr); + if self.is_binding(expr) { + if let Some(parent) = get_parent_expr(self.cx, expr) { + match parent.kind { + ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, _) => { + self.addr_of_exprs.push(parent); + return; + }, + ExprKind::MethodCall(_, _, args, _) => { + if_chain! { + if args.iter().skip(1).all(|arg| !self.is_binding(arg)); + if let Some(method_def_id) = self.cx.typeck_results().type_dependent_def_id(parent.hir_id); + let method_ty = self.cx.tcx.type_of(method_def_id); + let self_ty = method_ty.fn_sig(self.cx.tcx).input(0).skip_binder(); + if matches!(self_ty.kind(), ty::Ref(_, _, Mutability::Not)); + then { + return; + } + } + }, + _ => {}, + } + } + self.clone_or_copy_needed = true; + } + } +} + +impl<'cx, 'tcx> CloneOrCopyVisitor<'cx, 'tcx> { + fn is_binding(&self, expr: &Expr<'tcx>) -> bool { + self.binding_hir_ids + .iter() + .any(|hir_id| path_to_local_id(expr, *hir_id)) + } +} + +/// Returns true if the named method is `IntoIterator::into_iter`. +pub fn is_into_iter(cx: &LateContext<'_>, callee_def_id: DefId) -> bool { + cx.tcx.lang_items().require(LangItem::IntoIterIntoIter) == Ok(callee_def_id) +} diff --git a/clippy_lints/src/methods/unnecessary_to_owned.rs b/clippy_lints/src/methods/unnecessary_to_owned.rs new file mode 100644 index 00000000000..c48bacfce0d --- /dev/null +++ b/clippy_lints/src/methods/unnecessary_to_owned.rs @@ -0,0 +1,397 @@ +use super::implicit_clone::is_clone_like; +use super::unnecessary_iter_cloned::{self, is_into_iter}; +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::source::snippet_opt; +use clippy_utils::ty::{get_associated_type, get_iterator_item_ty, implements_trait, is_copy, peel_mid_ty_refs}; +use clippy_utils::{fn_def_id, get_parent_expr, is_diag_item_method, is_diag_trait_item}; +use rustc_errors::Applicability; +use rustc_hir::{def_id::DefId, BorrowKind, Expr, ExprKind}; +use rustc_lint::LateContext; +use rustc_middle::mir::Mutability; +use rustc_middle::ty::adjustment::{Adjust, Adjustment, OverloadedDeref}; +use rustc_middle::ty::subst::{GenericArg, GenericArgKind, SubstsRef}; +use rustc_middle::ty::{self, PredicateKind, ProjectionPredicate, TraitPredicate, Ty}; +use rustc_span::{sym, Symbol}; +use std::cmp::max; + +use super::UNNECESSARY_TO_OWNED; + +pub fn check(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, method_name: Symbol, args: &'tcx [Expr<'tcx>]) { + if_chain! { + if let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id); + if let [receiver] = args; + then { + if is_cloned_or_copied(cx, method_name, method_def_id) { + unnecessary_iter_cloned::check(cx, expr, method_name, receiver); + } else if is_to_owned_like(cx, method_name, method_def_id) { + // At this point, we know the call is of a `to_owned`-like function. The functions + // `check_addr_of_expr` and `check_call_arg` determine whether the call is unnecessary + // based on its context, that is, whether it is a referent in an `AddrOf` expression, an + // argument in a `into_iter` call, or an argument in the call of some other function. + if check_addr_of_expr(cx, expr, method_name, method_def_id, receiver) { + return; + } + if check_into_iter_call_arg(cx, expr, method_name, receiver) { + return; + } + check_other_call_arg(cx, expr, method_name, receiver); + } + } + } +} + +/// Checks whether `expr` is a referent in an `AddrOf` expression and, if so, determines whether its +/// call of a `to_owned`-like function is unnecessary. +#[allow(clippy::too_many_lines)] +fn check_addr_of_expr( + cx: &LateContext<'tcx>, + expr: &'tcx Expr<'tcx>, + method_name: Symbol, + method_def_id: DefId, + receiver: &'tcx Expr<'tcx>, +) -> bool { + if_chain! { + if let Some(parent) = get_parent_expr(cx, expr); + if let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, _) = parent.kind; + let adjustments = cx.typeck_results().expr_adjustments(parent).iter().collect::<Vec<_>>(); + if let Some(target_ty) = match adjustments[..] + { + // For matching uses of `Cow::from` + [ + Adjustment { + kind: Adjust::Deref(None), + .. + }, + Adjustment { + kind: Adjust::Borrow(_), + target: target_ty, + }, + ] + // For matching uses of arrays + | [ + Adjustment { + kind: Adjust::Deref(None), + .. + }, + Adjustment { + kind: Adjust::Borrow(_), + .. + }, + Adjustment { + kind: Adjust::Pointer(_), + target: target_ty, + }, + ] + // For matching everything else + | [ + Adjustment { + kind: Adjust::Deref(None), + .. + }, + Adjustment { + kind: Adjust::Deref(Some(OverloadedDeref { .. })), + .. + }, + Adjustment { + kind: Adjust::Borrow(_), + target: target_ty, + }, + ] => Some(target_ty), + _ => None, + }; + let receiver_ty = cx.typeck_results().expr_ty(receiver); + // Only flag cases where the receiver is copyable or the method is `Cow::into_owned`. This + // restriction is to ensure there is not overlap between `redundant_clone` and this lint. + if is_copy(cx, receiver_ty) || is_cow_into_owned(cx, method_name, method_def_id); + if let Some(receiver_snippet) = snippet_opt(cx, receiver.span); + then { + let (target_ty, n_target_refs) = peel_mid_ty_refs(target_ty); + let (receiver_ty, n_receiver_refs) = peel_mid_ty_refs(receiver_ty); + if receiver_ty == target_ty && n_target_refs >= n_receiver_refs { + span_lint_and_sugg( + cx, + UNNECESSARY_TO_OWNED, + parent.span, + &format!("unnecessary use of `{}`", method_name), + "use", + format!("{:&>width$}{}", "", receiver_snippet, width = n_target_refs - n_receiver_refs), + Applicability::MachineApplicable, + ); + return true; + } + if_chain! { + if let Some(deref_trait_id) = cx.tcx.get_diagnostic_item(sym::Deref); + if implements_trait(cx, receiver_ty, deref_trait_id, &[]); + if get_associated_type(cx, receiver_ty, deref_trait_id, "Target") == Some(target_ty); + then { + if n_receiver_refs > 0 { + span_lint_and_sugg( + cx, + UNNECESSARY_TO_OWNED, + parent.span, + &format!("unnecessary use of `{}`", method_name), + "use", + receiver_snippet, + Applicability::MachineApplicable, + ); + } else { + span_lint_and_sugg( + cx, + UNNECESSARY_TO_OWNED, + expr.span.with_lo(receiver.span.hi()), + &format!("unnecessary use of `{}`", method_name), + "remove this", + String::new(), + Applicability::MachineApplicable, + ); + } + return true; + } + } + if_chain! { + if let Some(as_ref_trait_id) = cx.tcx.get_diagnostic_item(sym::AsRef); + if implements_trait(cx, receiver_ty, as_ref_trait_id, &[GenericArg::from(target_ty)]); + then { + span_lint_and_sugg( + cx, + UNNECESSARY_TO_OWNED, + parent.span, + &format!("unnecessary use of `{}`", method_name), + "use", + format!("{}.as_ref()", receiver_snippet), + Applicability::MachineApplicable, + ); + return true; + } + } + } + } + false +} + +/// Checks whether `expr` is an argument in an `into_iter` call and, if so, determines whether its +/// call of a `to_owned`-like function is unnecessary. +fn check_into_iter_call_arg( + cx: &LateContext<'tcx>, + expr: &'tcx Expr<'tcx>, + method_name: Symbol, + receiver: &'tcx Expr<'tcx>, +) -> bool { + if_chain! { + if let Some(parent) = get_parent_expr(cx, expr); + if let Some(callee_def_id) = fn_def_id(cx, parent); + if is_into_iter(cx, callee_def_id); + if let Some(iterator_trait_id) = cx.tcx.get_diagnostic_item(sym::Iterator); + let parent_ty = cx.typeck_results().expr_ty(parent); + if implements_trait(cx, parent_ty, iterator_trait_id, &[]); + if let Some(item_ty) = get_iterator_item_ty(cx, parent_ty); + if let Some(receiver_snippet) = snippet_opt(cx, receiver.span); + then { + if unnecessary_iter_cloned::check_for_loop_iter(cx, parent, method_name, receiver) { + return true; + } + let cloned_or_copied = if is_copy(cx, item_ty) { + "copied" + } else { + "cloned" + }; + span_lint_and_sugg( + cx, + UNNECESSARY_TO_OWNED, + parent.span, + &format!("unnecessary use of `{}`", method_name), + "use", + format!("{}.iter().{}()", receiver_snippet, cloned_or_copied), + Applicability::MachineApplicable, + ); + return true; + } + } + false +} + +/// Checks whether `expr` is an argument in a function call and, if so, determines whether its call +/// of a `to_owned`-like function is unnecessary. +fn check_other_call_arg( + cx: &LateContext<'tcx>, + expr: &'tcx Expr<'tcx>, + method_name: Symbol, + receiver: &'tcx Expr<'tcx>, +) -> bool { + if_chain! { + if let Some((maybe_call, maybe_arg)) = skip_addr_of_ancestors(cx, expr); + if let Some((callee_def_id, call_substs, call_args)) = get_callee_substs_and_args(cx, maybe_call); + let fn_sig = cx.tcx.fn_sig(callee_def_id).skip_binder(); + if let Some(i) = call_args.iter().position(|arg| arg.hir_id == maybe_arg.hir_id); + if let Some(input) = fn_sig.inputs().get(i); + let (input, n_refs) = peel_mid_ty_refs(input); + if let (trait_predicates, projection_predicates) = get_input_traits_and_projections(cx, callee_def_id, input); + if let Some(sized_def_id) = cx.tcx.lang_items().sized_trait(); + if let [trait_predicate] = trait_predicates + .iter() + .filter(|trait_predicate| trait_predicate.def_id() != sized_def_id) + .collect::<Vec<_>>()[..]; + if let Some(deref_trait_id) = cx.tcx.get_diagnostic_item(sym::Deref); + if let Some(as_ref_trait_id) = cx.tcx.get_diagnostic_item(sym::AsRef); + let receiver_ty = cx.typeck_results().expr_ty(receiver); + // If the callee has type parameters, they could appear in `projection_predicate.ty` or the + // types of `trait_predicate.trait_ref.substs`. + if if trait_predicate.def_id() == deref_trait_id { + if let [projection_predicate] = projection_predicates[..] { + let normalized_ty = + cx.tcx.subst_and_normalize_erasing_regions(call_substs, cx.param_env, projection_predicate.ty); + implements_trait(cx, receiver_ty, deref_trait_id, &[]) + && get_associated_type(cx, receiver_ty, deref_trait_id, "Target") == Some(normalized_ty) + } else { + false + } + } else if trait_predicate.def_id() == as_ref_trait_id { + let composed_substs = compose_substs( + cx, + &trait_predicate.trait_ref.substs.iter().skip(1).collect::<Vec<_>>()[..], + call_substs + ); + implements_trait(cx, receiver_ty, as_ref_trait_id, &composed_substs) + } else { + false + }; + // We can't add an `&` when the trait is `Deref` because `Target = &T` won't match + // `Target = T`. + if n_refs > 0 || is_copy(cx, receiver_ty) || trait_predicate.def_id() != deref_trait_id; + let n_refs = max(n_refs, if is_copy(cx, receiver_ty) { 0 } else { 1 }); + if let Some(receiver_snippet) = snippet_opt(cx, receiver.span); + then { + span_lint_and_sugg( + cx, + UNNECESSARY_TO_OWNED, + maybe_arg.span, + &format!("unnecessary use of `{}`", method_name), + "use", + format!("{:&>width$}{}", "", receiver_snippet, width = n_refs), + Applicability::MachineApplicable, + ); + return true; + } + } + false +} + +/// Walks an expression's ancestors until it finds a non-`AddrOf` expression. Returns the first such +/// expression found (if any) along with the immediately prior expression. +fn skip_addr_of_ancestors( + cx: &LateContext<'tcx>, + mut expr: &'tcx Expr<'tcx>, +) -> Option<(&'tcx Expr<'tcx>, &'tcx Expr<'tcx>)> { + while let Some(parent) = get_parent_expr(cx, expr) { + if let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, _) = parent.kind { + expr = parent; + } else { + return Some((parent, expr)); + } + } + None +} + +/// Checks whether an expression is a function or method call and, if so, returns its `DefId`, +/// `Substs`, and arguments. +fn get_callee_substs_and_args( + cx: &LateContext<'tcx>, + expr: &'tcx Expr<'tcx>, +) -> Option<(DefId, SubstsRef<'tcx>, &'tcx [Expr<'tcx>])> { + if_chain! { + if let ExprKind::Call(callee, args) = expr.kind; + let callee_ty = cx.typeck_results().expr_ty(callee); + if let ty::FnDef(callee_def_id, _) = callee_ty.kind(); + then { + let substs = cx.typeck_results().node_substs(callee.hir_id); + return Some((*callee_def_id, substs, args)); + } + } + if_chain! { + if let ExprKind::MethodCall(_, _, args, _) = expr.kind; + if let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id); + then { + let substs = cx.typeck_results().node_substs(expr.hir_id); + return Some((method_def_id, substs, args)); + } + } + None +} + +/// Returns the `TraitPredicate`s and `ProjectionPredicate`s for a function's input type. +fn get_input_traits_and_projections( + cx: &LateContext<'tcx>, + callee_def_id: DefId, + input: Ty<'tcx>, +) -> (Vec<TraitPredicate<'tcx>>, Vec<ProjectionPredicate<'tcx>>) { + let mut trait_predicates = Vec::new(); + let mut projection_predicates = Vec::new(); + for (predicate, _) in cx.tcx.predicates_of(callee_def_id).predicates.iter() { + // `substs` should have 1 + n elements. The first is the type on the left hand side of an + // `as`. The remaining n are trait parameters. + let is_input_substs = |substs: SubstsRef<'tcx>| { + if_chain! { + if let Some(arg) = substs.iter().next(); + if let GenericArgKind::Type(arg_ty) = arg.unpack(); + if arg_ty == input; + then { + true + } else { + false + } + } + }; + match predicate.kind().skip_binder() { + PredicateKind::Trait(trait_predicate) => { + if is_input_substs(trait_predicate.trait_ref.substs) { + trait_predicates.push(trait_predicate); + } + }, + PredicateKind::Projection(projection_predicate) => { + if is_input_substs(projection_predicate.projection_ty.substs) { + projection_predicates.push(projection_predicate); + } + }, + _ => {}, + } + } + (trait_predicates, projection_predicates) +} + +/// Composes two substitutions by applying the latter to the types of the former. +fn compose_substs(cx: &LateContext<'tcx>, left: &[GenericArg<'tcx>], right: SubstsRef<'tcx>) -> Vec<GenericArg<'tcx>> { + left.iter() + .map(|arg| { + if let GenericArgKind::Type(arg_ty) = arg.unpack() { + let normalized_ty = cx.tcx.subst_and_normalize_erasing_regions(right, cx.param_env, arg_ty); + GenericArg::from(normalized_ty) + } else { + *arg + } + }) + .collect() +} + +/// Returns true if the named method is `Iterator::cloned` or `Iterator::copied`. +fn is_cloned_or_copied(cx: &LateContext<'_>, method_name: Symbol, method_def_id: DefId) -> bool { + (method_name.as_str() == "cloned" || method_name.as_str() == "copied") + && is_diag_trait_item(cx, method_def_id, sym::Iterator) +} + +/// Returns true if the named method can be used to convert the receiver to its "owned" +/// representation. +fn is_to_owned_like(cx: &LateContext<'_>, method_name: Symbol, method_def_id: DefId) -> bool { + is_clone_like(cx, &*method_name.as_str(), method_def_id) + || is_cow_into_owned(cx, method_name, method_def_id) + || is_to_string(cx, method_name, method_def_id) +} + +/// Returns true if the named method is `Cow::into_owned`. +fn is_cow_into_owned(cx: &LateContext<'_>, method_name: Symbol, method_def_id: DefId) -> bool { + method_name.as_str() == "into_owned" && is_diag_item_method(cx, method_def_id, sym::Cow) +} + +/// Returns true if the named method is `ToString::to_string`. +fn is_to_string(cx: &LateContext<'_>, method_name: Symbol, method_def_id: DefId) -> bool { + method_name.as_str() == "to_string" && is_diag_trait_item(cx, method_def_id, sym::ToString) +} |
