]> git.lizzy.rs Git - rust.git/blob - src/tools/clippy/clippy_lints/src/methods/unnecessary_to_owned.rs
Auto merge of #89123 - the8472:push_in_capacity, r=amanieu
[rust.git] / src / tools / clippy / clippy_lints / src / methods / unnecessary_to_owned.rs
1 use super::implicit_clone::is_clone_like;
2 use super::unnecessary_iter_cloned::{self, is_into_iter};
3 use clippy_utils::diagnostics::span_lint_and_sugg;
4 use clippy_utils::source::snippet_opt;
5 use clippy_utils::ty::{get_associated_type, get_iterator_item_ty, implements_trait, is_copy, peel_mid_ty_refs};
6 use clippy_utils::visitors::find_all_ret_expressions;
7 use clippy_utils::{fn_def_id, get_parent_expr, is_diag_item_method, is_diag_trait_item, return_ty};
8 use clippy_utils::{meets_msrv, msrvs};
9 use rustc_errors::Applicability;
10 use rustc_hir::{def_id::DefId, BorrowKind, Expr, ExprKind, ItemKind, LangItem, Node};
11 use rustc_hir_analysis::check::{FnCtxt, Inherited};
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 std::cmp::max;
23
24 use super::UNNECESSARY_TO_OWNED;
25
26 pub fn check<'tcx>(
27     cx: &LateContext<'tcx>,
28     expr: &'tcx Expr<'tcx>,
29     method_name: Symbol,
30     receiver: &'tcx Expr<'_>,
31     args: &'tcx [Expr<'_>],
32     msrv: Option<RustcVersion>,
33 ) {
34     if_chain! {
35         if let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id);
36         if args.is_empty();
37         then {
38             if is_cloned_or_copied(cx, method_name, method_def_id) {
39                 unnecessary_iter_cloned::check(cx, expr, method_name, receiver);
40             } else if is_to_owned_like(cx, expr, method_name, method_def_id) {
41                 // At this point, we know the call is of a `to_owned`-like function. The functions
42                 // `check_addr_of_expr` and `check_call_arg` determine whether the call is unnecessary
43                 // based on its context, that is, whether it is a referent in an `AddrOf` expression, an
44                 // argument in a `into_iter` call, or an argument in the call of some other function.
45                 if check_addr_of_expr(cx, expr, method_name, method_def_id, receiver) {
46                     return;
47                 }
48                 if check_into_iter_call_arg(cx, expr, method_name, receiver, msrv) {
49                     return;
50                 }
51                 check_other_call_arg(cx, expr, method_name, receiver);
52             }
53         }
54     }
55 }
56
57 /// Checks whether `expr` is a referent in an `AddrOf` expression and, if so, determines whether its
58 /// call of a `to_owned`-like function is unnecessary.
59 #[allow(clippy::too_many_lines)]
60 fn check_addr_of_expr(
61     cx: &LateContext<'_>,
62     expr: &Expr<'_>,
63     method_name: Symbol,
64     method_def_id: DefId,
65     receiver: &Expr<'_>,
66 ) -> bool {
67     if_chain! {
68         if let Some(parent) = get_parent_expr(cx, expr);
69         if let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, _) = parent.kind;
70         let adjustments = cx.typeck_results().expr_adjustments(parent).iter().collect::<Vec<_>>();
71         if let
72             // For matching uses of `Cow::from`
73             [
74                 Adjustment {
75                     kind: Adjust::Deref(None),
76                     target: referent_ty,
77                 },
78                 Adjustment {
79                     kind: Adjust::Borrow(_),
80                     target: target_ty,
81                 },
82             ]
83             // For matching uses of arrays
84             | [
85                 Adjustment {
86                     kind: Adjust::Deref(None),
87                     target: referent_ty,
88                 },
89                 Adjustment {
90                     kind: Adjust::Borrow(_),
91                     ..
92                 },
93                 Adjustment {
94                     kind: Adjust::Pointer(_),
95                     target: target_ty,
96                 },
97             ]
98             // For matching everything else
99             | [
100                 Adjustment {
101                     kind: Adjust::Deref(None),
102                     target: referent_ty,
103                 },
104                 Adjustment {
105                     kind: Adjust::Deref(Some(OverloadedDeref { .. })),
106                     ..
107                 },
108                 Adjustment {
109                     kind: Adjust::Borrow(_),
110                     target: target_ty,
111                 },
112             ] = adjustments[..];
113         let receiver_ty = cx.typeck_results().expr_ty(receiver);
114         let (target_ty, n_target_refs) = peel_mid_ty_refs(*target_ty);
115         let (receiver_ty, n_receiver_refs) = peel_mid_ty_refs(receiver_ty);
116         // Only flag cases satisfying at least one of the following three conditions:
117         // * the referent and receiver types are distinct
118         // * the referent/receiver type is a copyable array
119         // * the method is `Cow::into_owned`
120         // This restriction is to ensure there is no overlap between `redundant_clone` and this
121         // lint. It also avoids the following false positive:
122         //  https://github.com/rust-lang/rust-clippy/issues/8759
123         //   Arrays are a bit of a corner case. Non-copyable arrays are handled by
124         // `redundant_clone`, but copyable arrays are not.
125         if *referent_ty != receiver_ty
126             || (matches!(referent_ty.kind(), ty::Array(..)) && is_copy(cx, *referent_ty))
127             || is_cow_into_owned(cx, method_name, method_def_id);
128         if let Some(receiver_snippet) = snippet_opt(cx, receiver.span);
129         then {
130             if receiver_ty == target_ty && n_target_refs >= n_receiver_refs {
131                 span_lint_and_sugg(
132                     cx,
133                     UNNECESSARY_TO_OWNED,
134                     parent.span,
135                     &format!("unnecessary use of `{method_name}`"),
136                     "use",
137                     format!(
138                         "{:&>width$}{receiver_snippet}",
139                         "",
140                         width = n_target_refs - n_receiver_refs
141                     ),
142                     Applicability::MachineApplicable,
143                 );
144                 return true;
145             }
146             if_chain! {
147                 if let Some(deref_trait_id) = cx.tcx.get_diagnostic_item(sym::Deref);
148                 if implements_trait(cx, receiver_ty, deref_trait_id, &[]);
149                 if get_associated_type(cx, receiver_ty, deref_trait_id, "Target") == Some(target_ty);
150                 then {
151                     if n_receiver_refs > 0 {
152                         span_lint_and_sugg(
153                             cx,
154                             UNNECESSARY_TO_OWNED,
155                             parent.span,
156                             &format!("unnecessary use of `{method_name}`"),
157                             "use",
158                             receiver_snippet,
159                             Applicability::MachineApplicable,
160                         );
161                     } else {
162                         span_lint_and_sugg(
163                             cx,
164                             UNNECESSARY_TO_OWNED,
165                             expr.span.with_lo(receiver.span.hi()),
166                             &format!("unnecessary use of `{method_name}`"),
167                             "remove this",
168                             String::new(),
169                             Applicability::MachineApplicable,
170                         );
171                     }
172                     return true;
173                 }
174             }
175             if_chain! {
176                 if let Some(as_ref_trait_id) = cx.tcx.get_diagnostic_item(sym::AsRef);
177                 if implements_trait(cx, receiver_ty, as_ref_trait_id, &[GenericArg::from(target_ty)]);
178                 then {
179                     span_lint_and_sugg(
180                         cx,
181                         UNNECESSARY_TO_OWNED,
182                         parent.span,
183                         &format!("unnecessary use of `{method_name}`"),
184                         "use",
185                         format!("{receiver_snippet}.as_ref()"),
186                         Applicability::MachineApplicable,
187                     );
188                     return true;
189                 }
190             }
191         }
192     }
193     false
194 }
195
196 /// Checks whether `expr` is an argument in an `into_iter` call and, if so, determines whether its
197 /// call of a `to_owned`-like function is unnecessary.
198 fn check_into_iter_call_arg(
199     cx: &LateContext<'_>,
200     expr: &Expr<'_>,
201     method_name: Symbol,
202     receiver: &Expr<'_>,
203     msrv: Option<RustcVersion>,
204 ) -> bool {
205     if_chain! {
206         if let Some(parent) = get_parent_expr(cx, expr);
207         if let Some(callee_def_id) = fn_def_id(cx, parent);
208         if is_into_iter(cx, callee_def_id);
209         if let Some(iterator_trait_id) = cx.tcx.get_diagnostic_item(sym::Iterator);
210         let parent_ty = cx.typeck_results().expr_ty(parent);
211         if implements_trait(cx, parent_ty, iterator_trait_id, &[]);
212         if let Some(item_ty) = get_iterator_item_ty(cx, parent_ty);
213         if let Some(receiver_snippet) = snippet_opt(cx, receiver.span);
214         then {
215             if unnecessary_iter_cloned::check_for_loop_iter(cx, parent, method_name, receiver, true) {
216                 return true;
217             }
218             let cloned_or_copied = if is_copy(cx, item_ty) && meets_msrv(msrv, msrvs::ITERATOR_COPIED) {
219                 "copied"
220             } else {
221                 "cloned"
222             };
223             // The next suggestion may be incorrect because the removal of the `to_owned`-like
224             // function could cause the iterator to hold a reference to a resource that is used
225             // mutably. See https://github.com/rust-lang/rust-clippy/issues/8148.
226             span_lint_and_sugg(
227                 cx,
228                 UNNECESSARY_TO_OWNED,
229                 parent.span,
230                 &format!("unnecessary use of `{method_name}`"),
231                 "use",
232                 format!("{receiver_snippet}.iter().{cloned_or_copied}()"),
233                 Applicability::MaybeIncorrect,
234             );
235             return true;
236         }
237     }
238     false
239 }
240
241 /// Checks whether `expr` is an argument in a function call and, if so, determines whether its call
242 /// of a `to_owned`-like function is unnecessary.
243 fn check_other_call_arg<'tcx>(
244     cx: &LateContext<'tcx>,
245     expr: &'tcx Expr<'tcx>,
246     method_name: Symbol,
247     receiver: &'tcx Expr<'tcx>,
248 ) -> bool {
249     if_chain! {
250         if let Some((maybe_call, maybe_arg)) = skip_addr_of_ancestors(cx, expr);
251         if let Some((callee_def_id, _, recv, call_args)) = get_callee_substs_and_args(cx, maybe_call);
252         let fn_sig = cx.tcx.fn_sig(callee_def_id).skip_binder();
253         if let Some(i) = recv.into_iter().chain(call_args).position(|arg| arg.hir_id == maybe_arg.hir_id);
254         if let Some(input) = fn_sig.inputs().get(i);
255         let (input, n_refs) = peel_mid_ty_refs(*input);
256         if let (trait_predicates, _) = get_input_traits_and_projections(cx, callee_def_id, input);
257         if let Some(sized_def_id) = cx.tcx.lang_items().sized_trait();
258         if let [trait_predicate] = trait_predicates
259             .iter()
260             .filter(|trait_predicate| trait_predicate.def_id() != sized_def_id)
261             .collect::<Vec<_>>()[..];
262         if let Some(deref_trait_id) = cx.tcx.get_diagnostic_item(sym::Deref);
263         if let Some(as_ref_trait_id) = cx.tcx.get_diagnostic_item(sym::AsRef);
264         if trait_predicate.def_id() == deref_trait_id || trait_predicate.def_id() == as_ref_trait_id;
265         let receiver_ty = cx.typeck_results().expr_ty(receiver);
266         if can_change_type(cx, maybe_arg, receiver_ty);
267         // We can't add an `&` when the trait is `Deref` because `Target = &T` won't match
268         // `Target = T`.
269         if n_refs > 0 || is_copy(cx, receiver_ty) || trait_predicate.def_id() != deref_trait_id;
270         let n_refs = max(n_refs, usize::from(!is_copy(cx, receiver_ty)));
271         if let Some(receiver_snippet) = snippet_opt(cx, receiver.span);
272         then {
273             span_lint_and_sugg(
274                 cx,
275                 UNNECESSARY_TO_OWNED,
276                 maybe_arg.span,
277                 &format!("unnecessary use of `{method_name}`"),
278                 "use",
279                 format!("{:&>n_refs$}{receiver_snippet}", ""),
280                 Applicability::MachineApplicable,
281             );
282             return true;
283         }
284     }
285     false
286 }
287
288 /// Walks an expression's ancestors until it finds a non-`AddrOf` expression. Returns the first such
289 /// expression found (if any) along with the immediately prior expression.
290 fn skip_addr_of_ancestors<'tcx>(
291     cx: &LateContext<'tcx>,
292     mut expr: &'tcx Expr<'tcx>,
293 ) -> Option<(&'tcx Expr<'tcx>, &'tcx Expr<'tcx>)> {
294     while let Some(parent) = get_parent_expr(cx, expr) {
295         if let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, _) = parent.kind {
296             expr = parent;
297         } else {
298             return Some((parent, expr));
299         }
300     }
301     None
302 }
303
304 /// Checks whether an expression is a function or method call and, if so, returns its `DefId`,
305 /// `Substs`, and arguments.
306 fn get_callee_substs_and_args<'tcx>(
307     cx: &LateContext<'tcx>,
308     expr: &'tcx Expr<'tcx>,
309 ) -> Option<(DefId, SubstsRef<'tcx>, Option<&'tcx Expr<'tcx>>, &'tcx [Expr<'tcx>])> {
310     if_chain! {
311         if let ExprKind::Call(callee, args) = expr.kind;
312         let callee_ty = cx.typeck_results().expr_ty(callee);
313         if let ty::FnDef(callee_def_id, _) = callee_ty.kind();
314         then {
315             let substs = cx.typeck_results().node_substs(callee.hir_id);
316             return Some((*callee_def_id, substs, None, args));
317         }
318     }
319     if_chain! {
320         if let ExprKind::MethodCall(_, recv, args, _) = expr.kind;
321         if let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id);
322         then {
323             let substs = cx.typeck_results().node_substs(expr.hir_id);
324             return Some((method_def_id, substs, Some(recv), args));
325         }
326     }
327     None
328 }
329
330 /// Returns the `TraitPredicate`s and `ProjectionPredicate`s for a function's input type.
331 fn get_input_traits_and_projections<'tcx>(
332     cx: &LateContext<'tcx>,
333     callee_def_id: DefId,
334     input: Ty<'tcx>,
335 ) -> (Vec<TraitPredicate<'tcx>>, Vec<ProjectionPredicate<'tcx>>) {
336     let mut trait_predicates = Vec::new();
337     let mut projection_predicates = Vec::new();
338     for predicate in cx.tcx.param_env(callee_def_id).caller_bounds() {
339         match predicate.kind().skip_binder() {
340             PredicateKind::Trait(trait_predicate) => {
341                 if trait_predicate.trait_ref.self_ty() == input {
342                     trait_predicates.push(trait_predicate);
343                 }
344             },
345             PredicateKind::Projection(projection_predicate) => {
346                 if projection_predicate.projection_ty.self_ty() == input {
347                     projection_predicates.push(projection_predicate);
348                 }
349             },
350             _ => {},
351         }
352     }
353     (trait_predicates, projection_predicates)
354 }
355
356 fn can_change_type<'a>(cx: &LateContext<'a>, mut expr: &'a Expr<'a>, mut ty: Ty<'a>) -> bool {
357     for (_, node) in cx.tcx.hir().parent_iter(expr.hir_id) {
358         match node {
359             Node::Stmt(_) => return true,
360             Node::Block(..) => continue,
361             Node::Item(item) => {
362                 if let ItemKind::Fn(_, _, body_id) = &item.kind
363                 && let output_ty = return_ty(cx, item.hir_id())
364                 && let local_def_id = cx.tcx.hir().local_def_id(item.hir_id())
365                 && Inherited::build(cx.tcx, local_def_id).enter(|inherited| {
366                     let fn_ctxt = FnCtxt::new(&inherited, cx.param_env, item.hir_id());
367                     fn_ctxt.can_coerce(ty, output_ty)
368                 }) {
369                     if has_lifetime(output_ty) && has_lifetime(ty) {
370                         return false;
371                     }
372                     let body = cx.tcx.hir().body(*body_id);
373                     let body_expr = &body.value;
374                     let mut count = 0;
375                     return find_all_ret_expressions(cx, body_expr, |_| { count += 1; count <= 1 });
376                 }
377             }
378             Node::Expr(parent_expr) => {
379                 if let Some((callee_def_id, call_substs, recv, call_args)) = get_callee_substs_and_args(cx, parent_expr)
380                 {
381                     if cx.tcx.lang_items().require(LangItem::IntoFutureIntoFuture) == Ok(callee_def_id) {
382                         return false;
383                     }
384
385                     let fn_sig = cx.tcx.fn_sig(callee_def_id).skip_binder();
386                     if let Some(arg_index) = recv.into_iter().chain(call_args).position(|arg| arg.hir_id == expr.hir_id)
387                         && let Some(param_ty) = fn_sig.inputs().get(arg_index)
388                         && let ty::Param(ParamTy { index: param_index , ..}) = param_ty.kind()
389                     {
390                         if fn_sig
391                             .inputs()
392                             .iter()
393                             .enumerate()
394                             .filter(|(i, _)| *i != arg_index)
395                             .any(|(_, ty)| ty.contains(*param_ty))
396                         {
397                             return false;
398                         }
399
400                         let mut trait_predicates = cx.tcx.param_env(callee_def_id)
401                             .caller_bounds().iter().filter(|predicate| {
402                             if let PredicateKind::Trait(trait_predicate) =  predicate.kind().skip_binder()
403                                 && trait_predicate.trait_ref.self_ty() == *param_ty {
404                                     true
405                                 } else {
406                                 false
407                             }
408                         });
409
410                         let new_subst = cx.tcx.mk_substs(
411                             call_substs.iter()
412                                 .enumerate()
413                                 .map(|(i, t)|
414                                      if i == (*param_index as usize) {
415                                          GenericArg::from(ty)
416                                      } else {
417                                          t
418                                      }));
419
420                         if trait_predicates.any(|predicate| {
421                             let predicate = EarlyBinder(predicate).subst(cx.tcx, new_subst);
422                             let obligation = Obligation::new(ObligationCause::dummy(), cx.param_env, predicate);
423                             !cx.tcx.infer_ctxt().build().predicate_must_hold_modulo_regions(&obligation)
424                         }) {
425                             return false;
426                         }
427
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) {
432                                 expr = parent_expr;
433                                 ty = new_ty;
434                                 continue;
435                             }
436                             return false;
437                         }
438
439                         return true;
440                     }
441                 } else if let ExprKind::Block(..) = parent_expr.kind {
442                     continue;
443                 }
444                 return false;
445             },
446             _ => return false,
447         }
448     }
449
450     false
451 }
452
453 fn has_lifetime(ty: Ty<'_>) -> bool {
454     ty.walk().any(|t| matches!(t.unpack(), GenericArgKind::Lifetime(_)))
455 }
456
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)
461 }
462
463 /// Returns true if the named method can be used to convert the receiver to its "owned"
464 /// representation.
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)
469 }
470
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)
474 }
475
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>,
481     method_name: Symbol,
482     method_def_id: DefId,
483 ) -> bool {
484     if method_name != sym::to_string || !is_diag_trait_item(cx, method_def_id, sym::ToString) {
485         return false;
486     }
487
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()])) {
495             true
496         } else {
497             false
498         }
499 }