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