]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/entry.rs
Auto merge of #6905 - ThibsG:fpSingleComponentPathImports5210, r=giraffate
[rust.git] / clippy_lints / src / entry.rs
1 use clippy_utils::diagnostics::span_lint_and_then;
2 use clippy_utils::source::{snippet, snippet_opt, snippet_with_applicability};
3 use clippy_utils::ty::{is_type_diagnostic_item, match_type};
4 use clippy_utils::SpanlessEq;
5 use clippy_utils::{get_item_name, paths};
6 use if_chain::if_chain;
7 use rustc_errors::Applicability;
8 use rustc_hir::intravisit::{walk_expr, NestedVisitorMap, Visitor};
9 use rustc_hir::{BorrowKind, Expr, ExprKind, UnOp};
10 use rustc_lint::{LateContext, LateLintPass};
11 use rustc_middle::hir::map::Map;
12 use rustc_session::{declare_lint_pass, declare_tool_lint};
13 use rustc_span::source_map::Span;
14 use rustc_span::sym;
15
16 declare_clippy_lint! {
17     /// **What it does:** Checks for uses of `contains_key` + `insert` on `HashMap`
18     /// or `BTreeMap`.
19     ///
20     /// **Why is this bad?** Using `entry` is more efficient.
21     ///
22     /// **Known problems:** Some false negatives, eg.:
23     /// ```rust
24     /// # use std::collections::HashMap;
25     /// # let mut map = HashMap::new();
26     /// # let v = 1;
27     /// # let k = 1;
28     /// if !map.contains_key(&k) {
29     ///     map.insert(k.clone(), v);
30     /// }
31     /// ```
32     ///
33     /// **Example:**
34     /// ```rust
35     /// # use std::collections::HashMap;
36     /// # let mut map = HashMap::new();
37     /// # let k = 1;
38     /// # let v = 1;
39     /// if !map.contains_key(&k) {
40     ///     map.insert(k, v);
41     /// }
42     /// ```
43     /// can both be rewritten as:
44     /// ```rust
45     /// # use std::collections::HashMap;
46     /// # let mut map = HashMap::new();
47     /// # let k = 1;
48     /// # let v = 1;
49     /// map.entry(k).or_insert(v);
50     /// ```
51     pub MAP_ENTRY,
52     perf,
53     "use of `contains_key` followed by `insert` on a `HashMap` or `BTreeMap`"
54 }
55
56 declare_lint_pass!(HashMapPass => [MAP_ENTRY]);
57
58 impl<'tcx> LateLintPass<'tcx> for HashMapPass {
59     fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
60         if let ExprKind::If(check, then_block, ref else_block) = expr.kind {
61             if let ExprKind::Unary(UnOp::Not, check) = check.kind {
62                 if let Some((ty, map, key)) = check_cond(cx, check) {
63                     // in case of `if !m.contains_key(&k) { m.insert(k, v); }`
64                     // we can give a better error message
65                     let sole_expr = {
66                         else_block.is_none()
67                             && if let ExprKind::Block(then_block, _) = then_block.kind {
68                                 (then_block.expr.is_some() as usize) + then_block.stmts.len() == 1
69                             } else {
70                                 true
71                             }
72                         // XXXManishearth we can also check for if/else blocks containing `None`.
73                     };
74
75                     let mut visitor = InsertVisitor {
76                         cx,
77                         span: expr.span,
78                         ty,
79                         map,
80                         key,
81                         sole_expr,
82                     };
83
84                     walk_expr(&mut visitor, then_block);
85                 }
86             } else if let Some(else_block) = *else_block {
87                 if let Some((ty, map, key)) = check_cond(cx, check) {
88                     let mut visitor = InsertVisitor {
89                         cx,
90                         span: expr.span,
91                         ty,
92                         map,
93                         key,
94                         sole_expr: false,
95                     };
96
97                     walk_expr(&mut visitor, else_block);
98                 }
99             }
100         }
101     }
102 }
103
104 fn check_cond<'a>(cx: &LateContext<'_>, check: &'a Expr<'a>) -> Option<(&'static str, &'a Expr<'a>, &'a Expr<'a>)> {
105     if_chain! {
106         if let ExprKind::MethodCall(path, _, params, _) = check.kind;
107         if params.len() >= 2;
108         if path.ident.name == sym!(contains_key);
109         if let ExprKind::AddrOf(BorrowKind::Ref, _, key) = params[1].kind;
110         then {
111             let map = &params[0];
112             let obj_ty = cx.typeck_results().expr_ty(map).peel_refs();
113
114             return if match_type(cx, obj_ty, &paths::BTREEMAP) {
115                 Some(("BTreeMap", map, key))
116             }
117             else if is_type_diagnostic_item(cx, obj_ty, sym::hashmap_type) {
118                 Some(("HashMap", map, key))
119             }
120             else {
121                 None
122             };
123         }
124     }
125
126     None
127 }
128
129 struct InsertVisitor<'a, 'tcx, 'b> {
130     cx: &'a LateContext<'tcx>,
131     span: Span,
132     ty: &'static str,
133     map: &'b Expr<'b>,
134     key: &'b Expr<'b>,
135     sole_expr: bool,
136 }
137
138 impl<'a, 'tcx, 'b> Visitor<'tcx> for InsertVisitor<'a, 'tcx, 'b> {
139     type Map = Map<'tcx>;
140
141     fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
142         if_chain! {
143             if let ExprKind::MethodCall(path, _, params, _) = expr.kind;
144             if params.len() == 3;
145             if path.ident.name == sym!(insert);
146             if get_item_name(self.cx, self.map) == get_item_name(self.cx, &params[0]);
147             if SpanlessEq::new(self.cx).eq_expr(self.key, &params[1]);
148             if snippet_opt(self.cx, self.map.span) == snippet_opt(self.cx, params[0].span);
149             then {
150                 span_lint_and_then(self.cx, MAP_ENTRY, self.span,
151                                    &format!("usage of `contains_key` followed by `insert` on a `{}`", self.ty), |diag| {
152                     if self.sole_expr {
153                         let mut app = Applicability::MachineApplicable;
154                         let help = format!("{}.entry({}).or_insert({});",
155                                            snippet_with_applicability(self.cx, self.map.span, "map", &mut app),
156                                            snippet_with_applicability(self.cx, params[1].span, "..", &mut app),
157                                            snippet_with_applicability(self.cx, params[2].span, "..", &mut app));
158
159                         diag.span_suggestion(
160                             self.span,
161                             "consider using",
162                             help,
163                             Applicability::MachineApplicable, // snippet
164                         );
165                     }
166                     else {
167                         let help = format!("consider using `{}.entry({})`",
168                                            snippet(self.cx, self.map.span, "map"),
169                                            snippet(self.cx, params[1].span, ".."));
170
171                         diag.span_label(
172                             self.span,
173                             &help,
174                         );
175                     }
176                 });
177             }
178         }
179
180         if !self.sole_expr {
181             walk_expr(self, expr);
182         }
183     }
184     fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
185         NestedVisitorMap::None
186     }
187 }