]> git.lizzy.rs Git - rust.git/blob - src/tools/clippy/clippy_lints/src/implicit_hasher.rs
Merge commit '98e2b9f25b6db4b2680a3d388456d9f95cb28344' into clippyup
[rust.git] / src / tools / clippy / clippy_lints / src / implicit_hasher.rs
1 #![allow(rustc::default_hash_types)]
2
3 use std::borrow::Cow;
4 use std::collections::BTreeMap;
5
6 use rustc_errors::DiagnosticBuilder;
7 use rustc_hir as hir;
8 use rustc_hir::intravisit::{walk_body, walk_expr, walk_ty, NestedVisitorMap, Visitor};
9 use rustc_hir::{Body, Expr, ExprKind, GenericArg, Item, ItemKind, QPath, TyKind};
10 use rustc_lint::{LateContext, LateLintPass, LintContext};
11 use rustc_middle::hir::map::Map;
12 use rustc_middle::lint::in_external_macro;
13 use rustc_middle::ty::{Ty, TyS, TypeckResults};
14 use rustc_session::{declare_lint_pass, declare_tool_lint};
15 use rustc_span::source_map::Span;
16 use rustc_span::symbol::sym;
17 use rustc_typeck::hir_ty_to_ty;
18
19 use if_chain::if_chain;
20
21 use clippy_utils::diagnostics::{multispan_sugg, span_lint_and_then};
22 use clippy_utils::paths;
23 use clippy_utils::source::{snippet, snippet_opt};
24 use clippy_utils::ty::is_type_diagnostic_item;
25 use clippy_utils::{differing_macro_contexts, match_def_path};
26
27 declare_clippy_lint! {
28     /// **What it does:** Checks for public `impl` or `fn` missing generalization
29     /// over different hashers and implicitly defaulting to the default hashing
30     /// algorithm (`SipHash`).
31     ///
32     /// **Why is this bad?** `HashMap` or `HashSet` with custom hashers cannot be
33     /// used with them.
34     ///
35     /// **Known problems:** Suggestions for replacing constructors can contain
36     /// false-positives. Also applying suggestions can require modification of other
37     /// pieces of code, possibly including external crates.
38     ///
39     /// **Example:**
40     /// ```rust
41     /// # use std::collections::HashMap;
42     /// # use std::hash::{Hash, BuildHasher};
43     /// # trait Serialize {};
44     /// impl<K: Hash + Eq, V> Serialize for HashMap<K, V> { }
45     ///
46     /// pub fn foo(map: &mut HashMap<i32, i32>) { }
47     /// ```
48     /// could be rewritten as
49     /// ```rust
50     /// # use std::collections::HashMap;
51     /// # use std::hash::{Hash, BuildHasher};
52     /// # trait Serialize {};
53     /// impl<K: Hash + Eq, V, S: BuildHasher> Serialize for HashMap<K, V, S> { }
54     ///
55     /// pub fn foo<S: BuildHasher>(map: &mut HashMap<i32, i32, S>) { }
56     /// ```
57     pub IMPLICIT_HASHER,
58     pedantic,
59     "missing generalization over different hashers"
60 }
61
62 declare_lint_pass!(ImplicitHasher => [IMPLICIT_HASHER]);
63
64 impl<'tcx> LateLintPass<'tcx> for ImplicitHasher {
65     #[allow(clippy::cast_possible_truncation, clippy::too_many_lines)]
66     fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
67         use rustc_span::BytePos;
68
69         fn suggestion<'tcx>(
70             cx: &LateContext<'tcx>,
71             diag: &mut DiagnosticBuilder<'_>,
72             generics_span: Span,
73             generics_suggestion_span: Span,
74             target: &ImplicitHasherType<'_>,
75             vis: ImplicitHasherConstructorVisitor<'_, '_, '_>,
76         ) {
77             let generics_snip = snippet(cx, generics_span, "");
78             // trim `<` `>`
79             let generics_snip = if generics_snip.is_empty() {
80                 ""
81             } else {
82                 &generics_snip[1..generics_snip.len() - 1]
83             };
84
85             multispan_sugg(
86                 diag,
87                 "consider adding a type parameter",
88                 vec![
89                     (
90                         generics_suggestion_span,
91                         format!(
92                             "<{}{}S: ::std::hash::BuildHasher{}>",
93                             generics_snip,
94                             if generics_snip.is_empty() { "" } else { ", " },
95                             if vis.suggestions.is_empty() {
96                                 ""
97                             } else {
98                                 // request users to add `Default` bound so that generic constructors can be used
99                                 " + Default"
100                             },
101                         ),
102                     ),
103                     (
104                         target.span(),
105                         format!("{}<{}, S>", target.type_name(), target.type_arguments(),),
106                     ),
107                 ],
108             );
109
110             if !vis.suggestions.is_empty() {
111                 multispan_sugg(diag, "...and use generic constructor", vis.suggestions);
112             }
113         }
114
115         if !cx.access_levels.is_exported(item.hir_id()) {
116             return;
117         }
118
119         match item.kind {
120             ItemKind::Impl(ref impl_) => {
121                 let mut vis = ImplicitHasherTypeVisitor::new(cx);
122                 vis.visit_ty(impl_.self_ty);
123
124                 for target in &vis.found {
125                     if differing_macro_contexts(item.span, target.span()) {
126                         return;
127                     }
128
129                     let generics_suggestion_span = impl_.generics.span.substitute_dummy({
130                         let pos = snippet_opt(cx, item.span.until(target.span()))
131                             .and_then(|snip| Some(item.span.lo() + BytePos(snip.find("impl")? as u32 + 4)));
132                         if let Some(pos) = pos {
133                             Span::new(pos, pos, item.span.data().ctxt)
134                         } else {
135                             return;
136                         }
137                     });
138
139                     let mut ctr_vis = ImplicitHasherConstructorVisitor::new(cx, target);
140                     for item in impl_.items.iter().map(|item| cx.tcx.hir().impl_item(item.id)) {
141                         ctr_vis.visit_impl_item(item);
142                     }
143
144                     span_lint_and_then(
145                         cx,
146                         IMPLICIT_HASHER,
147                         target.span(),
148                         &format!(
149                             "impl for `{}` should be generalized over different hashers",
150                             target.type_name()
151                         ),
152                         move |diag| {
153                             suggestion(cx, diag, impl_.generics.span, generics_suggestion_span, target, ctr_vis);
154                         },
155                     );
156                 }
157             },
158             ItemKind::Fn(ref sig, ref generics, body_id) => {
159                 let body = cx.tcx.hir().body(body_id);
160
161                 for ty in sig.decl.inputs {
162                     let mut vis = ImplicitHasherTypeVisitor::new(cx);
163                     vis.visit_ty(ty);
164
165                     for target in &vis.found {
166                         if in_external_macro(cx.sess(), generics.span) {
167                             continue;
168                         }
169                         let generics_suggestion_span = generics.span.substitute_dummy({
170                             let pos = snippet_opt(cx, item.span.until(body.params[0].pat.span))
171                                 .and_then(|snip| {
172                                     let i = snip.find("fn")?;
173                                     Some(item.span.lo() + BytePos((i + (&snip[i..]).find('(')?) as u32))
174                                 })
175                                 .expect("failed to create span for type parameters");
176                             Span::new(pos, pos, item.span.data().ctxt)
177                         });
178
179                         let mut ctr_vis = ImplicitHasherConstructorVisitor::new(cx, target);
180                         ctr_vis.visit_body(body);
181
182                         span_lint_and_then(
183                             cx,
184                             IMPLICIT_HASHER,
185                             target.span(),
186                             &format!(
187                                 "parameter of type `{}` should be generalized over different hashers",
188                                 target.type_name()
189                             ),
190                             move |diag| {
191                                 suggestion(cx, diag, generics.span, generics_suggestion_span, target, ctr_vis);
192                             },
193                         );
194                     }
195                 }
196             },
197             _ => {},
198         }
199     }
200 }
201
202 enum ImplicitHasherType<'tcx> {
203     HashMap(Span, Ty<'tcx>, Cow<'static, str>, Cow<'static, str>),
204     HashSet(Span, Ty<'tcx>, Cow<'static, str>),
205 }
206
207 impl<'tcx> ImplicitHasherType<'tcx> {
208     /// Checks that `ty` is a target type without a `BuildHasher`.
209     fn new(cx: &LateContext<'tcx>, hir_ty: &hir::Ty<'_>) -> Option<Self> {
210         if let TyKind::Path(QPath::Resolved(None, path)) = hir_ty.kind {
211             let params: Vec<_> = path
212                 .segments
213                 .last()
214                 .as_ref()?
215                 .args
216                 .as_ref()?
217                 .args
218                 .iter()
219                 .filter_map(|arg| match arg {
220                     GenericArg::Type(ty) => Some(ty),
221                     _ => None,
222                 })
223                 .collect();
224             let params_len = params.len();
225
226             let ty = hir_ty_to_ty(cx.tcx, hir_ty);
227
228             if is_type_diagnostic_item(cx, ty, sym::hashmap_type) && params_len == 2 {
229                 Some(ImplicitHasherType::HashMap(
230                     hir_ty.span,
231                     ty,
232                     snippet(cx, params[0].span, "K"),
233                     snippet(cx, params[1].span, "V"),
234                 ))
235             } else if is_type_diagnostic_item(cx, ty, sym::hashset_type) && params_len == 1 {
236                 Some(ImplicitHasherType::HashSet(
237                     hir_ty.span,
238                     ty,
239                     snippet(cx, params[0].span, "T"),
240                 ))
241             } else {
242                 None
243             }
244         } else {
245             None
246         }
247     }
248
249     fn type_name(&self) -> &'static str {
250         match *self {
251             ImplicitHasherType::HashMap(..) => "HashMap",
252             ImplicitHasherType::HashSet(..) => "HashSet",
253         }
254     }
255
256     fn type_arguments(&self) -> String {
257         match *self {
258             ImplicitHasherType::HashMap(.., ref k, ref v) => format!("{}, {}", k, v),
259             ImplicitHasherType::HashSet(.., ref t) => format!("{}", t),
260         }
261     }
262
263     fn ty(&self) -> Ty<'tcx> {
264         match *self {
265             ImplicitHasherType::HashMap(_, ty, ..) | ImplicitHasherType::HashSet(_, ty, ..) => ty,
266         }
267     }
268
269     fn span(&self) -> Span {
270         match *self {
271             ImplicitHasherType::HashMap(span, ..) | ImplicitHasherType::HashSet(span, ..) => span,
272         }
273     }
274 }
275
276 struct ImplicitHasherTypeVisitor<'a, 'tcx> {
277     cx: &'a LateContext<'tcx>,
278     found: Vec<ImplicitHasherType<'tcx>>,
279 }
280
281 impl<'a, 'tcx> ImplicitHasherTypeVisitor<'a, 'tcx> {
282     fn new(cx: &'a LateContext<'tcx>) -> Self {
283         Self { cx, found: vec![] }
284     }
285 }
286
287 impl<'a, 'tcx> Visitor<'tcx> for ImplicitHasherTypeVisitor<'a, 'tcx> {
288     type Map = Map<'tcx>;
289
290     fn visit_ty(&mut self, t: &'tcx hir::Ty<'_>) {
291         if let Some(target) = ImplicitHasherType::new(self.cx, t) {
292             self.found.push(target);
293         }
294
295         walk_ty(self, t);
296     }
297
298     fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
299         NestedVisitorMap::None
300     }
301 }
302
303 /// Looks for default-hasher-dependent constructors like `HashMap::new`.
304 struct ImplicitHasherConstructorVisitor<'a, 'b, 'tcx> {
305     cx: &'a LateContext<'tcx>,
306     maybe_typeck_results: Option<&'tcx TypeckResults<'tcx>>,
307     target: &'b ImplicitHasherType<'tcx>,
308     suggestions: BTreeMap<Span, String>,
309 }
310
311 impl<'a, 'b, 'tcx> ImplicitHasherConstructorVisitor<'a, 'b, 'tcx> {
312     fn new(cx: &'a LateContext<'tcx>, target: &'b ImplicitHasherType<'tcx>) -> Self {
313         Self {
314             cx,
315             maybe_typeck_results: cx.maybe_typeck_results(),
316             target,
317             suggestions: BTreeMap::new(),
318         }
319     }
320 }
321
322 impl<'a, 'b, 'tcx> Visitor<'tcx> for ImplicitHasherConstructorVisitor<'a, 'b, 'tcx> {
323     type Map = Map<'tcx>;
324
325     fn visit_body(&mut self, body: &'tcx Body<'_>) {
326         let old_maybe_typeck_results = self.maybe_typeck_results.replace(self.cx.tcx.typeck_body(body.id()));
327         walk_body(self, body);
328         self.maybe_typeck_results = old_maybe_typeck_results;
329     }
330
331     fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
332         if_chain! {
333             if let ExprKind::Call(fun, args) = e.kind;
334             if let ExprKind::Path(QPath::TypeRelative(ty, method)) = fun.kind;
335             if let TyKind::Path(QPath::Resolved(None, ty_path)) = ty.kind;
336             if let Some(ty_did) = ty_path.res.opt_def_id();
337             then {
338                 if !TyS::same_type(self.target.ty(), self.maybe_typeck_results.unwrap().expr_ty(e)) {
339                     return;
340                 }
341
342                 if match_def_path(self.cx, ty_did, &paths::HASHMAP) {
343                     if method.ident.name == sym::new {
344                         self.suggestions
345                             .insert(e.span, "HashMap::default()".to_string());
346                     } else if method.ident.name == sym!(with_capacity) {
347                         self.suggestions.insert(
348                             e.span,
349                             format!(
350                                 "HashMap::with_capacity_and_hasher({}, Default::default())",
351                                 snippet(self.cx, args[0].span, "capacity"),
352                             ),
353                         );
354                     }
355                 } else if match_def_path(self.cx, ty_did, &paths::HASHSET) {
356                     if method.ident.name == sym::new {
357                         self.suggestions
358                             .insert(e.span, "HashSet::default()".to_string());
359                     } else if method.ident.name == sym!(with_capacity) {
360                         self.suggestions.insert(
361                             e.span,
362                             format!(
363                                 "HashSet::with_capacity_and_hasher({}, Default::default())",
364                                 snippet(self.cx, args[0].span, "capacity"),
365                             ),
366                         );
367                     }
368                 }
369             }
370         }
371
372         walk_expr(self, e);
373     }
374
375     fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
376         NestedVisitorMap::OnlyBodies(self.cx.tcx.hir())
377     }
378 }