2 use std
::collections
::BTreeMap
;
4 use rustc_errors
::Diagnostic
;
6 use rustc_hir
::intravisit
::{walk_body, walk_expr, walk_inf, walk_ty, 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
::nested_filter
;
10 use rustc_middle
::lint
::in_external_macro
;
11 use rustc_middle
::ty
::{Ty, 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
::source
::{snippet, snippet_opt}
;
21 use clippy_utils
::ty
::is_type_diagnostic_item
;
23 declare_clippy_lint
! {
25 /// Checks for public `impl` or `fn` missing generalization
26 /// over different hashers and implicitly defaulting to the default hashing
27 /// algorithm (`SipHash`).
29 /// ### Why is this bad?
30 /// `HashMap` or `HashSet` with custom hashers cannot be
33 /// ### Known problems
34 /// Suggestions for replacing constructors can contain
35 /// false-positives. Also applying suggestions can require modification of other
36 /// pieces of code, possibly including external crates.
40 /// # use std::collections::HashMap;
41 /// # use std::hash::{Hash, BuildHasher};
42 /// # trait Serialize {};
43 /// impl<K: Hash + Eq, V> Serialize for HashMap<K, V> { }
45 /// pub fn foo(map: &mut HashMap<i32, i32>) { }
47 /// could be rewritten as
49 /// # use std::collections::HashMap;
50 /// # use std::hash::{Hash, BuildHasher};
51 /// # trait Serialize {};
52 /// impl<K: Hash + Eq, V, S: BuildHasher> Serialize for HashMap<K, V, S> { }
54 /// pub fn foo<S: BuildHasher>(map: &mut HashMap<i32, i32, S>) { }
56 #[clippy::version = "pre 1.29.0"]
59 "missing generalization over different hashers"
62 declare_lint_pass
!(ImplicitHasher
=> [IMPLICIT_HASHER
]);
64 impl<'tcx
> LateLintPass
<'tcx
> for ImplicitHasher
{
65 #[expect(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
;
70 cx
: &LateContext
<'tcx
>,
71 diag
: &mut Diagnostic
,
73 generics_suggestion_span
: Span
,
74 target
: &ImplicitHasherType
<'_
>,
75 vis
: ImplicitHasherConstructorVisitor
<'_
, '_
, '_
>,
77 let generics_snip
= snippet(cx
, generics_span
, "");
79 let generics_snip
= if generics_snip
.is_empty() {
82 &generics_snip
[1..generics_snip
.len() - 1]
87 "consider adding a type parameter",
90 generics_suggestion_span
,
92 "<{}{}S: ::std::hash::BuildHasher{}>",
94 if generics_snip
.is_empty() { "" }
else { ", " }
,
95 if vis
.suggestions
.is_empty() {
98 // request users to add `Default` bound so that generic constructors can be used
105 format
!("{}<{}, S>", target
.type_name(), target
.type_arguments(),),
110 if !vis
.suggestions
.is_empty() {
111 multispan_sugg(diag
, "...and use generic constructor", vis
.suggestions
);
115 if !cx
.access_levels
.is_exported(item
.def_id
) {
120 ItemKind
::Impl(impl_
) => {
121 let mut vis
= ImplicitHasherTypeVisitor
::new(cx
);
122 vis
.visit_ty(impl_
.self_ty
);
124 for target
in &vis
.found
{
125 if item
.span
.ctxt() != target
.span().ctxt() {
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())
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
);
149 "impl for `{}` should be generalized over different hashers",
153 suggestion(cx
, diag
, impl_
.generics
.span
, generics_suggestion_span
, target
, ctr_vis
);
158 ItemKind
::Fn(ref sig
, generics
, body_id
) => {
159 let body
= cx
.tcx
.hir().body(body_id
);
161 for ty
in sig
.decl
.inputs
{
162 let mut vis
= ImplicitHasherTypeVisitor
::new(cx
);
165 for target
in &vis
.found
{
166 if in_external_macro(cx
.sess(), generics
.span
) {
169 let generics_suggestion_span
= generics
.span
.substitute_dummy({
170 let pos
= snippet_opt(
174 body
.params
[0].pat
.span
.lo(),
180 let i
= snip
.find("fn")?
;
181 Some(item
.span
.lo() + BytePos((i
+ snip
[i
..].find('
('
)?
) as u32))
183 .expect("failed to create span for type parameters");
184 Span
::new(pos
, pos
, item
.span
.ctxt(), item
.span
.parent())
187 let mut ctr_vis
= ImplicitHasherConstructorVisitor
::new(cx
, target
);
188 ctr_vis
.visit_body(body
);
195 "parameter of type `{}` should be generalized over different hashers",
199 suggestion(cx
, diag
, generics
.span
, generics_suggestion_span
, target
, ctr_vis
);
210 enum ImplicitHasherType
<'tcx
> {
211 HashMap(Span
, Ty
<'tcx
>, Cow
<'
static, str>, Cow
<'
static, str>),
212 HashSet(Span
, Ty
<'tcx
>, Cow
<'
static, str>),
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
227 .filter_map(|arg
| match arg
{
228 GenericArg
::Type(ty
) => Some(ty
),
232 let params_len
= params
.len();
234 let ty
= hir_ty_to_ty(cx
.tcx
, hir_ty
);
236 if is_type_diagnostic_item(cx
, ty
, sym
::HashMap
) && params_len
== 2 {
237 Some(ImplicitHasherType
::HashMap(
240 snippet(cx
, params
[0].span
, "K"),
241 snippet(cx
, params
[1].span
, "V"),
243 } else if is_type_diagnostic_item(cx
, ty
, sym
::HashSet
) && params_len
== 1 {
244 Some(ImplicitHasherType
::HashSet(
247 snippet(cx
, params
[0].span
, "T"),
257 fn type_name(&self) -> &'
static str {
259 ImplicitHasherType
::HashMap(..) => "HashMap",
260 ImplicitHasherType
::HashSet(..) => "HashSet",
264 fn type_arguments(&self) -> String
{
266 ImplicitHasherType
::HashMap(.., ref k
, ref v
) => format
!("{}, {}", k
, v
),
267 ImplicitHasherType
::HashSet(.., ref t
) => format
!("{}", t
),
271 fn ty(&self) -> Ty
<'tcx
> {
273 ImplicitHasherType
::HashMap(_
, ty
, ..) | ImplicitHasherType
::HashSet(_
, ty
, ..) => ty
,
277 fn span(&self) -> Span
{
279 ImplicitHasherType
::HashMap(span
, ..) | ImplicitHasherType
::HashSet(span
, ..) => span
,
284 struct ImplicitHasherTypeVisitor
<'a
, 'tcx
> {
285 cx
: &'a LateContext
<'tcx
>,
286 found
: Vec
<ImplicitHasherType
<'tcx
>>,
289 impl<'a
, 'tcx
> ImplicitHasherTypeVisitor
<'a
, 'tcx
> {
290 fn new(cx
: &'a LateContext
<'tcx
>) -> Self {
291 Self { cx, found: vec![] }
295 impl<'a
, 'tcx
> Visitor
<'tcx
> for ImplicitHasherTypeVisitor
<'a
, 'tcx
> {
296 fn visit_ty(&mut self, t
: &'tcx hir
::Ty
<'_
>) {
297 if let Some(target
) = ImplicitHasherType
::new(self.cx
, t
) {
298 self.found
.push(target
);
304 fn visit_infer(&mut self, inf
: &'tcx hir
::InferArg
) {
305 if let Some(target
) = ImplicitHasherType
::new(self.cx
, &inf
.to_ty()) {
306 self.found
.push(target
);
313 /// Looks for default-hasher-dependent constructors like `HashMap::new`.
314 struct ImplicitHasherConstructorVisitor
<'a
, 'b
, 'tcx
> {
315 cx
: &'a LateContext
<'tcx
>,
316 maybe_typeck_results
: Option
<&'tcx TypeckResults
<'tcx
>>,
317 target
: &'b ImplicitHasherType
<'tcx
>,
318 suggestions
: BTreeMap
<Span
, String
>,
321 impl<'a
, 'b
, 'tcx
> ImplicitHasherConstructorVisitor
<'a
, 'b
, 'tcx
> {
322 fn new(cx
: &'a LateContext
<'tcx
>, target
: &'b ImplicitHasherType
<'tcx
>) -> Self {
325 maybe_typeck_results
: cx
.maybe_typeck_results(),
327 suggestions
: BTreeMap
::new(),
332 impl<'a
, 'b
, 'tcx
> Visitor
<'tcx
> for ImplicitHasherConstructorVisitor
<'a
, 'b
, 'tcx
> {
333 type NestedFilter
= nested_filter
::OnlyBodies
;
335 fn visit_body(&mut self, body
: &'tcx Body
<'_
>) {
336 let old_maybe_typeck_results
= self.maybe_typeck_results
.replace(self.cx
.tcx
.typeck_body(body
.id()));
337 walk_body(self, body
);
338 self.maybe_typeck_results
= old_maybe_typeck_results
;
341 fn visit_expr(&mut self, e
: &'tcx Expr
<'_
>) {
343 if let ExprKind
::Call(fun
, args
) = e
.kind
;
344 if let ExprKind
::Path(QPath
::TypeRelative(ty
, method
)) = fun
.kind
;
345 if let TyKind
::Path(QPath
::Resolved(None
, ty_path
)) = ty
.kind
;
346 if let Some(ty_did
) = ty_path
.res
.opt_def_id();
348 if self.target
.ty() != self.maybe_typeck_results
.unwrap().expr_ty(e
) {
352 if self.cx
.tcx
.is_diagnostic_item(sym
::HashMap
, ty_did
) {
353 if method
.ident
.name
== sym
::new
{
355 .insert(e
.span
, "HashMap::default()".to_string());
356 } else if method
.ident
.name
== sym
!(with_capacity
) {
357 self.suggestions
.insert(
360 "HashMap::with_capacity_and_hasher({}, Default::default())",
361 snippet(self.cx
, args
[0].span
, "capacity"),
365 } else if self.cx
.tcx
.is_diagnostic_item(sym
::HashSet
, ty_did
) {
366 if method
.ident
.name
== sym
::new
{
368 .insert(e
.span
, "HashSet::default()".to_string());
369 } else if method
.ident
.name
== sym
!(with_capacity
) {
370 self.suggestions
.insert(
373 "HashSet::with_capacity_and_hasher({}, Default::default())",
374 snippet(self.cx
, args
[0].span
, "capacity"),
385 fn nested_visit_map(&mut self) -> Self::Map
{