]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/methods/unnecessary_to_owned.rs
Auto merge of #95565 - jackh726:remove-borrowck-mode, r=nikomatsakis
[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::{
6     contains_ty, get_associated_type, get_iterator_item_ty, implements_trait, is_copy, peel_mid_ty_refs,
7 };
8 use clippy_utils::{meets_msrv, msrvs};
9
10 use clippy_utils::{fn_def_id, get_parent_expr, is_diag_item_method, is_diag_trait_item};
11 use rustc_errors::Applicability;
12 use rustc_hir::{def_id::DefId, BorrowKind, Expr, ExprKind};
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, PredicateKind, ProjectionPredicate, TraitPredicate, Ty};
18 use rustc_semver::RustcVersion;
19 use rustc_span::{sym, Symbol};
20 use std::cmp::max;
21
22 use super::UNNECESSARY_TO_OWNED;
23
24 pub fn check<'tcx>(
25     cx: &LateContext<'tcx>,
26     expr: &'tcx Expr<'tcx>,
27     method_name: Symbol,
28     args: &'tcx [Expr<'tcx>],
29     msrv: Option<RustcVersion>,
30 ) {
31     if_chain! {
32         if let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id);
33         if let [receiver] = args;
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, 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$}{}",
136                         "",
137                         receiver_snippet,
138                         width = n_target_refs - n_receiver_refs
139                     ),
140                     Applicability::MachineApplicable,
141                 );
142                 return true;
143             }
144             if_chain! {
145                 if let Some(deref_trait_id) = cx.tcx.get_diagnostic_item(sym::Deref);
146                 if implements_trait(cx, receiver_ty, deref_trait_id, &[]);
147                 if get_associated_type(cx, receiver_ty, deref_trait_id, "Target") == Some(target_ty);
148                 then {
149                     if n_receiver_refs > 0 {
150                         span_lint_and_sugg(
151                             cx,
152                             UNNECESSARY_TO_OWNED,
153                             parent.span,
154                             &format!("unnecessary use of `{}`", method_name),
155                             "use",
156                             receiver_snippet,
157                             Applicability::MachineApplicable,
158                         );
159                     } else {
160                         span_lint_and_sugg(
161                             cx,
162                             UNNECESSARY_TO_OWNED,
163                             expr.span.with_lo(receiver.span.hi()),
164                             &format!("unnecessary use of `{}`", method_name),
165                             "remove this",
166                             String::new(),
167                             Applicability::MachineApplicable,
168                         );
169                     }
170                     return true;
171                 }
172             }
173             if_chain! {
174                 if let Some(as_ref_trait_id) = cx.tcx.get_diagnostic_item(sym::AsRef);
175                 if implements_trait(cx, receiver_ty, as_ref_trait_id, &[GenericArg::from(target_ty)]);
176                 then {
177                     span_lint_and_sugg(
178                         cx,
179                         UNNECESSARY_TO_OWNED,
180                         parent.span,
181                         &format!("unnecessary use of `{}`", method_name),
182                         "use",
183                         format!("{}.as_ref()", receiver_snippet),
184                         Applicability::MachineApplicable,
185                     );
186                     return true;
187                 }
188             }
189         }
190     }
191     false
192 }
193
194 /// Checks whether `expr` is an argument in an `into_iter` call and, if so, determines whether its
195 /// call of a `to_owned`-like function is unnecessary.
196 fn check_into_iter_call_arg(
197     cx: &LateContext<'_>,
198     expr: &Expr<'_>,
199     method_name: Symbol,
200     receiver: &Expr<'_>,
201     msrv: Option<RustcVersion>,
202 ) -> bool {
203     if_chain! {
204         if let Some(parent) = get_parent_expr(cx, expr);
205         if let Some(callee_def_id) = fn_def_id(cx, parent);
206         if is_into_iter(cx, callee_def_id);
207         if let Some(iterator_trait_id) = cx.tcx.get_diagnostic_item(sym::Iterator);
208         let parent_ty = cx.typeck_results().expr_ty(parent);
209         if implements_trait(cx, parent_ty, iterator_trait_id, &[]);
210         if let Some(item_ty) = get_iterator_item_ty(cx, parent_ty);
211         if let Some(receiver_snippet) = snippet_opt(cx, receiver.span);
212         then {
213             if unnecessary_iter_cloned::check_for_loop_iter(cx, parent, method_name, receiver, true) {
214                 return true;
215             }
216             let cloned_or_copied = if is_copy(cx, item_ty) && meets_msrv(msrv, msrvs::ITERATOR_COPIED) {
217                 "copied"
218             } else {
219                 "cloned"
220             };
221             // The next suggestion may be incorrect because the removal of the `to_owned`-like
222             // function could cause the iterator to hold a reference to a resource that is used
223             // mutably. See https://github.com/rust-lang/rust-clippy/issues/8148.
224             span_lint_and_sugg(
225                 cx,
226                 UNNECESSARY_TO_OWNED,
227                 parent.span,
228                 &format!("unnecessary use of `{}`", method_name),
229                 "use",
230                 format!("{}.iter().{}()", receiver_snippet, cloned_or_copied),
231                 Applicability::MaybeIncorrect,
232             );
233             return true;
234         }
235     }
236     false
237 }
238
239 /// Checks whether `expr` is an argument in a function call and, if so, determines whether its call
240 /// of a `to_owned`-like function is unnecessary.
241 fn check_other_call_arg<'tcx>(
242     cx: &LateContext<'tcx>,
243     expr: &'tcx Expr<'tcx>,
244     method_name: Symbol,
245     receiver: &'tcx Expr<'tcx>,
246 ) -> bool {
247     if_chain! {
248         if let Some((maybe_call, maybe_arg)) = skip_addr_of_ancestors(cx, expr);
249         if let Some((callee_def_id, call_substs, call_args)) = get_callee_substs_and_args(cx, maybe_call);
250         let fn_sig = cx.tcx.fn_sig(callee_def_id).skip_binder();
251         if let Some(i) = call_args.iter().position(|arg| arg.hir_id == maybe_arg.hir_id);
252         if let Some(input) = fn_sig.inputs().get(i);
253         let (input, n_refs) = peel_mid_ty_refs(*input);
254         if let (trait_predicates, projection_predicates) = get_input_traits_and_projections(cx, callee_def_id, input);
255         if let Some(sized_def_id) = cx.tcx.lang_items().sized_trait();
256         if let [trait_predicate] = trait_predicates
257             .iter()
258             .filter(|trait_predicate| trait_predicate.def_id() != sized_def_id)
259             .collect::<Vec<_>>()[..];
260         if let Some(deref_trait_id) = cx.tcx.get_diagnostic_item(sym::Deref);
261         if let Some(as_ref_trait_id) = cx.tcx.get_diagnostic_item(sym::AsRef);
262         let receiver_ty = cx.typeck_results().expr_ty(receiver);
263         // If the callee has type parameters, they could appear in `projection_predicate.ty` or the
264         // types of `trait_predicate.trait_ref.substs`.
265         if if trait_predicate.def_id() == deref_trait_id {
266             if let [projection_predicate] = projection_predicates[..] {
267                 let normalized_ty =
268                     cx.tcx
269                         .subst_and_normalize_erasing_regions(call_substs, cx.param_env, projection_predicate.term);
270                 implements_trait(cx, receiver_ty, deref_trait_id, &[])
271                     && get_associated_type(cx, receiver_ty, deref_trait_id, "Target")
272                         .map_or(false, |ty| ty::Term::Ty(ty) == normalized_ty)
273             } else {
274                 false
275             }
276         } else if trait_predicate.def_id() == as_ref_trait_id {
277             let composed_substs = compose_substs(
278                 cx,
279                 &trait_predicate.trait_ref.substs.iter().skip(1).collect::<Vec<_>>()[..],
280                 call_substs,
281             );
282             implements_trait(cx, receiver_ty, as_ref_trait_id, &composed_substs)
283         } else {
284             false
285         };
286         // We can't add an `&` when the trait is `Deref` because `Target = &T` won't match
287         // `Target = T`.
288         if n_refs > 0 || is_copy(cx, receiver_ty) || trait_predicate.def_id() != deref_trait_id;
289         let n_refs = max(n_refs, if is_copy(cx, receiver_ty) { 0 } else { 1 });
290         // If the trait is `AsRef` and the input type variable `T` occurs in the output type, then
291         // `T` must not be instantiated with a reference
292         // (https://github.com/rust-lang/rust-clippy/issues/8507).
293         if (n_refs == 0 && !receiver_ty.is_ref())
294             || trait_predicate.def_id() != as_ref_trait_id
295             || !contains_ty(fn_sig.output(), input);
296         if let Some(receiver_snippet) = snippet_opt(cx, receiver.span);
297         then {
298             span_lint_and_sugg(
299                 cx,
300                 UNNECESSARY_TO_OWNED,
301                 maybe_arg.span,
302                 &format!("unnecessary use of `{}`", method_name),
303                 "use",
304                 format!("{:&>width$}{}", "", receiver_snippet, width = n_refs),
305                 Applicability::MachineApplicable,
306             );
307             return true;
308         }
309     }
310     false
311 }
312
313 /// Walks an expression's ancestors until it finds a non-`AddrOf` expression. Returns the first such
314 /// expression found (if any) along with the immediately prior expression.
315 fn skip_addr_of_ancestors<'tcx>(
316     cx: &LateContext<'tcx>,
317     mut expr: &'tcx Expr<'tcx>,
318 ) -> Option<(&'tcx Expr<'tcx>, &'tcx Expr<'tcx>)> {
319     while let Some(parent) = get_parent_expr(cx, expr) {
320         if let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, _) = parent.kind {
321             expr = parent;
322         } else {
323             return Some((parent, expr));
324         }
325     }
326     None
327 }
328
329 /// Checks whether an expression is a function or method call and, if so, returns its `DefId`,
330 /// `Substs`, and arguments.
331 fn get_callee_substs_and_args<'tcx>(
332     cx: &LateContext<'tcx>,
333     expr: &'tcx Expr<'tcx>,
334 ) -> Option<(DefId, SubstsRef<'tcx>, &'tcx [Expr<'tcx>])> {
335     if_chain! {
336         if let ExprKind::Call(callee, args) = expr.kind;
337         let callee_ty = cx.typeck_results().expr_ty(callee);
338         if let ty::FnDef(callee_def_id, _) = callee_ty.kind();
339         then {
340             let substs = cx.typeck_results().node_substs(callee.hir_id);
341             return Some((*callee_def_id, substs, args));
342         }
343     }
344     if_chain! {
345         if let ExprKind::MethodCall(_, args, _) = expr.kind;
346         if let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id);
347         then {
348             let substs = cx.typeck_results().node_substs(expr.hir_id);
349             return Some((method_def_id, substs, args));
350         }
351     }
352     None
353 }
354
355 /// Returns the `TraitPredicate`s and `ProjectionPredicate`s for a function's input type.
356 fn get_input_traits_and_projections<'tcx>(
357     cx: &LateContext<'tcx>,
358     callee_def_id: DefId,
359     input: Ty<'tcx>,
360 ) -> (Vec<TraitPredicate<'tcx>>, Vec<ProjectionPredicate<'tcx>>) {
361     let mut trait_predicates = Vec::new();
362     let mut projection_predicates = Vec::new();
363     for (predicate, _) in cx.tcx.predicates_of(callee_def_id).predicates.iter() {
364         // `substs` should have 1 + n elements. The first is the type on the left hand side of an
365         // `as`. The remaining n are trait parameters.
366         let is_input_substs = |substs: SubstsRef<'tcx>| {
367             if_chain! {
368                 if let Some(arg) = substs.iter().next();
369                 if let GenericArgKind::Type(arg_ty) = arg.unpack();
370                 if arg_ty == input;
371                 then { true } else { false }
372             }
373         };
374         match predicate.kind().skip_binder() {
375             PredicateKind::Trait(trait_predicate) => {
376                 if is_input_substs(trait_predicate.trait_ref.substs) {
377                     trait_predicates.push(trait_predicate);
378                 }
379             },
380             PredicateKind::Projection(projection_predicate) => {
381                 if is_input_substs(projection_predicate.projection_ty.substs) {
382                     projection_predicates.push(projection_predicate);
383                 }
384             },
385             _ => {},
386         }
387     }
388     (trait_predicates, projection_predicates)
389 }
390
391 /// Composes two substitutions by applying the latter to the types of the former.
392 fn compose_substs<'tcx>(
393     cx: &LateContext<'tcx>,
394     left: &[GenericArg<'tcx>],
395     right: SubstsRef<'tcx>,
396 ) -> Vec<GenericArg<'tcx>> {
397     left.iter()
398         .map(|arg| {
399             if let GenericArgKind::Type(arg_ty) = arg.unpack() {
400                 let normalized_ty = cx.tcx.subst_and_normalize_erasing_regions(right, cx.param_env, arg_ty);
401                 GenericArg::from(normalized_ty)
402             } else {
403                 *arg
404             }
405         })
406         .collect()
407 }
408
409 /// Returns true if the named method is `Iterator::cloned` or `Iterator::copied`.
410 fn is_cloned_or_copied(cx: &LateContext<'_>, method_name: Symbol, method_def_id: DefId) -> bool {
411     (method_name.as_str() == "cloned" || method_name.as_str() == "copied")
412         && is_diag_trait_item(cx, method_def_id, sym::Iterator)
413 }
414
415 /// Returns true if the named method can be used to convert the receiver to its "owned"
416 /// representation.
417 fn is_to_owned_like(cx: &LateContext<'_>, method_name: Symbol, method_def_id: DefId) -> bool {
418     is_clone_like(cx, method_name.as_str(), method_def_id)
419         || is_cow_into_owned(cx, method_name, method_def_id)
420         || is_to_string(cx, method_name, method_def_id)
421 }
422
423 /// Returns true if the named method is `Cow::into_owned`.
424 fn is_cow_into_owned(cx: &LateContext<'_>, method_name: Symbol, method_def_id: DefId) -> bool {
425     method_name.as_str() == "into_owned" && is_diag_item_method(cx, method_def_id, sym::Cow)
426 }
427
428 /// Returns true if the named method is `ToString::to_string`.
429 fn is_to_string(cx: &LateContext<'_>, method_name: Symbol, method_def_id: DefId) -> bool {
430     method_name.as_str() == "to_string" && is_diag_trait_item(cx, method_def_id, sym::ToString)
431 }