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