]> git.lizzy.rs Git - rust.git/blob - src/tools/clippy/clippy_lints/src/implicit_hasher.rs
Auto merge of #91284 - t6:freebsd-riscv64, r=Amanieu
[rust.git] / src / tools / clippy / clippy_lints / src / implicit_hasher.rs
1 use std::borrow::Cow;
2 use std::collections::BTreeMap;
3
4 use rustc_errors::DiagnosticBuilder;
5 use rustc_hir as hir;
6 use rustc_hir::intravisit::{walk_body, walk_expr, walk_inf, walk_ty, NestedVisitorMap, Visitor};
7 use rustc_hir::{Body, Expr, ExprKind, GenericArg, Item, ItemKind, QPath, TyKind};
8 use rustc_lint::{LateContext, LateLintPass, LintContext};
9 use rustc_middle::hir::map::Map;
10 use rustc_middle::lint::in_external_macro;
11 use rustc_middle::ty::{Ty, TyS, TypeckResults};
12 use rustc_session::{declare_lint_pass, declare_tool_lint};
13 use rustc_span::source_map::Span;
14 use rustc_span::symbol::sym;
15 use rustc_typeck::hir_ty_to_ty;
16
17 use if_chain::if_chain;
18
19 use clippy_utils::diagnostics::{multispan_sugg, span_lint_and_then};
20 use clippy_utils::differing_macro_contexts;
21 use clippy_utils::source::{snippet, snippet_opt};
22 use clippy_utils::ty::is_type_diagnostic_item;
23
24 declare_clippy_lint! {
25     /// ### What it does
26     /// Checks for public `impl` or `fn` missing generalization
27     /// over different hashers and implicitly defaulting to the default hashing
28     /// algorithm (`SipHash`).
29     ///
30     /// ### Why is this bad?
31     /// `HashMap` or `HashSet` with custom hashers cannot be
32     /// used with them.
33     ///
34     /// ### Known problems
35     /// 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.def_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.ctxt(), item.span.parent())
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(
171                                 cx,
172                                 Span::new(
173                                     item.span.lo(),
174                                     body.params[0].pat.span.lo(),
175                                     item.span.ctxt(),
176                                     item.span.parent(),
177                                 ),
178                             )
179                             .and_then(|snip| {
180                                 let i = snip.find("fn")?;
181                                 Some(item.span.lo() + BytePos((i + (&snip[i..]).find('(')?) as u32))
182                             })
183                             .expect("failed to create span for type parameters");
184                             Span::new(pos, pos, item.span.ctxt(), item.span.parent())
185                         });
186
187                         let mut ctr_vis = ImplicitHasherConstructorVisitor::new(cx, target);
188                         ctr_vis.visit_body(body);
189
190                         span_lint_and_then(
191                             cx,
192                             IMPLICIT_HASHER,
193                             target.span(),
194                             &format!(
195                                 "parameter of type `{}` should be generalized over different hashers",
196                                 target.type_name()
197                             ),
198                             move |diag| {
199                                 suggestion(cx, diag, generics.span, generics_suggestion_span, target, ctr_vis);
200                             },
201                         );
202                     }
203                 }
204             },
205             _ => {},
206         }
207     }
208 }
209
210 enum ImplicitHasherType<'tcx> {
211     HashMap(Span, Ty<'tcx>, Cow<'static, str>, Cow<'static, str>),
212     HashSet(Span, Ty<'tcx>, Cow<'static, str>),
213 }
214
215 impl<'tcx> ImplicitHasherType<'tcx> {
216     /// Checks that `ty` is a target type without a `BuildHasher`.
217     fn new(cx: &LateContext<'tcx>, hir_ty: &hir::Ty<'_>) -> Option<Self> {
218         if let TyKind::Path(QPath::Resolved(None, path)) = hir_ty.kind {
219             let params: Vec<_> = path
220                 .segments
221                 .last()
222                 .as_ref()?
223                 .args
224                 .as_ref()?
225                 .args
226                 .iter()
227                 .filter_map(|arg| match arg {
228                     GenericArg::Type(ty) => Some(ty),
229                     _ => None,
230                 })
231                 .collect();
232             let params_len = params.len();
233
234             let ty = hir_ty_to_ty(cx.tcx, hir_ty);
235
236             if is_type_diagnostic_item(cx, ty, sym::HashMap) && params_len == 2 {
237                 Some(ImplicitHasherType::HashMap(
238                     hir_ty.span,
239                     ty,
240                     snippet(cx, params[0].span, "K"),
241                     snippet(cx, params[1].span, "V"),
242                 ))
243             } else if is_type_diagnostic_item(cx, ty, sym::HashSet) && params_len == 1 {
244                 Some(ImplicitHasherType::HashSet(
245                     hir_ty.span,
246                     ty,
247                     snippet(cx, params[0].span, "T"),
248                 ))
249             } else {
250                 None
251             }
252         } else {
253             None
254         }
255     }
256
257     fn type_name(&self) -> &'static str {
258         match *self {
259             ImplicitHasherType::HashMap(..) => "HashMap",
260             ImplicitHasherType::HashSet(..) => "HashSet",
261         }
262     }
263
264     fn type_arguments(&self) -> String {
265         match *self {
266             ImplicitHasherType::HashMap(.., ref k, ref v) => format!("{}, {}", k, v),
267             ImplicitHasherType::HashSet(.., ref t) => format!("{}", t),
268         }
269     }
270
271     fn ty(&self) -> Ty<'tcx> {
272         match *self {
273             ImplicitHasherType::HashMap(_, ty, ..) | ImplicitHasherType::HashSet(_, ty, ..) => ty,
274         }
275     }
276
277     fn span(&self) -> Span {
278         match *self {
279             ImplicitHasherType::HashMap(span, ..) | ImplicitHasherType::HashSet(span, ..) => span,
280         }
281     }
282 }
283
284 struct ImplicitHasherTypeVisitor<'a, 'tcx> {
285     cx: &'a LateContext<'tcx>,
286     found: Vec<ImplicitHasherType<'tcx>>,
287 }
288
289 impl<'a, 'tcx> ImplicitHasherTypeVisitor<'a, 'tcx> {
290     fn new(cx: &'a LateContext<'tcx>) -> Self {
291         Self { cx, found: vec![] }
292     }
293 }
294
295 impl<'a, 'tcx> Visitor<'tcx> for ImplicitHasherTypeVisitor<'a, 'tcx> {
296     type Map = Map<'tcx>;
297
298     fn visit_ty(&mut self, t: &'tcx hir::Ty<'_>) {
299         if let Some(target) = ImplicitHasherType::new(self.cx, t) {
300             self.found.push(target);
301         }
302
303         walk_ty(self, t);
304     }
305
306     fn visit_infer(&mut self, inf: &'tcx hir::InferArg) {
307         if let Some(target) = ImplicitHasherType::new(self.cx, &inf.to_ty()) {
308             self.found.push(target);
309         }
310
311         walk_inf(self, inf);
312     }
313
314     fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
315         NestedVisitorMap::None
316     }
317 }
318
319 /// Looks for default-hasher-dependent constructors like `HashMap::new`.
320 struct ImplicitHasherConstructorVisitor<'a, 'b, 'tcx> {
321     cx: &'a LateContext<'tcx>,
322     maybe_typeck_results: Option<&'tcx TypeckResults<'tcx>>,
323     target: &'b ImplicitHasherType<'tcx>,
324     suggestions: BTreeMap<Span, String>,
325 }
326
327 impl<'a, 'b, 'tcx> ImplicitHasherConstructorVisitor<'a, 'b, 'tcx> {
328     fn new(cx: &'a LateContext<'tcx>, target: &'b ImplicitHasherType<'tcx>) -> Self {
329         Self {
330             cx,
331             maybe_typeck_results: cx.maybe_typeck_results(),
332             target,
333             suggestions: BTreeMap::new(),
334         }
335     }
336 }
337
338 impl<'a, 'b, 'tcx> Visitor<'tcx> for ImplicitHasherConstructorVisitor<'a, 'b, 'tcx> {
339     type Map = Map<'tcx>;
340
341     fn visit_body(&mut self, body: &'tcx Body<'_>) {
342         let old_maybe_typeck_results = self.maybe_typeck_results.replace(self.cx.tcx.typeck_body(body.id()));
343         walk_body(self, body);
344         self.maybe_typeck_results = old_maybe_typeck_results;
345     }
346
347     fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
348         if_chain! {
349             if let ExprKind::Call(fun, args) = e.kind;
350             if let ExprKind::Path(QPath::TypeRelative(ty, method)) = fun.kind;
351             if let TyKind::Path(QPath::Resolved(None, ty_path)) = ty.kind;
352             if let Some(ty_did) = ty_path.res.opt_def_id();
353             then {
354                 if !TyS::same_type(self.target.ty(), self.maybe_typeck_results.unwrap().expr_ty(e)) {
355                     return;
356                 }
357
358                 if self.cx.tcx.is_diagnostic_item(sym::HashMap, ty_did) {
359                     if method.ident.name == sym::new {
360                         self.suggestions
361                             .insert(e.span, "HashMap::default()".to_string());
362                     } else if method.ident.name == sym!(with_capacity) {
363                         self.suggestions.insert(
364                             e.span,
365                             format!(
366                                 "HashMap::with_capacity_and_hasher({}, Default::default())",
367                                 snippet(self.cx, args[0].span, "capacity"),
368                             ),
369                         );
370                     }
371                 } else if self.cx.tcx.is_diagnostic_item(sym::HashSet, ty_did) {
372                     if method.ident.name == sym::new {
373                         self.suggestions
374                             .insert(e.span, "HashSet::default()".to_string());
375                     } else if method.ident.name == sym!(with_capacity) {
376                         self.suggestions.insert(
377                             e.span,
378                             format!(
379                                 "HashSet::with_capacity_and_hasher({}, Default::default())",
380                                 snippet(self.cx, args[0].span, "capacity"),
381                             ),
382                         );
383                     }
384                 }
385             }
386         }
387
388         walk_expr(self, e);
389     }
390
391     fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
392         NestedVisitorMap::OnlyBodies(self.cx.tcx.hir())
393     }
394 }