]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/map_clone.rs
457c6cc8d65ef77f25bdbbabf583632339fa3242
[rust.git] / clippy_lints / src / map_clone.rs
1 use rustc::lint::*;
2 use rustc::hir::*;
3 use rustc::ty;
4 use syntax::ast;
5 use utils::{is_adjusted, match_path, match_trait_method, match_type, remove_blocks, paths, snippet,
6             span_help_and_lint, walk_ptrs_ty, walk_ptrs_ty_depth, iter_input_pats};
7
8 /// **What it does:** Checks for mapping `clone()` over an iterator.
9 ///
10 /// **Why is this bad?** It makes the code less readable than using the
11 /// `.cloned()` adapter.
12 ///
13 /// **Known problems:** None.
14 ///
15 /// **Example:**
16 /// ```rust
17 /// x.map(|e| e.clone());
18 /// ```
19 declare_lint! {
20     pub MAP_CLONE,
21     Warn,
22     "using `.map(|x| x.clone())` to clone an iterator or option's contents"
23 }
24
25 #[derive(Copy, Clone)]
26 pub struct Pass;
27
28 impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Pass {
29     fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr) {
30         // call to .map()
31         if let ExprMethodCall(ref method, _, ref args) = expr.node {
32             if method.name == "map" && args.len() == 2 {
33                 match args[1].node {
34                     ExprClosure(_, ref decl, closure_eid, _) => {
35                         let body = cx.tcx.hir.body(closure_eid);
36                         let closure_expr = remove_blocks(&body.value);
37                         let ty = cx.tables.pat_ty(&body.arguments[0].pat);
38                         if_let_chain! {[
39                             // nothing special in the argument, besides reference bindings
40                             // (e.g. .map(|&x| x) )
41                             let Some(first_arg) = iter_input_pats(decl, body).next(),
42                             let Some(arg_ident) = get_arg_name(&first_arg.pat),
43                             // the method is being called on a known type (option or iterator)
44                             let Some(type_name) = get_type_name(cx, expr, &args[0])
45                         ], {
46                             // look for derefs, for .map(|x| *x)
47                             if only_derefs(cx, &*closure_expr, arg_ident) &&
48                                 // .cloned() only removes one level of indirection, don't lint on more
49                                 walk_ptrs_ty_depth(cx.tables.pat_ty(&first_arg.pat)).1 == 1
50                             {
51                                 // the argument is not an &mut T
52                                 if let ty::TyRef(_, tam) = ty.sty {
53                                     if tam.mutbl == MutImmutable {
54                                         span_help_and_lint(cx, MAP_CLONE, expr.span, &format!(
55                                             "you seem to be using .map() to clone the contents of an {}, consider \
56                                             using `.cloned()`", type_name),
57                                             &format!("try\n{}.cloned()", snippet(cx, args[0].span, "..")));
58                                     }
59                                 }
60                             }
61                             // explicit clone() calls ( .map(|x| x.clone()) )
62                             else if let ExprMethodCall(ref clone_call, _, ref clone_args) = closure_expr.node {
63                                 if clone_call.name == "clone" &&
64                                     clone_args.len() == 1 &&
65                                     match_trait_method(cx, closure_expr, &paths::CLONE_TRAIT) &&
66                                     expr_eq_name(&clone_args[0], arg_ident)
67                                 {
68                                     span_help_and_lint(cx, MAP_CLONE, expr.span, &format!(
69                                         "you seem to be using .map() to clone the contents of an {}, consider \
70                                         using `.cloned()`", type_name),
71                                         &format!("try\n{}.cloned()", snippet(cx, args[0].span, "..")));
72                                 }
73                             }
74                         }}
75                     },
76                     ExprPath(ref path) => {
77                         if match_path(path, &paths::CLONE) {
78                             let type_name = get_type_name(cx, expr, &args[0]).unwrap_or("_");
79                             span_help_and_lint(
80                                 cx,
81                                 MAP_CLONE,
82                                 expr.span,
83                                 &format!(
84                                     "you seem to be using .map() to clone the contents of an \
85                                                          {}, consider using `.cloned()`",
86                                     type_name
87                                 ),
88                                 &format!("try\n{}.cloned()", snippet(cx, args[0].span, "..")),
89                             );
90                         }
91                     },
92                     _ => (),
93                 }
94             }
95         }
96     }
97 }
98
99 fn expr_eq_name(expr: &Expr, id: ast::Name) -> bool {
100     match expr.node {
101         ExprPath(QPath::Resolved(None, ref path)) => {
102             let arg_segment = [
103                 PathSegment {
104                     name: id,
105                     parameters: PathParameters::none(),
106                 },
107             ];
108             !path.is_global() && path.segments[..] == arg_segment
109         },
110         _ => false,
111     }
112 }
113
114 fn get_type_name(cx: &LateContext, expr: &Expr, arg: &Expr) -> Option<&'static str> {
115     if match_trait_method(cx, expr, &paths::ITERATOR) {
116         Some("iterator")
117     } else if match_type(cx, walk_ptrs_ty(cx.tables.expr_ty(arg)), &paths::OPTION) {
118         Some("Option")
119     } else {
120         None
121     }
122 }
123
124 fn get_arg_name(pat: &Pat) -> Option<ast::Name> {
125     match pat.node {
126         PatKind::Binding(_, _, name, None) => Some(name.node),
127         PatKind::Ref(ref subpat, _) => get_arg_name(subpat),
128         _ => None,
129     }
130 }
131
132 fn only_derefs(cx: &LateContext, expr: &Expr, id: ast::Name) -> bool {
133     match expr.node {
134         ExprUnary(UnDeref, ref subexpr) if !is_adjusted(cx, subexpr) => only_derefs(cx, subexpr, id),
135         _ => expr_eq_name(expr, id),
136     }
137 }
138
139 impl LintPass for Pass {
140     fn get_lints(&self) -> LintArray {
141         lint_array!(MAP_CLONE)
142     }
143 }