]> git.proxmox.com Git - rustc.git/blob - src/tools/clippy/clippy_lints/src/map_clone.rs
New upstream version 1.22.1+dfsg1
[rustc.git] / src / tools / clippy / 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_qpath, 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_qpath(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 }