From 9bad0aed39a82515428916338264c68c61d8a142 Mon Sep 17 00:00:00 2001 From: Jeremy Smart Date: Mon, 29 Sep 2025 23:45:19 -0400 Subject: [PATCH] recommend using a HashMap if a HashSet's second generic parameter doesn't implement BuildHasher --- .../rustc_hir_typeck/src/method/suggest.rs | 50 +++++++++++++++---- compiler/rustc_span/src/symbol.rs | 1 + library/core/src/hash/mod.rs | 1 + tests/ui/hashmap/hashset_generics.rs | 18 +++++++ tests/ui/hashmap/hashset_generics.stderr | 29 +++++++++++ 5 files changed, 89 insertions(+), 10 deletions(-) create mode 100644 tests/ui/hashmap/hashset_generics.rs create mode 100644 tests/ui/hashmap/hashset_generics.stderr diff --git a/compiler/rustc_hir_typeck/src/method/suggest.rs b/compiler/rustc_hir_typeck/src/method/suggest.rs index 024b9ee08c222..dc4cea55f2dfb 100644 --- a/compiler/rustc_hir_typeck/src/method/suggest.rs +++ b/compiler/rustc_hir_typeck/src/method/suggest.rs @@ -1337,6 +1337,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { but its trait bounds were not satisfied" ) }); + err.primary_message(primary_message); if let Some(label) = label { custom_span_label = true; @@ -1353,6 +1354,21 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { suggested_derive = self.suggest_derive(&mut err, unsatisfied_predicates); + if let ty::Adt(adt_def, _) = rcvr_ty.kind() + && self.tcx.is_diagnostic_item(sym::HashSet, adt_def.did()) + && unsatisfied_predicates.iter().any(|(pred, _parent, _cause)| { + if let ty::PredicateKind::Clause(ty::ClauseKind::Trait(pred)) = + pred.kind().skip_binder() + { + self.tcx.is_diagnostic_item(sym::BuildHasher, pred.def_id()) + } else { + false + } + }) + { + err.help("you might have intended to use a HashMap instead"); + } + unsatisfied_bounds = true; } } else if let ty::Adt(def, targs) = rcvr_ty.kind() @@ -2925,7 +2941,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { .filter_map(|e| match e.obligation.predicate.kind().skip_binder() { ty::PredicateKind::Clause(ty::ClauseKind::Trait(pred)) => { match pred.self_ty().kind() { - ty::Adt(_, _) => Some(pred), + ty::Adt(_, _) => Some((e.root_obligation.predicate, pred)), _ => None, } } @@ -2935,7 +2951,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // Note for local items and foreign items respectively. let (mut local_preds, mut foreign_preds): (Vec<_>, Vec<_>) = - preds.iter().partition(|&pred| { + preds.iter().partition(|&(_, pred)| { if let ty::Adt(def, _) = pred.self_ty().kind() { def.did().is_local() } else { @@ -2943,10 +2959,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } }); - local_preds.sort_by_key(|pred: &&ty::TraitPredicate<'_>| pred.trait_ref.to_string()); + local_preds.sort_by_key(|(_, pred)| pred.trait_ref.to_string()); let local_def_ids = local_preds .iter() - .filter_map(|pred| match pred.self_ty().kind() { + .filter_map(|(_, pred)| match pred.self_ty().kind() { ty::Adt(def, _) => Some(def.did()), _ => None, }) @@ -2959,7 +2975,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { }) .collect::>() .into(); - for pred in &local_preds { + for (_, pred) in &local_preds { if let ty::Adt(def, _) = pred.self_ty().kind() { local_spans.push_span_label( self.tcx.def_span(def.did()), @@ -2968,7 +2984,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } } if local_spans.primary_span().is_some() { - let msg = if let [local_pred] = local_preds.as_slice() { + let msg = if let [(_, local_pred)] = local_preds.as_slice() { format!( "an implementation of `{}` might be missing for `{}`", local_pred.trait_ref.print_trait_sugared(), @@ -2986,10 +3002,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { err.span_note(local_spans, msg); } - foreign_preds.sort_by_key(|pred: &&ty::TraitPredicate<'_>| pred.trait_ref.to_string()); + foreign_preds.sort_by_key(|(_, pred)| pred.trait_ref.to_string()); let foreign_def_ids = foreign_preds .iter() - .filter_map(|pred| match pred.self_ty().kind() { + .filter_map(|(_, pred)| match pred.self_ty().kind() { ty::Adt(def, _) => Some(def.did()), _ => None, }) @@ -3002,7 +3018,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { }) .collect::>() .into(); - for pred in &foreign_preds { + for (_, pred) in &foreign_preds { if let ty::Adt(def, _) = pred.self_ty().kind() { foreign_spans.push_span_label( self.tcx.def_span(def.did()), @@ -3011,7 +3027,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } } if foreign_spans.primary_span().is_some() { - let msg = if let [foreign_pred] = foreign_preds.as_slice() { + let msg = if let [(_, foreign_pred)] = foreign_preds.as_slice() { format!( "the foreign item type `{}` doesn't implement `{}`", foreign_pred.self_ty(), @@ -3027,6 +3043,20 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ) }; err.span_note(foreign_spans, msg); + + if foreign_preds.iter().any(|&(root_pred, pred)| { + if let ty::PredicateKind::Clause(ty::ClauseKind::Trait(root_pred)) = + root_pred.kind().skip_binder() + && let Some(root_adt) = root_pred.self_ty().ty_adt_def() + { + self.tcx.is_diagnostic_item(sym::HashSet, root_adt.did()) + && self.tcx.is_diagnostic_item(sym::BuildHasher, pred.def_id()) + } else { + false + } + }) { + err.help("you might have intended to use a HashMap instead"); + } } let preds: Vec<_> = errors diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 4e48c96afd622..8f9c9bdbc31c5 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -191,6 +191,7 @@ symbols! { Borrow, BorrowMut, Break, + BuildHasher, C, CStr, C_dash_unwind: "C-unwind", diff --git a/library/core/src/hash/mod.rs b/library/core/src/hash/mod.rs index a10c85640bbb6..c3f3cd7294254 100644 --- a/library/core/src/hash/mod.rs +++ b/library/core/src/hash/mod.rs @@ -633,6 +633,7 @@ impl Hasher for &mut H { /// /// [`build_hasher`]: BuildHasher::build_hasher /// [`HashMap`]: ../../std/collections/struct.HashMap.html +#[cfg_attr(not(test), rustc_diagnostic_item = "BuildHasher")] #[stable(since = "1.7.0", feature = "build_hasher")] pub trait BuildHasher { /// Type of the hasher that will be created. diff --git a/tests/ui/hashmap/hashset_generics.rs b/tests/ui/hashmap/hashset_generics.rs new file mode 100644 index 0000000000000..85a31088f8f09 --- /dev/null +++ b/tests/ui/hashmap/hashset_generics.rs @@ -0,0 +1,18 @@ +use std::collections::HashSet; + +#[derive(PartialEq)] +//~^ NOTE in this expansion of +//~| NOTE in this expansion of +//~| NOTE in this expansion of +pub struct MyStruct { + pub parameters: HashSet, + //~^ NOTE the foreign item type + //~| ERROR binary operation +} + +fn main() { + let h1 = HashSet::::with_hasher(0); + h1.insert(1); + //~^ ERROR its trait bounds were not satisfied + //~| NOTE the following trait bounds +} diff --git a/tests/ui/hashmap/hashset_generics.stderr b/tests/ui/hashmap/hashset_generics.stderr new file mode 100644 index 0000000000000..0d80fa6f2498c --- /dev/null +++ b/tests/ui/hashmap/hashset_generics.stderr @@ -0,0 +1,29 @@ +error[E0369]: binary operation `==` cannot be applied to type `HashSet` + --> $DIR/hashset_generics.rs:8:5 + | +LL | #[derive(PartialEq)] + | --------- in this derive macro expansion +... +LL | pub parameters: HashSet, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +note: the foreign item type `String` doesn't implement `BuildHasher` + --> $SRC_DIR/alloc/src/string.rs:LL:COL + | + = note: not implement `BuildHasher` + = help: you might have intended to use a HashMap instead + +error[E0599]: the method `insert` exists for struct `HashSet`, but its trait bounds were not satisfied + --> $DIR/hashset_generics.rs:15:8 + | +LL | h1.insert(1); + | ^^^^^^ + | + = note: the following trait bounds were not satisfied: + `usize: BuildHasher` + = help: you might have intended to use a HashMap instead + +error: aborting due to 2 previous errors + +Some errors have detailed explanations: E0369, E0599. +For more information about an error, try `rustc --explain E0369`.