]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/methods/unnecessary_to_owned.rs
Auto merge of #101241 - camsteffen:refactor-binding-annotations, r=cjgillot
[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     get_associated_type, get_iterator_item_ty, implements_trait, is_copy, is_type_diagnostic_item, 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 clippy_utils::{meets_msrv, msrvs};
10 use rustc_errors::Applicability;
11 use rustc_hir::{def_id::DefId, BorrowKind, Expr, ExprKind};
12 use rustc_lint::LateContext;
13 use rustc_middle::mir::Mutability;
14 use rustc_middle::ty::adjustment::{Adjust, Adjustment, OverloadedDeref};
15 use rustc_middle::ty::subst::{GenericArg, GenericArgKind, SubstsRef};
16 use rustc_middle::ty::{self, PredicateKind, ProjectionPredicate, TraitPredicate, Ty};
17 use rustc_semver::RustcVersion;
18 use rustc_span::{sym, Symbol};
19 use std::cmp::max;
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: 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 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, 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_receiver, call_args)) = get_callee_substs_and_args(cx, maybe_call);
250         let fn_sig = cx.tcx.fn_sig(callee_def_id).skip_binder();
251         let index = if let Some(call_receiver) = call_receiver {
252             std::iter::once(call_receiver).chain(call_args.iter()).position(|arg| arg.hir_id == maybe_arg.hir_id)
253         } else {
254             call_args.iter().position(|arg| arg.hir_id == maybe_arg.hir_id)
255         };
256         if let Some(i) = index;
257         if let Some(input) = fn_sig.inputs().get(i);
258         let (input, n_refs) = peel_mid_ty_refs(*input);
259         if let (trait_predicates, projection_predicates) = get_input_traits_and_projections(cx, callee_def_id, input);
260         if let Some(sized_def_id) = cx.tcx.lang_items().sized_trait();
261         if let [trait_predicate] = trait_predicates
262             .iter()
263             .filter(|trait_predicate| trait_predicate.def_id() != sized_def_id)
264             .collect::<Vec<_>>()[..];
265         if let Some(deref_trait_id) = cx.tcx.get_diagnostic_item(sym::Deref);
266         if let Some(as_ref_trait_id) = cx.tcx.get_diagnostic_item(sym::AsRef);
267         let receiver_ty = cx.typeck_results().expr_ty(receiver);
268         // If the callee has type parameters, they could appear in `projection_predicate.ty` or the
269         // types of `trait_predicate.trait_ref.substs`.
270         if if trait_predicate.def_id() == deref_trait_id {
271             if let [projection_predicate] = projection_predicates[..] {
272                 let normalized_ty =
273                     cx.tcx
274                         .subst_and_normalize_erasing_regions(call_substs, cx.param_env, projection_predicate.term);
275                 implements_trait(cx, receiver_ty, deref_trait_id, &[])
276                     && get_associated_type(cx, receiver_ty, deref_trait_id, "Target")
277                         .map_or(false, |ty| ty::Term::Ty(ty) == normalized_ty)
278             } else {
279                 false
280             }
281         } else if trait_predicate.def_id() == as_ref_trait_id {
282             let composed_substs = compose_substs(
283                 cx,
284                 &trait_predicate.trait_ref.substs.iter().skip(1).collect::<Vec<_>>()[..],
285                 call_substs,
286             );
287             // if `expr` is a `String` and generic target is [u8], skip
288             // (https://github.com/rust-lang/rust-clippy/issues/9317).
289             if let [subst] = composed_substs[..]
290                 && let GenericArgKind::Type(arg_ty) = subst.unpack()
291                 && arg_ty.is_slice()
292                 && let inner_ty = arg_ty.builtin_index().unwrap()
293                 && let ty::Uint(ty::UintTy::U8) = inner_ty.kind()
294                 && let self_ty = cx.typeck_results().expr_ty(expr).peel_refs()
295                 && is_type_diagnostic_item(cx, self_ty, sym::String) {
296                 false
297             } else {
298                 implements_trait(cx, receiver_ty, as_ref_trait_id, &composed_substs)
299             }
300         } else {
301             false
302         };
303         // We can't add an `&` when the trait is `Deref` because `Target = &T` won't match
304         // `Target = T`.
305         if n_refs > 0 || is_copy(cx, receiver_ty) || trait_predicate.def_id() != deref_trait_id;
306         let n_refs = max(n_refs, if is_copy(cx, receiver_ty) { 0 } else { 1 });
307         // If the trait is `AsRef` and the input type variable `T` occurs in the output type, then
308         // `T` must not be instantiated with a reference
309         // (https://github.com/rust-lang/rust-clippy/issues/8507).
310         if (n_refs == 0 && !receiver_ty.is_ref())
311             || trait_predicate.def_id() != as_ref_trait_id
312             || !fn_sig.output().contains(input);
313         if let Some(receiver_snippet) = snippet_opt(cx, receiver.span);
314         then {
315             span_lint_and_sugg(
316                 cx,
317                 UNNECESSARY_TO_OWNED,
318                 maybe_arg.span,
319                 &format!("unnecessary use of `{}`", method_name),
320                 "use",
321                 format!("{:&>width$}{}", "", receiver_snippet, width = n_refs),
322                 Applicability::MachineApplicable,
323             );
324             return true;
325         }
326     }
327     false
328 }
329
330 /// Walks an expression's ancestors until it finds a non-`AddrOf` expression. Returns the first such
331 /// expression found (if any) along with the immediately prior expression.
332 fn skip_addr_of_ancestors<'tcx>(
333     cx: &LateContext<'tcx>,
334     mut expr: &'tcx Expr<'tcx>,
335 ) -> Option<(&'tcx Expr<'tcx>, &'tcx Expr<'tcx>)> {
336     while let Some(parent) = get_parent_expr(cx, expr) {
337         if let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, _) = parent.kind {
338             expr = parent;
339         } else {
340             return Some((parent, expr));
341         }
342     }
343     None
344 }
345
346 /// Checks whether an expression is a function or method call and, if so, returns its `DefId`,
347 /// `Substs`, and arguments.
348 fn get_callee_substs_and_args<'tcx>(
349     cx: &LateContext<'tcx>,
350     expr: &'tcx Expr<'tcx>,
351 ) -> Option<(DefId, SubstsRef<'tcx>, Option<&'tcx Expr<'tcx>>, &'tcx [Expr<'tcx>])> {
352     if_chain! {
353         if let ExprKind::Call(callee, args) = expr.kind;
354         let callee_ty = cx.typeck_results().expr_ty(callee);
355         if let ty::FnDef(callee_def_id, _) = callee_ty.kind();
356         then {
357             let substs = cx.typeck_results().node_substs(callee.hir_id);
358             return Some((*callee_def_id, substs, None, args));
359         }
360     }
361     if_chain! {
362         if let ExprKind::MethodCall(_, receiver, args, _) = expr.kind;
363         if let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id);
364         then {
365             let substs = cx.typeck_results().node_substs(expr.hir_id);
366             return Some((method_def_id, substs, Some(receiver), args));
367         }
368     }
369     None
370 }
371
372 /// Returns the `TraitPredicate`s and `ProjectionPredicate`s for a function's input type.
373 fn get_input_traits_and_projections<'tcx>(
374     cx: &LateContext<'tcx>,
375     callee_def_id: DefId,
376     input: Ty<'tcx>,
377 ) -> (Vec<TraitPredicate<'tcx>>, Vec<ProjectionPredicate<'tcx>>) {
378     let mut trait_predicates = Vec::new();
379     let mut projection_predicates = Vec::new();
380     for predicate in cx.tcx.param_env(callee_def_id).caller_bounds() {
381         match predicate.kind().skip_binder() {
382             PredicateKind::Trait(trait_predicate) => {
383                 if trait_predicate.trait_ref.self_ty() == input {
384                     trait_predicates.push(trait_predicate);
385                 }
386             },
387             PredicateKind::Projection(projection_predicate) => {
388                 if projection_predicate.projection_ty.self_ty() == input {
389                     projection_predicates.push(projection_predicate);
390                 }
391             },
392             _ => {},
393         }
394     }
395     (trait_predicates, projection_predicates)
396 }
397
398 /// Composes two substitutions by applying the latter to the types of the former.
399 fn compose_substs<'tcx>(
400     cx: &LateContext<'tcx>,
401     left: &[GenericArg<'tcx>],
402     right: SubstsRef<'tcx>,
403 ) -> Vec<GenericArg<'tcx>> {
404     left.iter()
405         .map(|arg| {
406             if let GenericArgKind::Type(arg_ty) = arg.unpack() {
407                 let normalized_ty = cx.tcx.subst_and_normalize_erasing_regions(right, cx.param_env, arg_ty);
408                 GenericArg::from(normalized_ty)
409             } else {
410                 *arg
411             }
412         })
413         .collect()
414 }
415
416 /// Returns true if the named method is `Iterator::cloned` or `Iterator::copied`.
417 fn is_cloned_or_copied(cx: &LateContext<'_>, method_name: Symbol, method_def_id: DefId) -> bool {
418     (method_name.as_str() == "cloned" || method_name.as_str() == "copied")
419         && is_diag_trait_item(cx, method_def_id, sym::Iterator)
420 }
421
422 /// Returns true if the named method can be used to convert the receiver to its "owned"
423 /// representation.
424 fn is_to_owned_like(cx: &LateContext<'_>, method_name: Symbol, method_def_id: DefId) -> bool {
425     is_clone_like(cx, method_name.as_str(), method_def_id)
426         || is_cow_into_owned(cx, method_name, method_def_id)
427         || is_to_string(cx, method_name, method_def_id)
428 }
429
430 /// Returns true if the named method is `Cow::into_owned`.
431 fn is_cow_into_owned(cx: &LateContext<'_>, method_name: Symbol, method_def_id: DefId) -> bool {
432     method_name.as_str() == "into_owned" && is_diag_item_method(cx, method_def_id, sym::Cow)
433 }
434
435 /// Returns true if the named method is `ToString::to_string`.
436 fn is_to_string(cx: &LateContext<'_>, method_name: Symbol, method_def_id: DefId) -> bool {
437     method_name == sym::to_string && is_diag_trait_item(cx, method_def_id, sym::ToString)
438 }