]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/entry.rs
Merge pull request #2821 from mati865/rust-2018-migration
[rust.git] / clippy_lints / src / entry.rs
1 use rustc::hir::*;
2 use rustc::hir::intravisit::{walk_expr, NestedVisitorMap, Visitor};
3 use rustc::lint::*;
4 use syntax::codemap::Span;
5 use crate::utils::SpanlessEq;
6 use crate::utils::{get_item_name, match_type, paths, snippet, span_lint_and_then, walk_ptrs_ty};
7
8 /// **What it does:** Checks for uses of `contains_key` + `insert` on `HashMap`
9 /// or `BTreeMap`.
10 ///
11 /// **Why is this bad?** Using `entry` is more efficient.
12 ///
13 /// **Known problems:** Some false negatives, eg.:
14 /// ```rust
15 /// let k = &key;
16 /// if !m.contains_key(k) { m.insert(k.clone(), v); }
17 /// ```
18 ///
19 /// **Example:**
20 /// ```rust
21 /// if !m.contains_key(&k) { m.insert(k, v) }
22 /// ```
23 /// can be rewritten as:
24 /// ```rust
25 /// m.entry(k).or_insert(v);
26 /// ```
27 declare_clippy_lint! {
28     pub MAP_ENTRY,
29     perf,
30     "use of `contains_key` followed by `insert` on a `HashMap` or `BTreeMap`"
31 }
32
33 #[derive(Copy, Clone)]
34 pub struct HashMapLint;
35
36 impl LintPass for HashMapLint {
37     fn get_lints(&self) -> LintArray {
38         lint_array!(MAP_ENTRY)
39     }
40 }
41
42 impl<'a, 'tcx> LateLintPass<'a, 'tcx> for HashMapLint {
43     fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr) {
44         if let ExprIf(ref check, ref then_block, ref else_block) = expr.node {
45             if let ExprUnary(UnOp::UnNot, ref check) = check.node {
46                 if let Some((ty, map, key)) = check_cond(cx, check) {
47                     // in case of `if !m.contains_key(&k) { m.insert(k, v); }`
48                     // we can give a better error message
49                     let sole_expr = {
50                         else_block.is_none() && if let ExprBlock(ref then_block, _) = then_block.node {
51                             (then_block.expr.is_some() as usize) + then_block.stmts.len() == 1
52                         } else {
53                             true
54                         }
55                     };
56
57                     let mut visitor = InsertVisitor {
58                         cx,
59                         span: expr.span,
60                         ty,
61                         map,
62                         key,
63                         sole_expr,
64                     };
65
66                     walk_expr(&mut visitor, &**then_block);
67                 }
68             } else if let Some(ref else_block) = *else_block {
69                 if let Some((ty, map, key)) = check_cond(cx, check) {
70                     let mut visitor = InsertVisitor {
71                         cx,
72                         span: expr.span,
73                         ty,
74                         map,
75                         key,
76                         sole_expr: false,
77                     };
78
79                     walk_expr(&mut visitor, else_block);
80                 }
81             }
82         }
83     }
84 }
85
86 fn check_cond<'a, 'tcx, 'b>(
87     cx: &'a LateContext<'a, 'tcx>,
88     check: &'b Expr,
89 ) -> Option<(&'static str, &'b Expr, &'b Expr)> {
90     if_chain! {
91         if let ExprMethodCall(ref path, _, ref params) = check.node;
92         if params.len() >= 2;
93         if path.name == "contains_key";
94         if let ExprAddrOf(_, ref key) = params[1].node;
95         then {
96             let map = &params[0];
97             let obj_ty = walk_ptrs_ty(cx.tables.expr_ty(map));
98
99             return if match_type(cx, obj_ty, &paths::BTREEMAP) {
100                 Some(("BTreeMap", map, key))
101             }
102             else if match_type(cx, obj_ty, &paths::HASHMAP) {
103                 Some(("HashMap", map, key))
104             }
105             else {
106                 None
107             };
108         }
109     }
110
111     None
112 }
113
114 struct InsertVisitor<'a, 'tcx: 'a, 'b> {
115     cx: &'a LateContext<'a, 'tcx>,
116     span: Span,
117     ty: &'static str,
118     map: &'b Expr,
119     key: &'b Expr,
120     sole_expr: bool,
121 }
122
123 impl<'a, 'tcx, 'b> Visitor<'tcx> for InsertVisitor<'a, 'tcx, 'b> {
124     fn visit_expr(&mut self, expr: &'tcx Expr) {
125         if_chain! {
126             if let ExprMethodCall(ref path, _, ref params) = expr.node;
127             if params.len() == 3;
128             if path.name == "insert";
129             if get_item_name(self.cx, self.map) == get_item_name(self.cx, &params[0]);
130             if SpanlessEq::new(self.cx).eq_expr(self.key, &params[1]);
131             then {
132                 span_lint_and_then(self.cx, MAP_ENTRY, self.span,
133                                    &format!("usage of `contains_key` followed by `insert` on a `{}`", self.ty), |db| {
134                     if self.sole_expr {
135                         let help = format!("{}.entry({}).or_insert({})",
136                                            snippet(self.cx, self.map.span, "map"),
137                                            snippet(self.cx, params[1].span, ".."),
138                                            snippet(self.cx, params[2].span, ".."));
139
140                         db.span_suggestion(self.span, "consider using", help);
141                     }
142                     else {
143                         let help = format!("{}.entry({})",
144                                            snippet(self.cx, self.map.span, "map"),
145                                            snippet(self.cx, params[1].span, ".."));
146
147                         db.span_suggestion(self.span, "consider using", help);
148                     }
149                 });
150             }
151         }
152
153         if !self.sole_expr {
154             walk_expr(self, expr);
155         }
156     }
157     fn nested_visit_map<'this>(&'this mut self) -> NestedVisitorMap<'this, 'tcx> {
158         NestedVisitorMap::None
159     }
160 }