2 use std::collections::BTreeMap;
4 use rustc_errors::DiagnosticBuilder;
6 use rustc_hir::intravisit::{walk_body, walk_expr, 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;
17 use if_chain::if_chain;
19 use clippy_utils::diagnostics::{multispan_sugg, span_lint_and_then};
20 use clippy_utils::paths;
21 use clippy_utils::source::{snippet, snippet_opt};
22 use clippy_utils::ty::is_type_diagnostic_item;
23 use clippy_utils::{differing_macro_contexts, match_def_path};
25 declare_clippy_lint! {
26 /// **What it does:** Checks for public `impl` or `fn` missing generalization
27 /// over different hashers and implicitly defaulting to the default hashing
28 /// algorithm (`SipHash`).
30 /// **Why is this bad?** `HashMap` or `HashSet` with custom hashers cannot be
33 /// **Known problems:** Suggestions for replacing constructors can contain
34 /// false-positives. Also applying suggestions can require modification of other
35 /// pieces of code, possibly including external crates.
39 /// # use std::collections::HashMap;
40 /// # use std::hash::{Hash, BuildHasher};
41 /// # trait Serialize {};
42 /// impl<K: Hash + Eq, V> Serialize for HashMap<K, V> { }
44 /// pub fn foo(map: &mut HashMap<i32, i32>) { }
46 /// could be rewritten as
48 /// # use std::collections::HashMap;
49 /// # use std::hash::{Hash, BuildHasher};
50 /// # trait Serialize {};
51 /// impl<K: Hash + Eq, V, S: BuildHasher> Serialize for HashMap<K, V, S> { }
53 /// pub fn foo<S: BuildHasher>(map: &mut HashMap<i32, i32, S>) { }
57 "missing generalization over different hashers"
60 declare_lint_pass!(ImplicitHasher => [IMPLICIT_HASHER]);
62 impl<'tcx> LateLintPass<'tcx> for ImplicitHasher {
63 #[allow(clippy::cast_possible_truncation, clippy::too_many_lines)]
64 fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
65 use rustc_span::BytePos;
68 cx: &LateContext<'tcx>,
69 diag: &mut DiagnosticBuilder<'_>,
71 generics_suggestion_span: Span,
72 target: &ImplicitHasherType<'_>,
73 vis: ImplicitHasherConstructorVisitor<'_, '_, '_>,
75 let generics_snip = snippet(cx, generics_span, "");
77 let generics_snip = if generics_snip.is_empty() {
80 &generics_snip[1..generics_snip.len() - 1]
85 "consider adding a type parameter",
88 generics_suggestion_span,
90 "<{}{}S: ::std::hash::BuildHasher{}>",
92 if generics_snip.is_empty() { "" } else { ", " },
93 if vis.suggestions.is_empty() {
96 // request users to add `Default` bound so that generic constructors can be used
103 format!("{}<{}, S>", target.type_name(), target.type_arguments(),),
108 if !vis.suggestions.is_empty() {
109 multispan_sugg(diag, "...and use generic constructor", vis.suggestions);
113 if !cx.access_levels.is_exported(item.hir_id()) {
118 ItemKind::Impl(ref impl_) => {
119 let mut vis = ImplicitHasherTypeVisitor::new(cx);
120 vis.visit_ty(impl_.self_ty);
122 for target in &vis.found {
123 if differing_macro_contexts(item.span, target.span()) {
127 let generics_suggestion_span = impl_.generics.span.substitute_dummy({
128 let pos = snippet_opt(cx, item.span.until(target.span()))
129 .and_then(|snip| Some(item.span.lo() + BytePos(snip.find("impl")? as u32 + 4)));
130 if let Some(pos) = pos {
131 Span::new(pos, pos, item.span.data().ctxt)
137 let mut ctr_vis = ImplicitHasherConstructorVisitor::new(cx, target);
138 for item in impl_.items.iter().map(|item| cx.tcx.hir().impl_item(item.id)) {
139 ctr_vis.visit_impl_item(item);
147 "impl for `{}` should be generalized over different hashers",
151 suggestion(cx, diag, impl_.generics.span, generics_suggestion_span, target, ctr_vis);
156 ItemKind::Fn(ref sig, ref generics, body_id) => {
157 let body = cx.tcx.hir().body(body_id);
159 for ty in sig.decl.inputs {
160 let mut vis = ImplicitHasherTypeVisitor::new(cx);
163 for target in &vis.found {
164 if in_external_macro(cx.sess(), generics.span) {
167 let generics_suggestion_span = generics.span.substitute_dummy({
168 let pos = snippet_opt(cx, item.span.until(body.params[0].pat.span))
170 let i = snip.find("fn")?;
171 Some(item.span.lo() + BytePos((i + (&snip[i..]).find('(')?) as u32))
173 .expect("failed to create span for type parameters");
174 Span::new(pos, pos, item.span.data().ctxt)
177 let mut ctr_vis = ImplicitHasherConstructorVisitor::new(cx, target);
178 ctr_vis.visit_body(body);
185 "parameter of type `{}` should be generalized over different hashers",
189 suggestion(cx, diag, generics.span, generics_suggestion_span, target, ctr_vis);
200 enum ImplicitHasherType<'tcx> {
201 HashMap(Span, Ty<'tcx>, Cow<'static, str>, Cow<'static, str>),
202 HashSet(Span, Ty<'tcx>, Cow<'static, str>),
205 impl<'tcx> ImplicitHasherType<'tcx> {
206 /// Checks that `ty` is a target type without a `BuildHasher`.
207 fn new(cx: &LateContext<'tcx>, hir_ty: &hir::Ty<'_>) -> Option<Self> {
208 if let TyKind::Path(QPath::Resolved(None, path)) = hir_ty.kind {
209 let params: Vec<_> = path
217 .filter_map(|arg| match arg {
218 GenericArg::Type(ty) => Some(ty),
222 let params_len = params.len();
224 let ty = hir_ty_to_ty(cx.tcx, hir_ty);
226 if is_type_diagnostic_item(cx, ty, sym::hashmap_type) && params_len == 2 {
227 Some(ImplicitHasherType::HashMap(
230 snippet(cx, params[0].span, "K"),
231 snippet(cx, params[1].span, "V"),
233 } else if is_type_diagnostic_item(cx, ty, sym::hashset_type) && params_len == 1 {
234 Some(ImplicitHasherType::HashSet(
237 snippet(cx, params[0].span, "T"),
247 fn type_name(&self) -> &'static str {
249 ImplicitHasherType::HashMap(..) => "HashMap",
250 ImplicitHasherType::HashSet(..) => "HashSet",
254 fn type_arguments(&self) -> String {
256 ImplicitHasherType::HashMap(.., ref k, ref v) => format!("{}, {}", k, v),
257 ImplicitHasherType::HashSet(.., ref t) => format!("{}", t),
261 fn ty(&self) -> Ty<'tcx> {
263 ImplicitHasherType::HashMap(_, ty, ..) | ImplicitHasherType::HashSet(_, ty, ..) => ty,
267 fn span(&self) -> Span {
269 ImplicitHasherType::HashMap(span, ..) | ImplicitHasherType::HashSet(span, ..) => span,
274 struct ImplicitHasherTypeVisitor<'a, 'tcx> {
275 cx: &'a LateContext<'tcx>,
276 found: Vec<ImplicitHasherType<'tcx>>,
279 impl<'a, 'tcx> ImplicitHasherTypeVisitor<'a, 'tcx> {
280 fn new(cx: &'a LateContext<'tcx>) -> Self {
281 Self { cx, found: vec![] }
285 impl<'a, 'tcx> Visitor<'tcx> for ImplicitHasherTypeVisitor<'a, 'tcx> {
286 type Map = Map<'tcx>;
288 fn visit_ty(&mut self, t: &'tcx hir::Ty<'_>) {
289 if let Some(target) = ImplicitHasherType::new(self.cx, t) {
290 self.found.push(target);
296 fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
297 NestedVisitorMap::None
301 /// Looks for default-hasher-dependent constructors like `HashMap::new`.
302 struct ImplicitHasherConstructorVisitor<'a, 'b, 'tcx> {
303 cx: &'a LateContext<'tcx>,
304 maybe_typeck_results: Option<&'tcx TypeckResults<'tcx>>,
305 target: &'b ImplicitHasherType<'tcx>,
306 suggestions: BTreeMap<Span, String>,
309 impl<'a, 'b, 'tcx> ImplicitHasherConstructorVisitor<'a, 'b, 'tcx> {
310 fn new(cx: &'a LateContext<'tcx>, target: &'b ImplicitHasherType<'tcx>) -> Self {
313 maybe_typeck_results: cx.maybe_typeck_results(),
315 suggestions: BTreeMap::new(),
320 impl<'a, 'b, 'tcx> Visitor<'tcx> for ImplicitHasherConstructorVisitor<'a, 'b, 'tcx> {
321 type Map = Map<'tcx>;
323 fn visit_body(&mut self, body: &'tcx Body<'_>) {
324 let old_maybe_typeck_results = self.maybe_typeck_results.replace(self.cx.tcx.typeck_body(body.id()));
325 walk_body(self, body);
326 self.maybe_typeck_results = old_maybe_typeck_results;
329 fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
331 if let ExprKind::Call(fun, args) = e.kind;
332 if let ExprKind::Path(QPath::TypeRelative(ty, method)) = fun.kind;
333 if let TyKind::Path(QPath::Resolved(None, ty_path)) = ty.kind;
334 if let Some(ty_did) = ty_path.res.opt_def_id();
336 if !TyS::same_type(self.target.ty(), self.maybe_typeck_results.unwrap().expr_ty(e)) {
340 if match_def_path(self.cx, ty_did, &paths::HASHMAP) {
341 if method.ident.name == sym::new {
343 .insert(e.span, "HashMap::default()".to_string());
344 } else if method.ident.name == sym!(with_capacity) {
345 self.suggestions.insert(
348 "HashMap::with_capacity_and_hasher({}, Default::default())",
349 snippet(self.cx, args[0].span, "capacity"),
353 } else if match_def_path(self.cx, ty_did, &paths::HASHSET) {
354 if method.ident.name == sym::new {
356 .insert(e.span, "HashSet::default()".to_string());
357 } else if method.ident.name == sym!(with_capacity) {
358 self.suggestions.insert(
361 "HashSet::with_capacity_and_hasher({}, Default::default())",
362 snippet(self.cx, args[0].span, "capacity"),
373 fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
374 NestedVisitorMap::OnlyBodies(self.cx.tcx.hir())