]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/methods/unnecessary_to_owned.rs
Auto merge of #91359 - dtolnay:args, r=Mark-Simulacrum
[rust.git] / 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::{fn_def_id, get_parent_expr, is_diag_item_method, is_diag_trait_item};
7 use rustc_errors::Applicability;
8 use rustc_hir::{def_id::DefId, BorrowKind, Expr, ExprKind};
9 use rustc_lint::LateContext;
10 use rustc_middle::mir::Mutability;
11 use rustc_middle::ty::adjustment::{Adjust, Adjustment, OverloadedDeref};
12 use rustc_middle::ty::subst::{GenericArg, GenericArgKind, SubstsRef};
13 use rustc_middle::ty::{self, PredicateKind, ProjectionPredicate, TraitPredicate, Ty};
14 use rustc_span::{sym, Symbol};
15 use std::cmp::max;
16
17 use super::UNNECESSARY_TO_OWNED;
18
19 pub fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, method_name: Symbol, args: &'tcx [Expr<'tcx>]) {
20     if_chain! {
21         if let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id);
22         if let [receiver] = args;
23         then {
24             if is_cloned_or_copied(cx, method_name, method_def_id) {
25                 unnecessary_iter_cloned::check(cx, expr, method_name, receiver);
26             } else if is_to_owned_like(cx, method_name, method_def_id) {
27                 // At this point, we know the call is of a `to_owned`-like function. The functions
28                 // `check_addr_of_expr` and `check_call_arg` determine whether the call is unnecessary
29                 // based on its context, that is, whether it is a referent in an `AddrOf` expression, an
30                 // argument in a `into_iter` call, or an argument in the call of some other function.
31                 if check_addr_of_expr(cx, expr, method_name, method_def_id, receiver) {
32                     return;
33                 }
34                 if check_into_iter_call_arg(cx, expr, method_name, receiver) {
35                     return;
36                 }
37                 check_other_call_arg(cx, expr, method_name, receiver);
38             }
39         }
40     }
41 }
42
43 /// Checks whether `expr` is a referent in an `AddrOf` expression and, if so, determines whether its
44 /// call of a `to_owned`-like function is unnecessary.
45 #[allow(clippy::too_many_lines)]
46 fn check_addr_of_expr(
47     cx: &LateContext<'_>,
48     expr: &Expr<'_>,
49     method_name: Symbol,
50     method_def_id: DefId,
51     receiver: &Expr<'_>,
52 ) -> bool {
53     if_chain! {
54         if let Some(parent) = get_parent_expr(cx, expr);
55         if let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, _) = parent.kind;
56         let adjustments = cx.typeck_results().expr_adjustments(parent).iter().collect::<Vec<_>>();
57         if let Some(target_ty) = match adjustments[..]
58         {
59             // For matching uses of `Cow::from`
60             [
61                 Adjustment {
62                     kind: Adjust::Deref(None),
63                     ..
64                 },
65                 Adjustment {
66                     kind: Adjust::Borrow(_),
67                     target: target_ty,
68                 },
69             ]
70             // For matching uses of arrays
71             | [
72                 Adjustment {
73                     kind: Adjust::Deref(None),
74                     ..
75                 },
76                 Adjustment {
77                     kind: Adjust::Borrow(_),
78                     ..
79                 },
80                 Adjustment {
81                     kind: Adjust::Pointer(_),
82                     target: target_ty,
83                 },
84             ]
85             // For matching everything else
86             | [
87                 Adjustment {
88                     kind: Adjust::Deref(None),
89                     ..
90                 },
91                 Adjustment {
92                     kind: Adjust::Deref(Some(OverloadedDeref { .. })),
93                     ..
94                 },
95                 Adjustment {
96                     kind: Adjust::Borrow(_),
97                     target: target_ty,
98                 },
99             ] => Some(target_ty),
100             _ => None,
101         };
102         let receiver_ty = cx.typeck_results().expr_ty(receiver);
103         // Only flag cases where the receiver is copyable or the method is `Cow::into_owned`. This
104         // restriction is to ensure there is not overlap between `redundant_clone` and this lint.
105         if is_copy(cx, receiver_ty) || is_cow_into_owned(cx, method_name, method_def_id);
106         if let Some(receiver_snippet) = snippet_opt(cx, receiver.span);
107         then {
108             let (target_ty, n_target_refs) = peel_mid_ty_refs(target_ty);
109             let (receiver_ty, n_receiver_refs) = peel_mid_ty_refs(receiver_ty);
110             if receiver_ty == target_ty && n_target_refs >= n_receiver_refs {
111                 span_lint_and_sugg(
112                     cx,
113                     UNNECESSARY_TO_OWNED,
114                     parent.span,
115                     &format!("unnecessary use of `{}`", method_name),
116                     "use",
117                     format!("{:&>width$}{}", "", receiver_snippet, width = n_target_refs - n_receiver_refs),
118                     Applicability::MachineApplicable,
119                 );
120                 return true;
121             }
122             if_chain! {
123                 if let Some(deref_trait_id) = cx.tcx.get_diagnostic_item(sym::Deref);
124                 if implements_trait(cx, receiver_ty, deref_trait_id, &[]);
125                 if get_associated_type(cx, receiver_ty, deref_trait_id, "Target") == Some(target_ty);
126                 then {
127                     if n_receiver_refs > 0 {
128                         span_lint_and_sugg(
129                             cx,
130                             UNNECESSARY_TO_OWNED,
131                             parent.span,
132                             &format!("unnecessary use of `{}`", method_name),
133                             "use",
134                             receiver_snippet,
135                             Applicability::MachineApplicable,
136                         );
137                     } else {
138                         span_lint_and_sugg(
139                             cx,
140                             UNNECESSARY_TO_OWNED,
141                             expr.span.with_lo(receiver.span.hi()),
142                             &format!("unnecessary use of `{}`", method_name),
143                             "remove this",
144                             String::new(),
145                             Applicability::MachineApplicable,
146                         );
147                     }
148                     return true;
149                 }
150             }
151             if_chain! {
152                 if let Some(as_ref_trait_id) = cx.tcx.get_diagnostic_item(sym::AsRef);
153                 if implements_trait(cx, receiver_ty, as_ref_trait_id, &[GenericArg::from(target_ty)]);
154                 then {
155                     span_lint_and_sugg(
156                         cx,
157                         UNNECESSARY_TO_OWNED,
158                         parent.span,
159                         &format!("unnecessary use of `{}`", method_name),
160                         "use",
161                         format!("{}.as_ref()", receiver_snippet),
162                         Applicability::MachineApplicable,
163                     );
164                     return true;
165                 }
166             }
167         }
168     }
169     false
170 }
171
172 /// Checks whether `expr` is an argument in an `into_iter` call and, if so, determines whether its
173 /// call of a `to_owned`-like function is unnecessary.
174 fn check_into_iter_call_arg(cx: &LateContext<'_>, expr: &Expr<'_>, method_name: Symbol, receiver: &Expr<'_>) -> bool {
175     if_chain! {
176         if let Some(parent) = get_parent_expr(cx, expr);
177         if let Some(callee_def_id) = fn_def_id(cx, parent);
178         if is_into_iter(cx, callee_def_id);
179         if let Some(iterator_trait_id) = cx.tcx.get_diagnostic_item(sym::Iterator);
180         let parent_ty = cx.typeck_results().expr_ty(parent);
181         if implements_trait(cx, parent_ty, iterator_trait_id, &[]);
182         if let Some(item_ty) = get_iterator_item_ty(cx, parent_ty);
183         if let Some(receiver_snippet) = snippet_opt(cx, receiver.span);
184         then {
185             if unnecessary_iter_cloned::check_for_loop_iter(
186                 cx,
187                 parent,
188                 method_name,
189                 receiver,
190                 true,
191             ) {
192                 return true;
193             }
194             let cloned_or_copied = if is_copy(cx, item_ty) {
195                 "copied"
196             } else {
197                 "cloned"
198             };
199             // The next suggestion may be incorrect because the removal of the `to_owned`-like
200             // function could cause the iterator to hold a reference to a resource that is used
201             // mutably. See https://github.com/rust-lang/rust-clippy/issues/8148.
202             span_lint_and_sugg(
203                 cx,
204                 UNNECESSARY_TO_OWNED,
205                 parent.span,
206                 &format!("unnecessary use of `{}`", method_name),
207                 "use",
208                 format!("{}.iter().{}()", receiver_snippet, cloned_or_copied),
209                 Applicability::MaybeIncorrect,
210             );
211             return true;
212         }
213     }
214     false
215 }
216
217 /// Checks whether `expr` is an argument in a function call and, if so, determines whether its call
218 /// of a `to_owned`-like function is unnecessary.
219 fn check_other_call_arg<'tcx>(
220     cx: &LateContext<'tcx>,
221     expr: &'tcx Expr<'tcx>,
222     method_name: Symbol,
223     receiver: &'tcx Expr<'tcx>,
224 ) -> bool {
225     if_chain! {
226         if let Some((maybe_call, maybe_arg)) = skip_addr_of_ancestors(cx, expr);
227         if let Some((callee_def_id, call_substs, call_args)) = get_callee_substs_and_args(cx, maybe_call);
228         let fn_sig = cx.tcx.fn_sig(callee_def_id).skip_binder();
229         if let Some(i) = call_args.iter().position(|arg| arg.hir_id == maybe_arg.hir_id);
230         if let Some(input) = fn_sig.inputs().get(i);
231         let (input, n_refs) = peel_mid_ty_refs(input);
232         if let (trait_predicates, projection_predicates) = get_input_traits_and_projections(cx, callee_def_id, input);
233         if let Some(sized_def_id) = cx.tcx.lang_items().sized_trait();
234         if let [trait_predicate] = trait_predicates
235             .iter()
236             .filter(|trait_predicate| trait_predicate.def_id() != sized_def_id)
237             .collect::<Vec<_>>()[..];
238         if let Some(deref_trait_id) = cx.tcx.get_diagnostic_item(sym::Deref);
239         if let Some(as_ref_trait_id) = cx.tcx.get_diagnostic_item(sym::AsRef);
240         let receiver_ty = cx.typeck_results().expr_ty(receiver);
241         // If the callee has type parameters, they could appear in `projection_predicate.ty` or the
242         // types of `trait_predicate.trait_ref.substs`.
243         if if trait_predicate.def_id() == deref_trait_id {
244             if let [projection_predicate] = projection_predicates[..] {
245                 let normalized_ty =
246                     cx.tcx.subst_and_normalize_erasing_regions(call_substs, cx.param_env, projection_predicate.term);
247                 implements_trait(cx, receiver_ty, deref_trait_id, &[])
248                     && get_associated_type(cx, receiver_ty, deref_trait_id,
249                     "Target").map_or(false, |ty| ty::Term::Ty(ty) == normalized_ty)
250             } else {
251                 false
252             }
253         } else if trait_predicate.def_id() == as_ref_trait_id {
254             let composed_substs = compose_substs(
255                 cx,
256                 &trait_predicate.trait_ref.substs.iter().skip(1).collect::<Vec<_>>()[..],
257                 call_substs
258             );
259             implements_trait(cx, receiver_ty, as_ref_trait_id, &composed_substs)
260         } else {
261             false
262         };
263         // We can't add an `&` when the trait is `Deref` because `Target = &T` won't match
264         // `Target = T`.
265         if n_refs > 0 || is_copy(cx, receiver_ty) || trait_predicate.def_id() != deref_trait_id;
266         let n_refs = max(n_refs, if is_copy(cx, receiver_ty) { 0 } else { 1 });
267         if let Some(receiver_snippet) = snippet_opt(cx, receiver.span);
268         then {
269             span_lint_and_sugg(
270                 cx,
271                 UNNECESSARY_TO_OWNED,
272                 maybe_arg.span,
273                 &format!("unnecessary use of `{}`", method_name),
274                 "use",
275                 format!("{:&>width$}{}", "", receiver_snippet, width = n_refs),
276                 Applicability::MachineApplicable,
277             );
278             return true;
279         }
280     }
281     false
282 }
283
284 /// Walks an expression's ancestors until it finds a non-`AddrOf` expression. Returns the first such
285 /// expression found (if any) along with the immediately prior expression.
286 fn skip_addr_of_ancestors<'tcx>(
287     cx: &LateContext<'tcx>,
288     mut expr: &'tcx Expr<'tcx>,
289 ) -> Option<(&'tcx Expr<'tcx>, &'tcx Expr<'tcx>)> {
290     while let Some(parent) = get_parent_expr(cx, expr) {
291         if let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, _) = parent.kind {
292             expr = parent;
293         } else {
294             return Some((parent, expr));
295         }
296     }
297     None
298 }
299
300 /// Checks whether an expression is a function or method call and, if so, returns its `DefId`,
301 /// `Substs`, and arguments.
302 fn get_callee_substs_and_args<'tcx>(
303     cx: &LateContext<'tcx>,
304     expr: &'tcx Expr<'tcx>,
305 ) -> Option<(DefId, SubstsRef<'tcx>, &'tcx [Expr<'tcx>])> {
306     if_chain! {
307         if let ExprKind::Call(callee, args) = expr.kind;
308         let callee_ty = cx.typeck_results().expr_ty(callee);
309         if let ty::FnDef(callee_def_id, _) = callee_ty.kind();
310         then {
311             let substs = cx.typeck_results().node_substs(callee.hir_id);
312             return Some((*callee_def_id, substs, args));
313         }
314     }
315     if_chain! {
316         if let ExprKind::MethodCall(_, _, args, _) = expr.kind;
317         if let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id);
318         then {
319             let substs = cx.typeck_results().node_substs(expr.hir_id);
320             return Some((method_def_id, substs, args));
321         }
322     }
323     None
324 }
325
326 /// Returns the `TraitPredicate`s and `ProjectionPredicate`s for a function's input type.
327 fn get_input_traits_and_projections<'tcx>(
328     cx: &LateContext<'tcx>,
329     callee_def_id: DefId,
330     input: Ty<'tcx>,
331 ) -> (Vec<TraitPredicate<'tcx>>, Vec<ProjectionPredicate<'tcx>>) {
332     let mut trait_predicates = Vec::new();
333     let mut projection_predicates = Vec::new();
334     for (predicate, _) in cx.tcx.predicates_of(callee_def_id).predicates.iter() {
335         // `substs` should have 1 + n elements. The first is the type on the left hand side of an
336         // `as`. The remaining n are trait parameters.
337         let is_input_substs = |substs: SubstsRef<'tcx>| {
338             if_chain! {
339                 if let Some(arg) = substs.iter().next();
340                 if let GenericArgKind::Type(arg_ty) = arg.unpack();
341                 if arg_ty == input;
342                 then {
343                     true
344                 } else {
345                     false
346                 }
347             }
348         };
349         match predicate.kind().skip_binder() {
350             PredicateKind::Trait(trait_predicate) => {
351                 if is_input_substs(trait_predicate.trait_ref.substs) {
352                     trait_predicates.push(trait_predicate);
353                 }
354             },
355             PredicateKind::Projection(projection_predicate) => {
356                 if is_input_substs(projection_predicate.projection_ty.substs) {
357                     projection_predicates.push(projection_predicate);
358                 }
359             },
360             _ => {},
361         }
362     }
363     (trait_predicates, projection_predicates)
364 }
365
366 /// Composes two substitutions by applying the latter to the types of the former.
367 fn compose_substs<'tcx>(
368     cx: &LateContext<'tcx>,
369     left: &[GenericArg<'tcx>],
370     right: SubstsRef<'tcx>,
371 ) -> Vec<GenericArg<'tcx>> {
372     left.iter()
373         .map(|arg| {
374             if let GenericArgKind::Type(arg_ty) = arg.unpack() {
375                 let normalized_ty = cx.tcx.subst_and_normalize_erasing_regions(right, cx.param_env, arg_ty);
376                 GenericArg::from(normalized_ty)
377             } else {
378                 *arg
379             }
380         })
381         .collect()
382 }
383
384 /// Returns true if the named method is `Iterator::cloned` or `Iterator::copied`.
385 fn is_cloned_or_copied(cx: &LateContext<'_>, method_name: Symbol, method_def_id: DefId) -> bool {
386     (method_name.as_str() == "cloned" || method_name.as_str() == "copied")
387         && is_diag_trait_item(cx, method_def_id, sym::Iterator)
388 }
389
390 /// Returns true if the named method can be used to convert the receiver to its "owned"
391 /// representation.
392 fn is_to_owned_like(cx: &LateContext<'_>, method_name: Symbol, method_def_id: DefId) -> bool {
393     is_clone_like(cx, &*method_name.as_str(), method_def_id)
394         || is_cow_into_owned(cx, method_name, method_def_id)
395         || is_to_string(cx, method_name, method_def_id)
396 }
397
398 /// Returns true if the named method is `Cow::into_owned`.
399 fn is_cow_into_owned(cx: &LateContext<'_>, method_name: Symbol, method_def_id: DefId) -> bool {
400     method_name.as_str() == "into_owned" && is_diag_item_method(cx, method_def_id, sym::Cow)
401 }
402
403 /// Returns true if the named method is `ToString::to_string`.
404 fn is_to_string(cx: &LateContext<'_>, method_name: Symbol, method_def_id: DefId) -> bool {
405     method_name.as_str() == "to_string" && is_diag_trait_item(cx, method_def_id, sym::ToString)
406 }