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 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 clippy_utils::{meets_msrv, msrvs};
use rustc_errors::Applicability;
-use rustc_hir::{def_id::DefId, BorrowKind, Expr, ExprKind};
+use rustc_hir::{def_id::DefId, BorrowKind, Expr, ExprKind, ItemKind, Node};
+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::subst::{GenericArg, GenericArgKind, SubstsRef};
-use rustc_middle::ty::{self, PredicateKind, ProjectionPredicate, TraitPredicate, Ty};
+use rustc_middle::ty::EarlyBinder;
+use rustc_middle::ty::{self, ParamTy, PredicateKind, ProjectionPredicate, TraitPredicate, Ty};
+use rustc_semver::RustcVersion;
use rustc_span::{sym, Symbol};
+use rustc_trait_selection::traits::{query::evaluate_obligation::InferCtxtExt as _, Obligation, ObligationCause};
+use rustc_typeck::check::{FnCtxt, Inherited};
use std::cmp::max;
use super::UNNECESSARY_TO_OWNED;
-pub fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, method_name: Symbol, args: &'tcx [Expr<'tcx>]) {
+pub fn check<'tcx>(
+ cx: &LateContext<'tcx>,
+ expr: &'tcx Expr<'tcx>,
+ method_name: Symbol,
+ receiver: &'tcx Expr<'_>,
+ args: &'tcx [Expr<'_>],
+ msrv: Option<RustcVersion>,
+) {
if_chain! {
if let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id);
- if let [receiver] = args;
+ if args.is_empty();
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) {
+ } else if is_to_owned_like(cx, expr, 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
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) {
+ if check_into_iter_call_arg(cx, expr, method_name, receiver, msrv) {
return;
}
check_other_call_arg(cx, expr, method_name, receiver);
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[..]
- {
+ if let
// For matching uses of `Cow::from`
[
Adjustment {
kind: Adjust::Deref(None),
- ..
+ target: referent_ty,
},
Adjustment {
kind: Adjust::Borrow(_),
| [
Adjustment {
kind: Adjust::Deref(None),
- ..
+ target: referent_ty,
},
Adjustment {
kind: Adjust::Borrow(_),
| [
Adjustment {
kind: Adjust::Deref(None),
- ..
+ target: referent_ty,
},
Adjustment {
kind: Adjust::Deref(Some(OverloadedDeref { .. })),
kind: Adjust::Borrow(_),
target: target_ty,
},
- ] => Some(target_ty),
- _ => None,
- };
+ ] = adjustments[..];
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);
+ 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.
+ if *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);
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,
parent.span,
&format!("unnecessary use of `{}`", method_name),
"use",
- format!("{:&>width$}{}", "", receiver_snippet, width = n_target_refs - n_receiver_refs),
+ format!(
+ "{:&>width$}{}",
+ "",
+ receiver_snippet,
+ width = n_target_refs - n_receiver_refs
+ ),
Applicability::MachineApplicable,
);
return true;
/// 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<'_>) -> bool {
+fn check_into_iter_call_arg(
+ cx: &LateContext<'_>,
+ expr: &Expr<'_>,
+ method_name: Symbol,
+ receiver: &Expr<'_>,
+ msrv: Option<RustcVersion>,
+) -> bool {
if_chain! {
if let Some(parent) = get_parent_expr(cx, expr);
if let Some(callee_def_id) = fn_def_id(cx, parent);
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,
- true,
- ) {
+ 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) {
+ let cloned_or_copied = if is_copy(cx, item_ty) && meets_msrv(msrv, msrvs::ITERATOR_COPIED) {
"copied"
} else {
"cloned"
) -> 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);
+ if let Some((callee_def_id, _, recv, 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(i) = recv.into_iter().chain(call_args).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);
+ let (input, n_refs) = peel_mid_ty_refs(*input);
+ if let (trait_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()
.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);
+ if trait_predicate.def_id() == deref_trait_id || trait_predicate.def_id() == as_ref_trait_id;
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.term);
- implements_trait(cx, receiver_ty, deref_trait_id, &[])
- && get_associated_type(cx, receiver_ty, deref_trait_id,
- "Target").map_or(false, |ty| ty::Term::Ty(ty) == 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
- };
+ if can_change_type(cx, maybe_arg, receiver_ty);
// 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;
fn get_callee_substs_and_args<'tcx>(
cx: &LateContext<'tcx>,
expr: &'tcx Expr<'tcx>,
-) -> Option<(DefId, SubstsRef<'tcx>, &'tcx [Expr<'tcx>])> {
+) -> Option<(DefId, SubstsRef<'tcx>, Option<&'tcx Expr<'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));
+ return Some((*callee_def_id, substs, None, args));
}
}
if_chain! {
- if let ExprKind::MethodCall(_, _, args, _) = expr.kind;
+ if let ExprKind::MethodCall(_, recv, 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));
+ return Some((method_def_id, substs, Some(recv), args));
}
}
None
) -> (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
- }
- }
- };
+ for predicate in cx.tcx.param_env(callee_def_id).caller_bounds() {
match predicate.kind().skip_binder() {
PredicateKind::Trait(trait_predicate) => {
- if is_input_substs(trait_predicate.trait_ref.substs) {
+ if trait_predicate.trait_ref.self_ty() == input {
trait_predicates.push(trait_predicate);
}
},
PredicateKind::Projection(projection_predicate) => {
- if is_input_substs(projection_predicate.projection_ty.substs) {
+ if projection_predicate.projection_ty.self_ty() == input {
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<'tcx>(
- 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
+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.hir_id())
+ && let local_def_id = cx.tcx.hir().local_def_id(item.hir_id())
+ && Inherited::build(cx.tcx, local_def_id).enter(|inherited| {
+ let fn_ctxt = FnCtxt::new(&inherited, cx.param_env, item.hir_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 });
+ }
}
- })
- .collect()
+ Node::Expr(parent_expr) => {
+ if let Some((callee_def_id, call_substs, recv, call_args)) = get_callee_substs_and_args(cx, parent_expr)
+ {
+ let fn_sig = cx.tcx.fn_sig(callee_def_id).skip_binder();
+ if let Some(arg_index) = recv.into_iter().chain(call_args).position(|arg| arg.hir_id == expr.hir_id)
+ && let Some(param_ty) = fn_sig.inputs().get(arg_index)
+ && let ty::Param(ParamTy { index: param_index , ..}) = param_ty.kind()
+ {
+ if fn_sig
+ .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 PredicateKind::Trait(trait_predicate) = predicate.kind().skip_binder()
+ && trait_predicate.trait_ref.self_ty() == *param_ty {
+ true
+ } else {
+ false
+ }
+ });
+
+ let new_subst = cx.tcx.mk_substs(
+ call_substs.iter()
+ .enumerate()
+ .map(|(i, t)|
+ if i == (*param_index as usize) {
+ GenericArg::from(ty)
+ } else {
+ t
+ }));
+
+ if trait_predicates.any(|predicate| {
+ let predicate = EarlyBinder(predicate).subst(cx.tcx, new_subst);
+ let obligation = Obligation::new(ObligationCause::dummy(), cx.param_env, predicate);
+ !cx.tcx
+ .infer_ctxt()
+ .enter(|infcx| infcx.predicate_must_hold_modulo_regions(&obligation))
+ }) {
+ return false;
+ }
+
+ let output_ty = fn_sig.output();
+ if output_ty.contains(*param_ty) {
+ if let Ok(new_ty) = cx.tcx.try_subst_and_normalize_erasing_regions(
+ new_subst, cx.param_env, 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`.
/// 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)
+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(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`.
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)
+/// 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<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(substs) = cx.typeck_results().node_substs_opt(call_expr.hir_id)
+ && let [generic_arg] = substs.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)
+ && (implements_trait(cx, ty, deref_trait_id, &[cx.tcx.types.str_.into()]) ||
+ implements_trait(cx, ty, as_ref_trait_id, &[cx.tcx.types.str_.into()])) {
+ true
+ } else {
+ false
+ }
}