blob: c234e4f9b110c4715e2739e20257ae24a628bfb6 [file] [log] [blame]
use super::implicit_clone::is_clone_like;
use super::unnecessary_iter_cloned::{self, is_into_iter};
use clippy_config::msrvs::{self, Msrv};
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet_opt;
use clippy_utils::ty::{
get_iterator_item_ty, implements_trait, is_copy, is_type_diagnostic_item, is_type_lang_item, peel_mid_ty_refs,
};
use clippy_utils::visitors::find_all_ret_expressions;
use clippy_utils::{fn_def_id, get_parent_expr, is_diag_item_method, is_diag_trait_item, return_ty};
use rustc_errors::Applicability;
use rustc_hir::def::{DefKind, Res};
use rustc_hir::def_id::DefId;
use rustc_hir::{BorrowKind, Expr, ExprKind, ItemKind, LangItem, Node};
use rustc_hir_typeck::{FnCtxt, Inherited};
use rustc_infer::infer::TyCtxtInferExt;
use rustc_lint::LateContext;
use rustc_middle::mir::Mutability;
use rustc_middle::ty::adjustment::{Adjust, Adjustment, OverloadedDeref};
use rustc_middle::ty::{
self, ClauseKind, GenericArg, GenericArgKind, GenericArgsRef, ImplPolarity, ParamTy, ProjectionPredicate,
TraitPredicate, Ty,
};
use rustc_span::{sym, Symbol};
use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt as _;
use rustc_trait_selection::traits::{Obligation, ObligationCause};
use super::UNNECESSARY_TO_OWNED;
pub fn check<'tcx>(
cx: &LateContext<'tcx>,
expr: &'tcx Expr<'tcx>,
method_name: Symbol,
receiver: &'tcx Expr<'_>,
args: &'tcx [Expr<'_>],
msrv: &Msrv,
) {
if let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id)
&& args.is_empty()
{
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, expr, method_name, method_def_id) {
if check_split_call_arg(cx, expr, method_name, receiver) {
return;
}
// 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, msrv) {
return;
}
check_other_call_arg(cx, expr, method_name, receiver);
}
} else {
check_borrow_predicate(cx, expr);
}
}
/// 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<'_>,
expr: &Expr<'_>,
method_name: Symbol,
method_def_id: DefId,
receiver: &Expr<'_>,
) -> bool {
if let Some(parent) = get_parent_expr(cx, expr)
&& let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, _) = parent.kind
&& let adjustments = cx.typeck_results().expr_adjustments(parent).iter().collect::<Vec<_>>()
&& let
// For matching uses of `Cow::from`
[
Adjustment {
kind: Adjust::Deref(None),
target: referent_ty,
},
Adjustment {
kind: Adjust::Borrow(_),
target: target_ty,
},
]
// For matching uses of arrays
| [
Adjustment {
kind: Adjust::Deref(None),
target: referent_ty,
},
Adjustment {
kind: Adjust::Borrow(_),
..
},
Adjustment {
kind: Adjust::Pointer(_),
target: target_ty,
},
]
// For matching everything else
| [
Adjustment {
kind: Adjust::Deref(None),
target: referent_ty,
},
Adjustment {
kind: Adjust::Deref(Some(OverloadedDeref { .. })),
..
},
Adjustment {
kind: Adjust::Borrow(_),
target: target_ty,
},
] = adjustments[..]
&& let receiver_ty = cx.typeck_results().expr_ty(receiver)
&& let (target_ty, n_target_refs) = peel_mid_ty_refs(*target_ty)
&& let (receiver_ty, n_receiver_refs) = peel_mid_ty_refs(receiver_ty)
// Only flag cases satisfying at least one of the following three conditions:
// * the referent and receiver types are distinct
// * the referent/receiver type is a copyable array
// * the method is `Cow::into_owned`
// This restriction is to ensure there is no overlap between `redundant_clone` and this
// lint. It also avoids the following false positive:
// https://github.com/rust-lang/rust-clippy/issues/8759
// Arrays are a bit of a corner case. Non-copyable arrays are handled by
// `redundant_clone`, but copyable arrays are not.
&& (*referent_ty != receiver_ty
|| (matches!(referent_ty.kind(), ty::Array(..)) && is_copy(cx, *referent_ty))
|| is_cow_into_owned(cx, method_name, method_def_id))
&& let Some(receiver_snippet) = snippet_opt(cx, receiver.span)
{
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 let Some(deref_trait_id) = cx.tcx.get_diagnostic_item(sym::Deref)
&& implements_trait(cx, receiver_ty, deref_trait_id, &[])
&& cx.get_associated_type(receiver_ty, deref_trait_id, "Target") == Some(target_ty)
// Make sure that it's actually calling the right `.to_string()`, (#10033)
// *or* this is a `Cow::into_owned()` call (which would be the wrong into_owned receiver (str != Cow)
// but that's ok for Cow::into_owned specifically)
&& (cx.typeck_results().expr_ty_adjusted(receiver).peel_refs() == target_ty
|| is_cow_into_owned(cx, method_name, method_def_id))
{
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 let Some(as_ref_trait_id) = cx.tcx.get_diagnostic_item(sym::AsRef)
&& implements_trait(cx, receiver_ty, as_ref_trait_id, &[GenericArg::from(target_ty)])
{
span_lint_and_sugg(
cx,
UNNECESSARY_TO_OWNED,
parent.span,
&format!("unnecessary use of `{method_name}`"),
"use",
format!("{receiver_snippet}.as_ref()"),
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<'_>,
expr: &Expr<'_>,
method_name: Symbol,
receiver: &Expr<'_>,
msrv: &Msrv,
) -> bool {
if let Some(parent) = get_parent_expr(cx, expr)
&& let Some(callee_def_id) = fn_def_id(cx, parent)
&& is_into_iter(cx, callee_def_id)
&& let Some(iterator_trait_id) = cx.tcx.get_diagnostic_item(sym::Iterator)
&& let parent_ty = cx.typeck_results().expr_ty(parent)
&& implements_trait(cx, parent_ty, iterator_trait_id, &[])
&& let Some(item_ty) = get_iterator_item_ty(cx, parent_ty)
&& let Some(receiver_snippet) = snippet_opt(cx, receiver.span)
{
if unnecessary_iter_cloned::check_for_loop_iter(cx, parent, method_name, receiver, true) {
return true;
}
let cloned_or_copied = if is_copy(cx, item_ty) && msrv.meets(msrvs::ITERATOR_COPIED) {
"copied"
} else {
"cloned"
};
// The next suggestion may be incorrect because the removal of the `to_owned`-like
// function could cause the iterator to hold a reference to a resource that is used
// mutably. See https://github.com/rust-lang/rust-clippy/issues/8148.
span_lint_and_sugg(
cx,
UNNECESSARY_TO_OWNED,
parent.span,
&format!("unnecessary use of `{method_name}`"),
"use",
format!("{receiver_snippet}.iter().{cloned_or_copied}()"),
Applicability::MaybeIncorrect,
);
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_split_call_arg(cx: &LateContext<'_>, expr: &Expr<'_>, method_name: Symbol, receiver: &Expr<'_>) -> bool {
if let Some(parent) = get_parent_expr(cx, expr)
&& let Some((fn_name, argument_expr)) = get_fn_name_and_arg(cx, parent)
&& fn_name.as_str() == "split"
&& let Some(receiver_snippet) = snippet_opt(cx, receiver.span)
&& let Some(arg_snippet) = snippet_opt(cx, argument_expr.span)
{
// We may end-up here because of an expression like `x.to_string().split(…)` where the type of `x`
// implements `AsRef<str>` but does not implement `Deref<Target = str>`. In this case, we have to
// add `.as_ref()` to the suggestion.
let as_ref = if is_type_lang_item(cx, cx.typeck_results().expr_ty(expr), LangItem::String)
&& let Some(deref_trait_id) = cx.tcx.get_diagnostic_item(sym::Deref)
&& cx.get_associated_type(cx.typeck_results().expr_ty(receiver), deref_trait_id, "Target")
!= Some(cx.tcx.types.str_)
{
".as_ref()"
} else {
""
};
// The next suggestion may be incorrect because the removal of the `to_owned`-like
// function could cause the iterator to hold a reference to a resource that is used
// mutably. See https://github.com/rust-lang/rust-clippy/issues/8148.
span_lint_and_sugg(
cx,
UNNECESSARY_TO_OWNED,
parent.span,
&format!("unnecessary use of `{method_name}`"),
"use",
format!("{receiver_snippet}{as_ref}.split({arg_snippet})"),
Applicability::MaybeIncorrect,
);
return true;
}
false
}
fn get_fn_name_and_arg<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) -> Option<(Symbol, Expr<'tcx>)> {
match &expr.kind {
ExprKind::MethodCall(path, _, [arg_expr], ..) => Some((path.ident.name, *arg_expr)),
ExprKind::Call(
Expr {
kind: ExprKind::Path(qpath),
hir_id: path_hir_id,
..
},
[arg_expr],
) => {
// Only return Fn-like DefIds, not the DefIds of statics/consts/etc that contain or
// deref to fn pointers, dyn Fn, impl Fn - #8850
if let Res::Def(DefKind::Fn | DefKind::Ctor(..) | DefKind::AssocFn, def_id) =
cx.typeck_results().qpath_res(qpath, *path_hir_id)
&& let Some(fn_name) = cx.tcx.opt_item_name(def_id)
{
Some((fn_name, *arg_expr))
} else {
None
}
},
_ => None,
}
}
/// 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<'tcx>(
cx: &LateContext<'tcx>,
expr: &'tcx Expr<'tcx>,
method_name: Symbol,
receiver: &'tcx Expr<'tcx>,
) -> bool {
if let Some((maybe_call, maybe_arg)) = skip_addr_of_ancestors(cx, expr)
&& let Some((callee_def_id, _, recv, call_args)) = get_callee_generic_args_and_args(cx, maybe_call)
&& let fn_sig = cx.tcx.fn_sig(callee_def_id).instantiate_identity().skip_binder()
&& let Some(i) = recv.into_iter().chain(call_args).position(|arg| arg.hir_id == maybe_arg.hir_id)
&& let Some(input) = fn_sig.inputs().get(i)
&& let (input, n_refs) = peel_mid_ty_refs(*input)
&& let (trait_predicates, _) = get_input_traits_and_projections(cx, callee_def_id, input)
&& let Some(sized_def_id) = cx.tcx.lang_items().sized_trait()
&& let [trait_predicate] = trait_predicates
.iter()
.filter(|trait_predicate| trait_predicate.def_id() != sized_def_id)
.collect::<Vec<_>>()[..]
&& let Some(deref_trait_id) = cx.tcx.get_diagnostic_item(sym::Deref)
&& let Some(as_ref_trait_id) = cx.tcx.get_diagnostic_item(sym::AsRef)
&& (trait_predicate.def_id() == deref_trait_id || trait_predicate.def_id() == as_ref_trait_id)
&& let receiver_ty = cx.typeck_results().expr_ty(receiver)
// We can't add an `&` when the trait is `Deref` because `Target = &T` won't match
// `Target = T`.
&& let Some((n_refs, receiver_ty)) = if n_refs > 0 || is_copy(cx, receiver_ty) {
Some((n_refs, receiver_ty))
} else if trait_predicate.def_id() != deref_trait_id {
Some((1, Ty::new_ref(cx.tcx,
cx.tcx.lifetimes.re_erased,
ty::TypeAndMut {
ty: receiver_ty,
mutbl: Mutability::Not,
},
)))
} else {
None
}
&& can_change_type(cx, maybe_arg, receiver_ty)
&& let Some(receiver_snippet) = snippet_opt(cx, receiver.span)
{
span_lint_and_sugg(
cx,
UNNECESSARY_TO_OWNED,
maybe_arg.span,
&format!("unnecessary use of `{method_name}`"),
"use",
format!("{:&>n_refs$}{receiver_snippet}", ""),
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<'tcx>(
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`,
/// `GenericArgs`, and arguments.
fn get_callee_generic_args_and_args<'tcx>(
cx: &LateContext<'tcx>,
expr: &'tcx Expr<'tcx>,
) -> Option<(
DefId,
GenericArgsRef<'tcx>,
Option<&'tcx Expr<'tcx>>,
&'tcx [Expr<'tcx>],
)> {
if let ExprKind::Call(callee, args) = expr.kind
&& let callee_ty = cx.typeck_results().expr_ty(callee)
&& let ty::FnDef(callee_def_id, _) = callee_ty.kind()
{
let generic_args = cx.typeck_results().node_args(callee.hir_id);
return Some((*callee_def_id, generic_args, None, args));
}
if let ExprKind::MethodCall(_, recv, args, _) = expr.kind
&& let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id)
{
let generic_args = cx.typeck_results().node_args(expr.hir_id);
return Some((method_def_id, generic_args, Some(recv), args));
}
None
}
/// Returns the `TraitPredicate`s and `ProjectionPredicate`s for a function's input type.
fn get_input_traits_and_projections<'tcx>(
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.param_env(callee_def_id).caller_bounds() {
match predicate.kind().skip_binder() {
ClauseKind::Trait(trait_predicate) => {
if trait_predicate.trait_ref.self_ty() == input {
trait_predicates.push(trait_predicate);
}
},
ClauseKind::Projection(projection_predicate) => {
if projection_predicate.projection_ty.self_ty() == input {
projection_predicates.push(projection_predicate);
}
},
_ => {},
}
}
(trait_predicates, projection_predicates)
}
#[expect(clippy::too_many_lines)]
fn can_change_type<'a>(cx: &LateContext<'a>, mut expr: &'a Expr<'a>, mut ty: Ty<'a>) -> bool {
for (_, node) in cx.tcx.hir().parent_iter(expr.hir_id) {
match node {
Node::Stmt(_) => return true,
Node::Block(..) => continue,
Node::Item(item) => {
if let ItemKind::Fn(_, _, body_id) = &item.kind
&& let output_ty = return_ty(cx, item.owner_id)
&& let inherited = Inherited::new(cx.tcx, item.owner_id.def_id)
&& let fn_ctxt = FnCtxt::new(&inherited, cx.param_env, item.owner_id.def_id)
&& fn_ctxt.can_coerce(ty, output_ty)
{
if has_lifetime(output_ty) && has_lifetime(ty) {
return false;
}
let body = cx.tcx.hir().body(*body_id);
let body_expr = &body.value;
let mut count = 0;
return find_all_ret_expressions(cx, body_expr, |_| {
count += 1;
count <= 1
});
}
},
Node::Expr(parent_expr) => {
if let Some((callee_def_id, call_generic_args, recv, call_args)) =
get_callee_generic_args_and_args(cx, parent_expr)
{
let bound_fn_sig = cx.tcx.fn_sig(callee_def_id);
let fn_sig = bound_fn_sig.skip_binder();
if let Some(arg_index) = recv
.into_iter()
.chain(call_args)
.position(|arg| arg.hir_id == expr.hir_id)
&& let param_ty = fn_sig.input(arg_index).skip_binder()
&& let ty::Param(ParamTy { index: param_index , ..}) = *param_ty.kind()
// https://github.com/rust-lang/rust-clippy/issues/9504 and https://github.com/rust-lang/rust-clippy/issues/10021
&& (param_index as usize) < call_generic_args.len()
{
if fn_sig
.skip_binder()
.inputs()
.iter()
.enumerate()
.filter(|(i, _)| *i != arg_index)
.any(|(_, ty)| ty.contains(param_ty))
{
return false;
}
let mut trait_predicates =
cx.tcx
.param_env(callee_def_id)
.caller_bounds()
.iter()
.filter(|predicate| {
if let ClauseKind::Trait(trait_predicate) = predicate.kind().skip_binder()
&& trait_predicate.trait_ref.self_ty() == param_ty
{
true
} else {
false
}
});
let new_subst = cx
.tcx
.mk_args_from_iter(call_generic_args.iter().enumerate().map(|(i, t)| {
if i == param_index as usize {
GenericArg::from(ty)
} else {
t
}
}));
if trait_predicates.any(|predicate| {
let predicate = bound_fn_sig.rebind(predicate).instantiate(cx.tcx, new_subst);
let obligation = Obligation::new(cx.tcx, ObligationCause::dummy(), cx.param_env, predicate);
!cx.tcx
.infer_ctxt()
.build()
.predicate_must_hold_modulo_regions(&obligation)
}) {
return false;
}
let output_ty = cx.tcx.instantiate_bound_regions_with_erased(fn_sig.output());
if output_ty.contains(param_ty) {
if let Ok(new_ty) = cx.tcx.try_instantiate_and_normalize_erasing_regions(
new_subst,
cx.param_env,
bound_fn_sig.rebind(output_ty),
) {
expr = parent_expr;
ty = new_ty;
continue;
}
return false;
}
return true;
}
} else if let ExprKind::Block(..) = parent_expr.kind {
continue;
}
return false;
},
_ => return false,
}
}
false
}
fn has_lifetime(ty: Ty<'_>) -> bool {
ty.walk().any(|t| matches!(t.unpack(), GenericArgKind::Lifetime(_)))
}
/// 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<'a>(cx: &LateContext<'a>, call_expr: &Expr<'a>, 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_on_string_like(cx, call_expr, 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` and it's called on a type that
/// is string-like i.e. implements `AsRef<str>` or `Deref<Target = str>`.
fn is_to_string_on_string_like<'a>(
cx: &LateContext<'_>,
call_expr: &'a Expr<'a>,
method_name: Symbol,
method_def_id: DefId,
) -> bool {
if method_name != sym::to_string || !is_diag_trait_item(cx, method_def_id, sym::ToString) {
return false;
}
if let Some(args) = cx.typeck_results().node_args_opt(call_expr.hir_id)
&& let [generic_arg] = args.as_slice()
&& let GenericArgKind::Type(ty) = generic_arg.unpack()
&& let Some(deref_trait_id) = cx.tcx.get_diagnostic_item(sym::Deref)
&& let Some(as_ref_trait_id) = cx.tcx.get_diagnostic_item(sym::AsRef)
&& (cx.get_associated_type(ty, deref_trait_id, "Target") == Some(cx.tcx.types.str_)
|| implements_trait(cx, ty, as_ref_trait_id, &[cx.tcx.types.str_.into()]))
{
true
} else {
false
}
}
fn is_a_std_map_type(cx: &LateContext<'_>, ty: Ty<'_>) -> bool {
is_type_diagnostic_item(cx, ty, sym::HashSet)
|| is_type_diagnostic_item(cx, ty, sym::HashMap)
|| is_type_diagnostic_item(cx, ty, sym::BTreeMap)
|| is_type_diagnostic_item(cx, ty, sym::BTreeSet)
}
fn is_str_and_string(cx: &LateContext<'_>, arg_ty: Ty<'_>, original_arg_ty: Ty<'_>) -> bool {
original_arg_ty.is_str() && is_type_lang_item(cx, arg_ty, LangItem::String)
}
fn is_slice_and_vec(cx: &LateContext<'_>, arg_ty: Ty<'_>, original_arg_ty: Ty<'_>) -> bool {
(original_arg_ty.is_slice() || original_arg_ty.is_array() || original_arg_ty.is_array_slice())
&& is_type_diagnostic_item(cx, arg_ty, sym::Vec)
}
// This function will check the following:
// 1. The argument is a non-mutable reference.
// 2. It calls `to_owned()`, `to_string()` or `to_vec()`.
// 3. That the method is called on `String` or on `Vec` (only types supported for the moment).
fn check_if_applicable_to_argument<'tcx>(cx: &LateContext<'tcx>, arg: &Expr<'tcx>) {
if let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, expr) = arg.kind
&& let ExprKind::MethodCall(method_path, caller, &[], _) = expr.kind
&& let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id)
&& let method_name = method_path.ident.name.as_str()
&& match method_name {
"to_owned" => cx.tcx.is_diagnostic_item(sym::to_owned_method, method_def_id),
"to_string" => cx.tcx.is_diagnostic_item(sym::to_string_method, method_def_id),
"to_vec" => cx
.tcx
.impl_of_method(method_def_id)
.filter(|&impl_did| cx.tcx.type_of(impl_did).instantiate_identity().is_slice())
.is_some(),
_ => false,
}
&& let original_arg_ty = cx.typeck_results().node_type(caller.hir_id).peel_refs()
&& let arg_ty = cx.typeck_results().expr_ty(arg)
&& let ty::Ref(_, arg_ty, Mutability::Not) = arg_ty.kind()
// FIXME: try to fix `can_change_type` to make it work in this case.
// && can_change_type(cx, caller, *arg_ty)
&& let arg_ty = arg_ty.peel_refs()
// For now we limit this lint to `String` and `Vec`.
&& (is_str_and_string(cx, arg_ty, original_arg_ty) || is_slice_and_vec(cx, arg_ty, original_arg_ty))
&& let Some(snippet) = snippet_opt(cx, caller.span)
{
span_lint_and_sugg(
cx,
UNNECESSARY_TO_OWNED,
arg.span,
&format!("unnecessary use of `{method_name}`"),
"replace it with",
if original_arg_ty.is_array() {
format!("{snippet}.as_slice()")
} else {
snippet
},
Applicability::MaybeIncorrect,
);
}
}
// In std "map types", the getters all expect a `Borrow<Key>` generic argument. So in here, we
// check that:
// 1. This is a method with only one argument that doesn't come from a trait.
// 2. That it has `Borrow` in its generic predicates.
// 3. `Self` is a std "map type" (ie `HashSet`, `HashMap`, BTreeSet`, `BTreeMap`).
fn check_borrow_predicate<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) {
if let ExprKind::MethodCall(_, caller, &[arg], _) = expr.kind
&& let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id)
&& cx.tcx.trait_of_item(method_def_id).is_none()
&& let Some(borrow_id) = cx.tcx.get_diagnostic_item(sym::Borrow)
&& cx.tcx.predicates_of(method_def_id).predicates.iter().any(|(pred, _)| {
if let ClauseKind::Trait(trait_pred) = pred.kind().skip_binder()
&& trait_pred.polarity == ImplPolarity::Positive
&& trait_pred.trait_ref.def_id == borrow_id
{
true
} else {
false
}
})
&& let caller_ty = cx.typeck_results().expr_ty(caller)
// For now we limit it to "map types".
&& is_a_std_map_type(cx, caller_ty)
{
check_if_applicable_to_argument(cx, &arg);
}
}