]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/methods/clone_on_copy.rs
Merge commit '98e2b9f25b6db4b2680a3d388456d9f95cb28344' into clippyup
[rust.git] / clippy_lints / src / methods / clone_on_copy.rs
1 use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
2 use clippy_utils::get_parent_node;
3 use clippy_utils::source::snippet_with_context;
4 use clippy_utils::sugg;
5 use clippy_utils::ty::is_copy;
6 use rustc_errors::Applicability;
7 use rustc_hir::{BindingAnnotation, Expr, ExprKind, MatchSource, Node, PatKind};
8 use rustc_lint::LateContext;
9 use rustc_middle::ty::{self, adjustment::Adjust};
10 use rustc_span::symbol::{sym, Symbol};
11 use std::iter;
12
13 use super::CLONE_DOUBLE_REF;
14 use super::CLONE_ON_COPY;
15
16 /// Checks for the `CLONE_ON_COPY` lint.
17 #[allow(clippy::too_many_lines)]
18 pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, method_name: Symbol, args: &[Expr<'_>]) {
19     let arg = match args {
20         [arg] if method_name == sym::clone => arg,
21         _ => return,
22     };
23     if cx
24         .typeck_results()
25         .type_dependent_def_id(expr.hir_id)
26         .and_then(|id| cx.tcx.trait_of_item(id))
27         .zip(cx.tcx.lang_items().clone_trait())
28         .map_or(true, |(x, y)| x != y)
29     {
30         return;
31     }
32     let arg_adjustments = cx.typeck_results().expr_adjustments(arg);
33     let arg_ty = arg_adjustments
34         .last()
35         .map_or_else(|| cx.typeck_results().expr_ty(arg), |a| a.target);
36
37     let ty = cx.typeck_results().expr_ty(expr);
38     if let ty::Ref(_, inner, _) = arg_ty.kind() {
39         if let ty::Ref(_, innermost, _) = inner.kind() {
40             span_lint_and_then(
41                 cx,
42                 CLONE_DOUBLE_REF,
43                 expr.span,
44                 &format!(
45                     "using `clone` on a double-reference; \
46                     this will copy the reference of type `{}` instead of cloning the inner type",
47                     ty
48                 ),
49                 |diag| {
50                     if let Some(snip) = sugg::Sugg::hir_opt(cx, arg) {
51                         let mut ty = innermost;
52                         let mut n = 0;
53                         while let ty::Ref(_, inner, _) = ty.kind() {
54                             ty = inner;
55                             n += 1;
56                         }
57                         let refs: String = iter::repeat('&').take(n + 1).collect();
58                         let derefs: String = iter::repeat('*').take(n).collect();
59                         let explicit = format!("<{}{}>::clone({})", refs, ty, snip);
60                         diag.span_suggestion(
61                             expr.span,
62                             "try dereferencing it",
63                             format!("{}({}{}).clone()", refs, derefs, snip.deref()),
64                             Applicability::MaybeIncorrect,
65                         );
66                         diag.span_suggestion(
67                             expr.span,
68                             "or try being explicit if you are sure, that you want to clone a reference",
69                             explicit,
70                             Applicability::MaybeIncorrect,
71                         );
72                     }
73                 },
74             );
75             return; // don't report clone_on_copy
76         }
77     }
78
79     if is_copy(cx, ty) {
80         let parent_is_suffix_expr = match get_parent_node(cx.tcx, expr.hir_id) {
81             Some(Node::Expr(parent)) => match parent.kind {
82                 // &*x is a nop, &x.clone() is not
83                 ExprKind::AddrOf(..) => return,
84                 // (*x).func() is useless, x.clone().func() can work in case func borrows self
85                 ExprKind::MethodCall(_, _, [self_arg, ..], _)
86                     if expr.hir_id == self_arg.hir_id && ty != cx.typeck_results().expr_ty_adjusted(expr) =>
87                 {
88                     return;
89                 }
90                 ExprKind::MethodCall(_, _, [self_arg, ..], _) if expr.hir_id == self_arg.hir_id => true,
91                 ExprKind::Match(_, _, MatchSource::TryDesugar | MatchSource::AwaitDesugar)
92                 | ExprKind::Field(..)
93                 | ExprKind::Index(..) => true,
94                 _ => false,
95             },
96             // local binding capturing a reference
97             Some(Node::Local(l))
98                 if matches!(
99                     l.pat.kind,
100                     PatKind::Binding(BindingAnnotation::Ref | BindingAnnotation::RefMut, ..)
101                 ) =>
102             {
103                 return;
104             }
105             _ => false,
106         };
107
108         let mut app = Applicability::MachineApplicable;
109         let snip = snippet_with_context(cx, arg.span, expr.span.ctxt(), "_", &mut app).0;
110
111         let deref_count = arg_adjustments
112             .iter()
113             .take_while(|adj| matches!(adj.kind, Adjust::Deref(_)))
114             .count();
115         let (help, sugg) = if deref_count == 0 {
116             ("try removing the `clone` call", snip.into())
117         } else if parent_is_suffix_expr {
118             ("try dereferencing it", format!("({}{})", "*".repeat(deref_count), snip))
119         } else {
120             ("try dereferencing it", format!("{}{}", "*".repeat(deref_count), snip))
121         };
122
123         span_lint_and_sugg(
124             cx,
125             CLONE_ON_COPY,
126             expr.span,
127             &format!("using `clone` on type `{}` which implements the `Copy` trait", ty),
128             help,
129             sugg,
130             app,
131         );
132     }
133 }