From d513dc28de975aacc0d08185e721f722c9e462a1 Mon Sep 17 00:00:00 2001 From: Romain Perier Date: Wed, 23 Jul 2025 12:44:27 +0200 Subject: [PATCH] Add a note when a type implements a trait with the same name as the required one This is useful when you have two dependencies that use different trait for the same thing and with the same name. The user can accidentally implement the bad one which might be confusing. --- .../traits/fulfillment_errors.rs | 69 ++++++++++++++++++- tests/ui/traits/similarly_named_trait.rs | 27 ++++++++ tests/ui/traits/similarly_named_trait.stderr | 43 ++++++++++++ tests/ui/union/issue-81199.stderr | 1 + 4 files changed, 139 insertions(+), 1 deletion(-) create mode 100644 tests/ui/traits/similarly_named_trait.rs create mode 100644 tests/ui/traits/similarly_named_trait.stderr diff --git a/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs b/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs index 214a6e86d391a..df79f9d036b96 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs @@ -441,7 +441,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { span, leaf_trait_predicate, ); - self.note_version_mismatch(&mut err, leaf_trait_predicate); + self.note_version_mismatch(&mut err, &obligation, leaf_trait_predicate); self.suggest_remove_await(&obligation, &mut err); self.suggest_derive(&obligation, &mut err, leaf_trait_predicate); @@ -2337,6 +2337,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { fn note_version_mismatch( &self, err: &mut Diag<'_>, + obligation: &PredicateObligation<'tcx>, trait_pred: ty::PolyTraitPredicate<'tcx>, ) -> bool { let get_trait_impls = |trait_def_id| { @@ -2363,6 +2364,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { let traits_with_same_path = traits_with_same_path.into_items().into_sorted_stable_ord_by_key(|(p, _)| p); let mut suggested = false; + for (_, trait_with_same_path) in traits_with_same_path { let trait_impls = get_trait_impls(trait_with_same_path); if trait_impls.is_empty() { @@ -2380,6 +2382,71 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { err.note(crate_msg); suggested = true; } + + if suggested { + return true; + } + + let trait_def_id = trait_pred.def_id(); + let trait_name = self.tcx.item_name(trait_def_id); + let trait_krate_name = self.tcx.crate_name(trait_def_id.krate); + + // If there are multiple different versions of a crate in the dependency graph, there is already + // a suggestion designed for this purpose in the rustc_hir_typeck compiler crate + if self + .tcx + .all_traits_including_private() + .find(|def_id| { + def_id.krate != trait_def_id.krate + && self.tcx.crate_name(def_id.krate) == trait_krate_name + && self.tcx.item_name(def_id) == trait_name + }) + .is_some() + { + return false; + } + + let trait_has_same_params = |other_trait_def_id: DefId| -> bool { + let trait_generics = self.tcx.generics_of(trait_def_id); + let other_trait_generics = self.tcx.generics_of(other_trait_def_id); + + if trait_generics.count() != other_trait_generics.count() { + return false; + } + trait_generics.own_params.iter().zip(other_trait_generics.own_params.iter()).all( + |(a, b)| { + (matches!(a.kind, ty::GenericParamDefKind::Type { .. }) + && matches!(b.kind, ty::GenericParamDefKind::Type { .. })) + || (matches!(a.kind, ty::GenericParamDefKind::Lifetime,) + && matches!(b.kind, ty::GenericParamDefKind::Lifetime)) + || (matches!(a.kind, ty::GenericParamDefKind::Const { .. }) + && matches!(b.kind, ty::GenericParamDefKind::Const { .. })) + }, + ) + }; + let trait_name = self.tcx.item_name(trait_def_id); + if let Some(other_trait_def_id) = self.tcx.all_traits_including_private().find(|def_id| { + trait_def_id != *def_id + && trait_name == self.tcx.item_name(def_id) + && trait_has_same_params(*def_id) + && self.predicate_must_hold_modulo_regions(&Obligation::new( + self.tcx, + obligation.cause.clone(), + obligation.param_env, + trait_pred.map_bound(|tr| ty::TraitPredicate { + trait_ref: ty::TraitRef::new(self.tcx, *def_id, tr.trait_ref.args), + ..tr + }), + )) + }) { + err.note(format!( + "`{}` implements similarly named `{}`, but not `{}`", + trait_pred.self_ty(), + self.tcx.def_path_str(other_trait_def_id), + trait_pred.print_modifiers_and_trait_path() + )); + suggested = true; + } suggested } diff --git a/tests/ui/traits/similarly_named_trait.rs b/tests/ui/traits/similarly_named_trait.rs new file mode 100644 index 0000000000000..e49c6df97b505 --- /dev/null +++ b/tests/ui/traits/similarly_named_trait.rs @@ -0,0 +1,27 @@ +trait Trait {} //~ HELP this trait has no implementations, consider adding one +trait TraitWithParam {} //~ HELP this trait has no implementations, consider adding one + +mod m { + pub trait Trait {} + pub trait TraitWithParam {} + pub struct St; + impl Trait for St {} + impl TraitWithParam for St {} +} + +fn func(_: T) {} //~ NOTE required by a bound in `func` +//~^ NOTE required by this bound in `func` + +fn func2> (_: T) {} //~ NOTE required by a bound in `func2` +//~^ NOTE required by this bound in `func2` + +fn main() { + func(m::St); //~ ERROR the trait bound `St: Trait` is not satisfied + //~^ NOTE the trait `Trait` is not implemented for `St` + //~| NOTE required by a bound introduced by this call + //~| NOTE `St` implements similarly named `m::Trait`, but not `Trait` + func2(m::St); //~ ERROR the trait bound `St: TraitWithParam` is not satisfied + //~^ NOTE the trait `TraitWithParam` is not implemented for `St` + //~| NOTE required by a bound introduced by this call + //~| NOTE `St` implements similarly named `m::TraitWithParam`, but not `TraitWithParam` +} diff --git a/tests/ui/traits/similarly_named_trait.stderr b/tests/ui/traits/similarly_named_trait.stderr new file mode 100644 index 0000000000000..655ee34bbb981 --- /dev/null +++ b/tests/ui/traits/similarly_named_trait.stderr @@ -0,0 +1,43 @@ +error[E0277]: the trait bound `St: Trait` is not satisfied + --> $DIR/similarly_named_trait.rs:19:10 + | +LL | func(m::St); + | ---- ^^^^^ the trait `Trait` is not implemented for `St` + | | + | required by a bound introduced by this call + | + = note: `St` implements similarly named `m::Trait`, but not `Trait` +help: this trait has no implementations, consider adding one + --> $DIR/similarly_named_trait.rs:1:1 + | +LL | trait Trait {} + | ^^^^^^^^^^^ +note: required by a bound in `func` + --> $DIR/similarly_named_trait.rs:12:12 + | +LL | fn func(_: T) {} + | ^^^^^ required by this bound in `func` + +error[E0277]: the trait bound `St: TraitWithParam` is not satisfied + --> $DIR/similarly_named_trait.rs:23:11 + | +LL | func2(m::St); + | ----- ^^^^^ the trait `TraitWithParam` is not implemented for `St` + | | + | required by a bound introduced by this call + | + = note: `St` implements similarly named `m::TraitWithParam`, but not `TraitWithParam` +help: this trait has no implementations, consider adding one + --> $DIR/similarly_named_trait.rs:2:1 + | +LL | trait TraitWithParam {} + | ^^^^^^^^^^^^^^^^^^^^^^^ +note: required by a bound in `func2` + --> $DIR/similarly_named_trait.rs:15:13 + | +LL | fn func2> (_: T) {} + | ^^^^^^^^^^^^^^^^^ required by this bound in `func2` + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/union/issue-81199.stderr b/tests/ui/union/issue-81199.stderr index 7deba88fc803c..dbc4a0ab55346 100644 --- a/tests/ui/union/issue-81199.stderr +++ b/tests/ui/union/issue-81199.stderr @@ -16,6 +16,7 @@ error[E0277]: the trait bound `T: Pointee` is not satisfied LL | components: PtrComponents, | ^^^^^^^^^^^^^^^^ the trait `Pointee` is not implemented for `T` | + = note: `T` implements similarly named `std::ptr::Pointee`, but not `Pointee` note: required by a bound in `PtrComponents` --> $DIR/issue-81199.rs:11:25 |