]> git.proxmox.com Git - rustc.git/blame - src/tools/clippy/clippy_lints/src/implicit_hasher.rs
New upstream version 1.55.0+dfsg1
[rustc.git] / src / tools / clippy / clippy_lints / src / implicit_hasher.rs
CommitLineData
136023e0 1#![cfg_attr(bootstrap, allow(rustc::default_hash_types))]
cdc7bbd5
XL
2
3use std::borrow::Cow;
4use std::collections::BTreeMap;
5
6use rustc_errors::DiagnosticBuilder;
7use rustc_hir as hir;
8use rustc_hir::intravisit::{walk_body, walk_expr, walk_ty, NestedVisitorMap, Visitor};
9use rustc_hir::{Body, Expr, ExprKind, GenericArg, Item, ItemKind, QPath, TyKind};
10use rustc_lint::{LateContext, LateLintPass, LintContext};
11use rustc_middle::hir::map::Map;
12use rustc_middle::lint::in_external_macro;
13use rustc_middle::ty::{Ty, TyS, TypeckResults};
14use rustc_session::{declare_lint_pass, declare_tool_lint};
15use rustc_span::source_map::Span;
16use rustc_span::symbol::sym;
17use rustc_typeck::hir_ty_to_ty;
18
19use if_chain::if_chain;
20
21use clippy_utils::diagnostics::{multispan_sugg, span_lint_and_then};
22use clippy_utils::paths;
23use clippy_utils::source::{snippet, snippet_opt};
24use clippy_utils::ty::is_type_diagnostic_item;
25use clippy_utils::{differing_macro_contexts, match_def_path};
26
27declare_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
62declare_lint_pass!(ImplicitHasher => [IMPLICIT_HASHER]);
63
64impl<'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
202enum ImplicitHasherType<'tcx> {
203 HashMap(Span, Ty<'tcx>, Cow<'static, str>, Cow<'static, str>),
204 HashSet(Span, Ty<'tcx>, Cow<'static, str>),
205}
206
207impl<'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
276struct ImplicitHasherTypeVisitor<'a, 'tcx> {
277 cx: &'a LateContext<'tcx>,
278 found: Vec<ImplicitHasherType<'tcx>>,
279}
280
281impl<'a, 'tcx> ImplicitHasherTypeVisitor<'a, 'tcx> {
282 fn new(cx: &'a LateContext<'tcx>) -> Self {
283 Self { cx, found: vec![] }
284 }
285}
286
287impl<'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`.
304struct 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
311impl<'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
322impl<'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}