1 use super::implicit_clone::is_clone_like;
2 use super::unnecessary_iter_cloned::{self, is_into_iter};
3 use crate::rustc_middle::ty::Subst;
4 use clippy_utils::diagnostics::span_lint_and_sugg;
5 use clippy_utils::source::snippet_opt;
6 use clippy_utils::ty::{get_associated_type, get_iterator_item_ty, implements_trait, is_copy, peel_mid_ty_refs};
7 use clippy_utils::visitors::find_all_ret_expressions;
8 use clippy_utils::{fn_def_id, get_parent_expr, is_diag_item_method, is_diag_trait_item, return_ty};
9 use clippy_utils::{meets_msrv, msrvs};
10 use rustc_errors::Applicability;
11 use rustc_hir::{def_id::DefId, BorrowKind, Expr, ExprKind, ItemKind, Node};
12 use rustc_infer::infer::TyCtxtInferExt;
13 use rustc_lint::LateContext;
14 use rustc_middle::mir::Mutability;
15 use rustc_middle::ty::adjustment::{Adjust, Adjustment, OverloadedDeref};
16 use rustc_middle::ty::subst::{GenericArg, GenericArgKind, SubstsRef};
17 use rustc_middle::ty::EarlyBinder;
18 use rustc_middle::ty::{self, ParamTy, PredicateKind, ProjectionPredicate, TraitPredicate, Ty};
19 use rustc_semver::RustcVersion;
20 use rustc_span::{sym, Symbol};
21 use rustc_trait_selection::traits::{query::evaluate_obligation::InferCtxtExt as _, Obligation, ObligationCause};
22 use rustc_typeck::check::{FnCtxt, Inherited};
25 use super::UNNECESSARY_TO_OWNED;
28 cx: &LateContext<'tcx>,
29 expr: &'tcx Expr<'tcx>,
31 receiver: &'tcx Expr<'_>,
32 args: &'tcx [Expr<'_>],
33 msrv: Option<RustcVersion>,
36 if let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id);
39 if is_cloned_or_copied(cx, method_name, method_def_id) {
40 unnecessary_iter_cloned::check(cx, expr, method_name, receiver);
41 } else if is_to_owned_like(cx, expr, method_name, method_def_id) {
42 // At this point, we know the call is of a `to_owned`-like function. The functions
43 // `check_addr_of_expr` and `check_call_arg` determine whether the call is unnecessary
44 // based on its context, that is, whether it is a referent in an `AddrOf` expression, an
45 // argument in a `into_iter` call, or an argument in the call of some other function.
46 if check_addr_of_expr(cx, expr, method_name, method_def_id, receiver) {
49 if check_into_iter_call_arg(cx, expr, method_name, receiver, msrv) {
52 check_other_call_arg(cx, expr, method_name, receiver);
58 /// Checks whether `expr` is a referent in an `AddrOf` expression and, if so, determines whether its
59 /// call of a `to_owned`-like function is unnecessary.
60 #[allow(clippy::too_many_lines)]
61 fn check_addr_of_expr(
69 if let Some(parent) = get_parent_expr(cx, expr);
70 if let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, _) = parent.kind;
71 let adjustments = cx.typeck_results().expr_adjustments(parent).iter().collect::<Vec<_>>();
73 // For matching uses of `Cow::from`
76 kind: Adjust::Deref(None),
80 kind: Adjust::Borrow(_),
84 // For matching uses of arrays
87 kind: Adjust::Deref(None),
91 kind: Adjust::Borrow(_),
95 kind: Adjust::Pointer(_),
99 // For matching everything else
102 kind: Adjust::Deref(None),
106 kind: Adjust::Deref(Some(OverloadedDeref { .. })),
110 kind: Adjust::Borrow(_),
114 let receiver_ty = cx.typeck_results().expr_ty(receiver);
115 let (target_ty, n_target_refs) = peel_mid_ty_refs(*target_ty);
116 let (receiver_ty, n_receiver_refs) = peel_mid_ty_refs(receiver_ty);
117 // Only flag cases satisfying at least one of the following three conditions:
118 // * the referent and receiver types are distinct
119 // * the referent/receiver type is a copyable array
120 // * the method is `Cow::into_owned`
121 // This restriction is to ensure there is no overlap between `redundant_clone` and this
122 // lint. It also avoids the following false positive:
123 // https://github.com/rust-lang/rust-clippy/issues/8759
124 // Arrays are a bit of a corner case. Non-copyable arrays are handled by
125 // `redundant_clone`, but copyable arrays are not.
126 if *referent_ty != receiver_ty
127 || (matches!(referent_ty.kind(), ty::Array(..)) && is_copy(cx, *referent_ty))
128 || is_cow_into_owned(cx, method_name, method_def_id);
129 if let Some(receiver_snippet) = snippet_opt(cx, receiver.span);
131 if receiver_ty == target_ty && n_target_refs >= n_receiver_refs {
134 UNNECESSARY_TO_OWNED,
136 &format!("unnecessary use of `{}`", method_name),
142 width = n_target_refs - n_receiver_refs
144 Applicability::MachineApplicable,
149 if let Some(deref_trait_id) = cx.tcx.get_diagnostic_item(sym::Deref);
150 if implements_trait(cx, receiver_ty, deref_trait_id, &[]);
151 if get_associated_type(cx, receiver_ty, deref_trait_id, "Target") == Some(target_ty);
153 if n_receiver_refs > 0 {
156 UNNECESSARY_TO_OWNED,
158 &format!("unnecessary use of `{}`", method_name),
161 Applicability::MachineApplicable,
166 UNNECESSARY_TO_OWNED,
167 expr.span.with_lo(receiver.span.hi()),
168 &format!("unnecessary use of `{}`", method_name),
171 Applicability::MachineApplicable,
178 if let Some(as_ref_trait_id) = cx.tcx.get_diagnostic_item(sym::AsRef);
179 if implements_trait(cx, receiver_ty, as_ref_trait_id, &[GenericArg::from(target_ty)]);
183 UNNECESSARY_TO_OWNED,
185 &format!("unnecessary use of `{}`", method_name),
187 format!("{}.as_ref()", receiver_snippet),
188 Applicability::MachineApplicable,
198 /// Checks whether `expr` is an argument in an `into_iter` call and, if so, determines whether its
199 /// call of a `to_owned`-like function is unnecessary.
200 fn check_into_iter_call_arg(
201 cx: &LateContext<'_>,
205 msrv: Option<RustcVersion>,
208 if let Some(parent) = get_parent_expr(cx, expr);
209 if let Some(callee_def_id) = fn_def_id(cx, parent);
210 if is_into_iter(cx, callee_def_id);
211 if let Some(iterator_trait_id) = cx.tcx.get_diagnostic_item(sym::Iterator);
212 let parent_ty = cx.typeck_results().expr_ty(parent);
213 if implements_trait(cx, parent_ty, iterator_trait_id, &[]);
214 if let Some(item_ty) = get_iterator_item_ty(cx, parent_ty);
215 if let Some(receiver_snippet) = snippet_opt(cx, receiver.span);
217 if unnecessary_iter_cloned::check_for_loop_iter(cx, parent, method_name, receiver, true) {
220 let cloned_or_copied = if is_copy(cx, item_ty) && meets_msrv(msrv, msrvs::ITERATOR_COPIED) {
225 // The next suggestion may be incorrect because the removal of the `to_owned`-like
226 // function could cause the iterator to hold a reference to a resource that is used
227 // mutably. See https://github.com/rust-lang/rust-clippy/issues/8148.
230 UNNECESSARY_TO_OWNED,
232 &format!("unnecessary use of `{}`", method_name),
234 format!("{}.iter().{}()", receiver_snippet, cloned_or_copied),
235 Applicability::MaybeIncorrect,
243 /// Checks whether `expr` is an argument in a function call and, if so, determines whether its call
244 /// of a `to_owned`-like function is unnecessary.
245 fn check_other_call_arg<'tcx>(
246 cx: &LateContext<'tcx>,
247 expr: &'tcx Expr<'tcx>,
249 receiver: &'tcx Expr<'tcx>,
252 if let Some((maybe_call, maybe_arg)) = skip_addr_of_ancestors(cx, expr);
253 if let Some((callee_def_id, _, recv, call_args)) = get_callee_substs_and_args(cx, maybe_call);
254 let fn_sig = cx.tcx.fn_sig(callee_def_id).skip_binder();
255 if let Some(i) = recv.into_iter().chain(call_args).position(|arg| arg.hir_id == maybe_arg.hir_id);
256 if let Some(input) = fn_sig.inputs().get(i);
257 let (input, n_refs) = peel_mid_ty_refs(*input);
258 if let (trait_predicates, _) = get_input_traits_and_projections(cx, callee_def_id, input);
259 if let Some(sized_def_id) = cx.tcx.lang_items().sized_trait();
260 if let [trait_predicate] = trait_predicates
262 .filter(|trait_predicate| trait_predicate.def_id() != sized_def_id)
263 .collect::<Vec<_>>()[..];
264 if let Some(deref_trait_id) = cx.tcx.get_diagnostic_item(sym::Deref);
265 if let Some(as_ref_trait_id) = cx.tcx.get_diagnostic_item(sym::AsRef);
266 if trait_predicate.def_id() == deref_trait_id || trait_predicate.def_id() == as_ref_trait_id;
267 let receiver_ty = cx.typeck_results().expr_ty(receiver);
268 if can_change_type(cx, maybe_arg, receiver_ty);
269 // We can't add an `&` when the trait is `Deref` because `Target = &T` won't match
271 if n_refs > 0 || is_copy(cx, receiver_ty) || trait_predicate.def_id() != deref_trait_id;
272 let n_refs = max(n_refs, if is_copy(cx, receiver_ty) { 0 } else { 1 });
273 if let Some(receiver_snippet) = snippet_opt(cx, receiver.span);
277 UNNECESSARY_TO_OWNED,
279 &format!("unnecessary use of `{}`", method_name),
281 format!("{:&>width$}{}", "", receiver_snippet, width = n_refs),
282 Applicability::MachineApplicable,
290 /// Walks an expression's ancestors until it finds a non-`AddrOf` expression. Returns the first such
291 /// expression found (if any) along with the immediately prior expression.
292 fn skip_addr_of_ancestors<'tcx>(
293 cx: &LateContext<'tcx>,
294 mut expr: &'tcx Expr<'tcx>,
295 ) -> Option<(&'tcx Expr<'tcx>, &'tcx Expr<'tcx>)> {
296 while let Some(parent) = get_parent_expr(cx, expr) {
297 if let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, _) = parent.kind {
300 return Some((parent, expr));
306 /// Checks whether an expression is a function or method call and, if so, returns its `DefId`,
307 /// `Substs`, and arguments.
308 fn get_callee_substs_and_args<'tcx>(
309 cx: &LateContext<'tcx>,
310 expr: &'tcx Expr<'tcx>,
311 ) -> Option<(DefId, SubstsRef<'tcx>, Option<&'tcx Expr<'tcx>>, &'tcx [Expr<'tcx>])> {
313 if let ExprKind::Call(callee, args) = expr.kind;
314 let callee_ty = cx.typeck_results().expr_ty(callee);
315 if let ty::FnDef(callee_def_id, _) = callee_ty.kind();
317 let substs = cx.typeck_results().node_substs(callee.hir_id);
318 return Some((*callee_def_id, substs, None, args));
322 if let ExprKind::MethodCall(_, recv, args, _) = expr.kind;
323 if let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id);
325 let substs = cx.typeck_results().node_substs(expr.hir_id);
326 return Some((method_def_id, substs, Some(recv), args));
332 /// Returns the `TraitPredicate`s and `ProjectionPredicate`s for a function's input type.
333 fn get_input_traits_and_projections<'tcx>(
334 cx: &LateContext<'tcx>,
335 callee_def_id: DefId,
337 ) -> (Vec<TraitPredicate<'tcx>>, Vec<ProjectionPredicate<'tcx>>) {
338 let mut trait_predicates = Vec::new();
339 let mut projection_predicates = Vec::new();
340 for predicate in cx.tcx.param_env(callee_def_id).caller_bounds() {
341 match predicate.kind().skip_binder() {
342 PredicateKind::Trait(trait_predicate) => {
343 if trait_predicate.trait_ref.self_ty() == input {
344 trait_predicates.push(trait_predicate);
347 PredicateKind::Projection(projection_predicate) => {
348 if projection_predicate.projection_ty.self_ty() == input {
349 projection_predicates.push(projection_predicate);
355 (trait_predicates, projection_predicates)
358 fn can_change_type<'a>(cx: &LateContext<'a>, mut expr: &'a Expr<'a>, mut ty: Ty<'a>) -> bool {
359 for (_, node) in cx.tcx.hir().parent_iter(expr.hir_id) {
361 Node::Stmt(_) => return true,
362 Node::Block(..) => continue,
363 Node::Item(item) => {
364 if let ItemKind::Fn(_, _, body_id) = &item.kind
365 && let output_ty = return_ty(cx, item.hir_id())
366 && let local_def_id = cx.tcx.hir().local_def_id(item.hir_id())
367 && Inherited::build(cx.tcx, local_def_id).enter(|inherited| {
368 let fn_ctxt = FnCtxt::new(&inherited, cx.param_env, item.hir_id());
369 fn_ctxt.can_coerce(ty, output_ty)
371 if has_lifetime(output_ty) && has_lifetime(ty) {
374 let body = cx.tcx.hir().body(*body_id);
375 let body_expr = &body.value;
377 return find_all_ret_expressions(cx, body_expr, |_| { count += 1; count <= 1 });
380 Node::Expr(parent_expr) => {
381 if let Some((callee_def_id, call_substs, recv, call_args)) = get_callee_substs_and_args(cx, parent_expr)
383 let fn_sig = cx.tcx.fn_sig(callee_def_id).skip_binder();
384 if let Some(arg_index) = recv.into_iter().chain(call_args).position(|arg| arg.hir_id == expr.hir_id)
385 && let Some(param_ty) = fn_sig.inputs().get(arg_index)
386 && let ty::Param(ParamTy { index: param_index , ..}) = param_ty.kind()
392 .filter(|(i, _)| *i != arg_index)
393 .any(|(_, ty)| ty.contains(*param_ty))
398 let mut trait_predicates = cx.tcx.param_env(callee_def_id)
399 .caller_bounds().iter().filter(|predicate| {
400 if let PredicateKind::Trait(trait_predicate) = predicate.kind().skip_binder()
401 && trait_predicate.trait_ref.self_ty() == *param_ty {
408 let new_subst = cx.tcx.mk_substs(
412 if i == (*param_index as usize) {
418 if trait_predicates.any(|predicate| {
419 let predicate = EarlyBinder(predicate).subst(cx.tcx, new_subst);
420 let obligation = Obligation::new(ObligationCause::dummy(), cx.param_env, predicate);
423 .enter(|infcx| infcx.predicate_must_hold_modulo_regions(&obligation))
428 let output_ty = fn_sig.output();
429 if output_ty.contains(*param_ty) {
430 if let Ok(new_ty) = cx.tcx.try_subst_and_normalize_erasing_regions(
431 new_subst, cx.param_env, output_ty) {
441 } else if let ExprKind::Block(..) = parent_expr.kind {
453 fn has_lifetime(ty: Ty<'_>) -> bool {
454 ty.walk().any(|t| matches!(t.unpack(), GenericArgKind::Lifetime(_)))
457 /// Returns true if the named method is `Iterator::cloned` or `Iterator::copied`.
458 fn is_cloned_or_copied(cx: &LateContext<'_>, method_name: Symbol, method_def_id: DefId) -> bool {
459 (method_name.as_str() == "cloned" || method_name.as_str() == "copied")
460 && is_diag_trait_item(cx, method_def_id, sym::Iterator)
463 /// Returns true if the named method can be used to convert the receiver to its "owned"
465 fn is_to_owned_like<'a>(cx: &LateContext<'a>, call_expr: &Expr<'a>, method_name: Symbol, method_def_id: DefId) -> bool {
466 is_clone_like(cx, method_name.as_str(), method_def_id)
467 || is_cow_into_owned(cx, method_name, method_def_id)
468 || is_to_string_on_string_like(cx, call_expr, method_name, method_def_id)
471 /// Returns true if the named method is `Cow::into_owned`.
472 fn is_cow_into_owned(cx: &LateContext<'_>, method_name: Symbol, method_def_id: DefId) -> bool {
473 method_name.as_str() == "into_owned" && is_diag_item_method(cx, method_def_id, sym::Cow)
476 /// Returns true if the named method is `ToString::to_string` and it's called on a type that
477 /// is string-like i.e. implements `AsRef<str>` or `Deref<str>`.
478 fn is_to_string_on_string_like<'a>(
479 cx: &LateContext<'_>,
480 call_expr: &'a Expr<'a>,
482 method_def_id: DefId,
484 if method_name != sym::to_string || !is_diag_trait_item(cx, method_def_id, sym::ToString) {
488 if let Some(substs) = cx.typeck_results().node_substs_opt(call_expr.hir_id)
489 && let [generic_arg] = substs.as_slice()
490 && let GenericArgKind::Type(ty) = generic_arg.unpack()
491 && let Some(deref_trait_id) = cx.tcx.get_diagnostic_item(sym::Deref)
492 && let Some(as_ref_trait_id) = cx.tcx.get_diagnostic_item(sym::AsRef)
493 && (implements_trait(cx, ty, deref_trait_id, &[cx.tcx.types.str_.into()]) ||
494 implements_trait(cx, ty, as_ref_trait_id, &[cx.tcx.types.str_.into()])) {