]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/dereference.rs
Check for Deref trait impl + add fixed version
[rust.git] / clippy_lints / src / dereference.rs
1 use crate::utils::{
2     get_parent_expr, get_trait_def_id, implements_trait, method_calls, paths, snippet, span_lint_and_sugg,
3 };
4 use if_chain::if_chain;
5 use rustc_errors::Applicability;
6 use rustc_hir as hir;
7 use rustc_hir::{Expr, ExprKind, QPath, StmtKind};
8 use rustc_lint::{LateContext, LateLintPass};
9 use rustc_session::{declare_lint_pass, declare_tool_lint};
10 use rustc_span::source_map::Span;
11
12 declare_clippy_lint! {
13     /// **What it does:** Checks for explicit `deref()` or `deref_mut()` method calls.
14     ///
15     /// **Why is this bad?** Derefencing by `&*x` or `&mut *x` is clearer and more concise,
16     /// when not part of a method chain.
17     ///
18     /// **Example:**
19     /// ```rust
20     /// use std::ops::Deref;
21     /// let a: &mut String = &mut String::from("foo");
22     /// let b: &str = a.deref();
23     /// ```
24     /// Could be written as:
25     /// ```rust
26     /// let a: &mut String = &mut String::from("foo");
27     /// let b = &*a;
28     /// ```
29     ///
30     /// This lint excludes
31     /// ```rust,ignore
32     /// let _ = d.unwrap().deref();
33     /// ```
34     pub EXPLICIT_DEREF_METHOD,
35     pedantic,
36     "Explicit use of deref or deref_mut method while not in a method chain."
37 }
38
39 declare_lint_pass!(Dereferencing => [
40     EXPLICIT_DEREF_METHOD
41 ]);
42
43 impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Dereferencing {
44     fn check_stmt(&mut self, cx: &LateContext<'a, 'tcx>, stmt: &'tcx hir::Stmt<'_>) {
45         if_chain! {
46             if let StmtKind::Local(ref local) = stmt.kind;
47             if let Some(ref init) = local.init;
48
49             then {
50                 match init.kind {
51                     ExprKind::Call(ref _method, args) => {
52                         for arg in args {
53                             if_chain! {
54                                 // Caller must call only one other function (deref or deref_mut)
55                                 // otherwise it can lead to error prone suggestions (ie: &*a.len())
56                                 let (method_names, arg_list, _) = method_calls(arg, 2);
57                                 if method_names.len() == 1;
58                                 // Caller must be a variable
59                                 let variables = arg_list[0];
60                                 if variables.len() == 1;
61                                 if let ExprKind::Path(QPath::Resolved(None, _)) = variables[0].kind;
62
63                                 then {
64                                     let name = method_names[0].as_str();
65                                     lint_deref(cx, &*name, &variables[0], variables[0].span, arg.span);
66                                 }
67                             }
68                         }
69                     }
70                     ExprKind::MethodCall(ref method_name, _, ref args) => {
71                         if init.span.from_expansion() {
72                             return;
73                         }
74                         if_chain! {
75                             if args.len() == 1;
76                             if let ExprKind::Path(QPath::Resolved(None, _)) = args[0].kind;
77                             // Caller must call only one other function (deref or deref_mut)
78                             // otherwise it can lead to error prone suggestions (ie: &*a.len())
79                             let (method_names, arg_list, _) = method_calls(init, 2);
80                             if method_names.len() == 1;
81                             // Caller must be a variable
82                             let variables = arg_list[0];
83                             if variables.len() == 1;
84                             if let ExprKind::Path(QPath::Resolved(None, _)) = variables[0].kind;
85
86                             then {
87                                 let name = method_name.ident.as_str();
88                                 lint_deref(cx, &*name, init, args[0].span, init.span);
89                             }
90                         }
91                     }
92                     _ => ()
93                 }
94             }
95         }
96     }
97
98     fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr<'_>) {
99         if_chain! {
100             if let ExprKind::MethodCall(ref method_name, _, ref args) = &expr.kind;
101             if args.len() == 1;
102             if let ExprKind::Path(QPath::Resolved(None, _)) = args[0].kind;
103             if let Some(parent) = get_parent_expr(cx, &expr);
104
105             then {
106                 match parent.kind {
107                     // Already linted using statements
108                     ExprKind::Call(_, _) | ExprKind::MethodCall(_, _, _) => (),
109                     _ => {
110                         let name = method_name.ident.as_str();
111                         lint_deref(cx, &*name, &args[0], args[0].span, expr.span);
112                     }
113                 }
114             }
115         }
116     }
117 }
118
119 fn lint_deref(cx: &LateContext<'_, '_>, fn_name: &str, call_expr: &Expr<'_>, var_span: Span, expr_span: Span) {
120     match fn_name {
121         "deref" => {
122             if get_trait_def_id(cx, &paths::DEREF_TRAIT)
123                 .map_or(false, |id| implements_trait(cx, cx.tables.expr_ty(&call_expr), id, &[]))
124             {
125                 span_lint_and_sugg(
126                     cx,
127                     EXPLICIT_DEREF_METHOD,
128                     expr_span,
129                     "explicit deref method call",
130                     "try this",
131                     format!("&*{}", &snippet(cx, var_span, "..")),
132                     Applicability::MachineApplicable,
133                 );
134             }
135         },
136         "deref_mut" => {
137             if get_trait_def_id(cx, &paths::DEREF_MUT_TRAIT)
138                 .map_or(false, |id| implements_trait(cx, cx.tables.expr_ty(&call_expr), id, &[]))
139             {
140                 span_lint_and_sugg(
141                     cx,
142                     EXPLICIT_DEREF_METHOD,
143                     expr_span,
144                     "explicit deref_mut method call",
145                     "try this",
146                     format!("&mut *{}", &snippet(cx, var_span, "..")),
147                     Applicability::MachineApplicable,
148                 );
149             }
150         },
151         _ => (),
152     }
153 }