]> git.lizzy.rs Git - rust.git/blob - src/tools/clippy/clippy_lints/src/methods/unnecessary_to_owned.rs
Merge commit 'd0cf3481a84e3aa68c2f185c460e282af36ebc42' into clippyup
[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::{
6     contains_ty, get_associated_type, get_iterator_item_ty, implements_trait, is_copy, peel_mid_ty_refs,
7 };
8 use clippy_utils::{fn_def_id, get_parent_expr, is_diag_item_method, is_diag_trait_item};
9 use rustc_errors::Applicability;
10 use rustc_hir::{def_id::DefId, BorrowKind, Expr, ExprKind};
11 use rustc_lint::LateContext;
12 use rustc_middle::mir::Mutability;
13 use rustc_middle::ty::adjustment::{Adjust, Adjustment, OverloadedDeref};
14 use rustc_middle::ty::subst::{GenericArg, GenericArgKind, SubstsRef};
15 use rustc_middle::ty::{self, PredicateKind, ProjectionPredicate, TraitPredicate, Ty};
16 use rustc_span::{sym, Symbol};
17 use std::cmp::max;
18
19 use super::UNNECESSARY_TO_OWNED;
20
21 pub fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, method_name: Symbol, args: &'tcx [Expr<'tcx>]) {
22     if_chain! {
23         if let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id);
24         if let [receiver] = args;
25         then {
26             if is_cloned_or_copied(cx, method_name, method_def_id) {
27                 unnecessary_iter_cloned::check(cx, expr, method_name, receiver);
28             } else if is_to_owned_like(cx, method_name, method_def_id) {
29                 // At this point, we know the call is of a `to_owned`-like function. The functions
30                 // `check_addr_of_expr` and `check_call_arg` determine whether the call is unnecessary
31                 // based on its context, that is, whether it is a referent in an `AddrOf` expression, an
32                 // argument in a `into_iter` call, or an argument in the call of some other function.
33                 if check_addr_of_expr(cx, expr, method_name, method_def_id, receiver) {
34                     return;
35                 }
36                 if check_into_iter_call_arg(cx, expr, method_name, receiver) {
37                     return;
38                 }
39                 check_other_call_arg(cx, expr, method_name, receiver);
40             }
41         }
42     }
43 }
44
45 /// Checks whether `expr` is a referent in an `AddrOf` expression and, if so, determines whether its
46 /// call of a `to_owned`-like function is unnecessary.
47 #[allow(clippy::too_many_lines)]
48 fn check_addr_of_expr(
49     cx: &LateContext<'_>,
50     expr: &Expr<'_>,
51     method_name: Symbol,
52     method_def_id: DefId,
53     receiver: &Expr<'_>,
54 ) -> bool {
55     if_chain! {
56         if let Some(parent) = get_parent_expr(cx, expr);
57         if let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, _) = parent.kind;
58         let adjustments = cx.typeck_results().expr_adjustments(parent).iter().collect::<Vec<_>>();
59         if let Some(target_ty) = match adjustments[..]
60         {
61             // For matching uses of `Cow::from`
62             [
63                 Adjustment {
64                     kind: Adjust::Deref(None),
65                     ..
66                 },
67                 Adjustment {
68                     kind: Adjust::Borrow(_),
69                     target: target_ty,
70                 },
71             ]
72             // For matching uses of arrays
73             | [
74                 Adjustment {
75                     kind: Adjust::Deref(None),
76                     ..
77                 },
78                 Adjustment {
79                     kind: Adjust::Borrow(_),
80                     ..
81                 },
82                 Adjustment {
83                     kind: Adjust::Pointer(_),
84                     target: target_ty,
85                 },
86             ]
87             // For matching everything else
88             | [
89                 Adjustment {
90                     kind: Adjust::Deref(None),
91                     ..
92                 },
93                 Adjustment {
94                     kind: Adjust::Deref(Some(OverloadedDeref { .. })),
95                     ..
96                 },
97                 Adjustment {
98                     kind: Adjust::Borrow(_),
99                     target: target_ty,
100                 },
101             ] => Some(target_ty),
102             _ => None,
103         };
104         let receiver_ty = cx.typeck_results().expr_ty(receiver);
105         // Only flag cases where the receiver is copyable or the method is `Cow::into_owned`. This
106         // restriction is to ensure there is not overlap between `redundant_clone` and this lint.
107         if is_copy(cx, receiver_ty) || is_cow_into_owned(cx, method_name, method_def_id);
108         if let Some(receiver_snippet) = snippet_opt(cx, receiver.span);
109         then {
110             let (target_ty, n_target_refs) = peel_mid_ty_refs(*target_ty);
111             let (receiver_ty, n_receiver_refs) = peel_mid_ty_refs(receiver_ty);
112             if receiver_ty == target_ty && n_target_refs >= n_receiver_refs {
113                 span_lint_and_sugg(
114                     cx,
115                     UNNECESSARY_TO_OWNED,
116                     parent.span,
117                     &format!("unnecessary use of `{}`", method_name),
118                     "use",
119                     format!(
120                         "{:&>width$}{}",
121                         "",
122                         receiver_snippet,
123                         width = n_target_refs - n_receiver_refs
124                     ),
125                     Applicability::MachineApplicable,
126                 );
127                 return true;
128             }
129             if_chain! {
130                 if let Some(deref_trait_id) = cx.tcx.get_diagnostic_item(sym::Deref);
131                 if implements_trait(cx, receiver_ty, deref_trait_id, &[]);
132                 if get_associated_type(cx, receiver_ty, deref_trait_id, "Target") == Some(target_ty);
133                 then {
134                     if n_receiver_refs > 0 {
135                         span_lint_and_sugg(
136                             cx,
137                             UNNECESSARY_TO_OWNED,
138                             parent.span,
139                             &format!("unnecessary use of `{}`", method_name),
140                             "use",
141                             receiver_snippet,
142                             Applicability::MachineApplicable,
143                         );
144                     } else {
145                         span_lint_and_sugg(
146                             cx,
147                             UNNECESSARY_TO_OWNED,
148                             expr.span.with_lo(receiver.span.hi()),
149                             &format!("unnecessary use of `{}`", method_name),
150                             "remove this",
151                             String::new(),
152                             Applicability::MachineApplicable,
153                         );
154                     }
155                     return true;
156                 }
157             }
158             if_chain! {
159                 if let Some(as_ref_trait_id) = cx.tcx.get_diagnostic_item(sym::AsRef);
160                 if implements_trait(cx, receiver_ty, as_ref_trait_id, &[GenericArg::from(target_ty)]);
161                 then {
162                     span_lint_and_sugg(
163                         cx,
164                         UNNECESSARY_TO_OWNED,
165                         parent.span,
166                         &format!("unnecessary use of `{}`", method_name),
167                         "use",
168                         format!("{}.as_ref()", receiver_snippet),
169                         Applicability::MachineApplicable,
170                     );
171                     return true;
172                 }
173             }
174         }
175     }
176     false
177 }
178
179 /// Checks whether `expr` is an argument in an `into_iter` call and, if so, determines whether its
180 /// call of a `to_owned`-like function is unnecessary.
181 fn check_into_iter_call_arg(cx: &LateContext<'_>, expr: &Expr<'_>, method_name: Symbol, receiver: &Expr<'_>) -> bool {
182     if_chain! {
183         if let Some(parent) = get_parent_expr(cx, expr);
184         if let Some(callee_def_id) = fn_def_id(cx, parent);
185         if is_into_iter(cx, callee_def_id);
186         if let Some(iterator_trait_id) = cx.tcx.get_diagnostic_item(sym::Iterator);
187         let parent_ty = cx.typeck_results().expr_ty(parent);
188         if implements_trait(cx, parent_ty, iterator_trait_id, &[]);
189         if let Some(item_ty) = get_iterator_item_ty(cx, parent_ty);
190         if let Some(receiver_snippet) = snippet_opt(cx, receiver.span);
191         then {
192             if unnecessary_iter_cloned::check_for_loop_iter(cx, parent, method_name, receiver, true) {
193                 return true;
194             }
195             let cloned_or_copied = if is_copy(cx, item_ty) { "copied" } else { "cloned" };
196             // The next suggestion may be incorrect because the removal of the `to_owned`-like
197             // function could cause the iterator to hold a reference to a resource that is used
198             // mutably. See https://github.com/rust-lang/rust-clippy/issues/8148.
199             span_lint_and_sugg(
200                 cx,
201                 UNNECESSARY_TO_OWNED,
202                 parent.span,
203                 &format!("unnecessary use of `{}`", method_name),
204                 "use",
205                 format!("{}.iter().{}()", receiver_snippet, cloned_or_copied),
206                 Applicability::MaybeIncorrect,
207             );
208             return true;
209         }
210     }
211     false
212 }
213
214 /// Checks whether `expr` is an argument in a function call and, if so, determines whether its call
215 /// of a `to_owned`-like function is unnecessary.
216 fn check_other_call_arg<'tcx>(
217     cx: &LateContext<'tcx>,
218     expr: &'tcx Expr<'tcx>,
219     method_name: Symbol,
220     receiver: &'tcx Expr<'tcx>,
221 ) -> bool {
222     if_chain! {
223         if let Some((maybe_call, maybe_arg)) = skip_addr_of_ancestors(cx, expr);
224         if let Some((callee_def_id, call_substs, call_args)) = get_callee_substs_and_args(cx, maybe_call);
225         let fn_sig = cx.tcx.fn_sig(callee_def_id).skip_binder();
226         if let Some(i) = call_args.iter().position(|arg| arg.hir_id == maybe_arg.hir_id);
227         if let Some(input) = fn_sig.inputs().get(i);
228         let (input, n_refs) = peel_mid_ty_refs(*input);
229         if let (trait_predicates, projection_predicates) = get_input_traits_and_projections(cx, callee_def_id, input);
230         if let Some(sized_def_id) = cx.tcx.lang_items().sized_trait();
231         if let [trait_predicate] = trait_predicates
232             .iter()
233             .filter(|trait_predicate| trait_predicate.def_id() != sized_def_id)
234             .collect::<Vec<_>>()[..];
235         if let Some(deref_trait_id) = cx.tcx.get_diagnostic_item(sym::Deref);
236         if let Some(as_ref_trait_id) = cx.tcx.get_diagnostic_item(sym::AsRef);
237         let receiver_ty = cx.typeck_results().expr_ty(receiver);
238         // If the callee has type parameters, they could appear in `projection_predicate.ty` or the
239         // types of `trait_predicate.trait_ref.substs`.
240         if if trait_predicate.def_id() == deref_trait_id {
241             if let [projection_predicate] = projection_predicates[..] {
242                 let normalized_ty =
243                     cx.tcx
244                         .subst_and_normalize_erasing_regions(call_substs, cx.param_env, projection_predicate.term);
245                 implements_trait(cx, receiver_ty, deref_trait_id, &[])
246                     && get_associated_type(cx, receiver_ty, deref_trait_id, "Target")
247                         .map_or(false, |ty| ty::Term::Ty(ty) == normalized_ty)
248             } else {
249                 false
250             }
251         } else if trait_predicate.def_id() == as_ref_trait_id {
252             let composed_substs = compose_substs(
253                 cx,
254                 &trait_predicate.trait_ref.substs.iter().skip(1).collect::<Vec<_>>()[..],
255                 call_substs,
256             );
257             implements_trait(cx, receiver_ty, as_ref_trait_id, &composed_substs)
258         } else {
259             false
260         };
261         // We can't add an `&` when the trait is `Deref` because `Target = &T` won't match
262         // `Target = T`.
263         if n_refs > 0 || is_copy(cx, receiver_ty) || trait_predicate.def_id() != deref_trait_id;
264         let n_refs = max(n_refs, if is_copy(cx, receiver_ty) { 0 } else { 1 });
265         // If the trait is `AsRef` and the input type variable `T` occurs in the output type, then
266         // `T` must not be instantiated with a reference
267         // (https://github.com/rust-lang/rust-clippy/issues/8507).
268         if (n_refs == 0 && !receiver_ty.is_ref())
269             || trait_predicate.def_id() != as_ref_trait_id
270             || !contains_ty(fn_sig.output(), input);
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!("{:&>width$}{}", "", receiver_snippet, width = n_refs),
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>, &'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, args));
317         }
318     }
319     if_chain! {
320         if let ExprKind::MethodCall(_, 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, 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.predicates_of(callee_def_id).predicates.iter() {
339         // `substs` should have 1 + n elements. The first is the type on the left hand side of an
340         // `as`. The remaining n are trait parameters.
341         let is_input_substs = |substs: SubstsRef<'tcx>| {
342             if_chain! {
343                 if let Some(arg) = substs.iter().next();
344                 if let GenericArgKind::Type(arg_ty) = arg.unpack();
345                 if arg_ty == input;
346                 then { true } else { false }
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 }