Skip to content

Commit d2ecc57

Browse files
committed
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. This commits refactorizes existing diagnostics about multiple different crates with the same version and adds a note when similarly named traits are found. Both diagnostics are merged into a single function.
1 parent e100792 commit d2ecc57

File tree

11 files changed

+279
-247
lines changed

11 files changed

+279
-247
lines changed

compiler/rustc_hir_typeck/src/method/suggest.rs

Lines changed: 10 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -4089,7 +4089,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
40894089
err: &mut Diag<'_>,
40904090
item_def_id: DefId,
40914091
hir_id: hir::HirId,
4092-
rcvr_ty: Option<Ty<'_>>,
4092+
rcvr_ty: Option<Ty<'tcx>>,
40934093
) -> bool {
40944094
let hir_id = self.tcx.parent_hir_id(hir_id);
40954095
let Some(traits) = self.tcx.in_scope_traits(hir_id) else { return false };
@@ -4100,49 +4100,16 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
41004100
if !self.tcx.is_trait(trait_def_id) {
41014101
return false;
41024102
}
4103-
let krate = self.tcx.crate_name(trait_def_id.krate);
4104-
let name = self.tcx.item_name(trait_def_id);
4105-
let candidates: Vec<_> = traits
4106-
.iter()
4107-
.filter(|c| {
4108-
c.def_id.krate != trait_def_id.krate
4109-
&& self.tcx.crate_name(c.def_id.krate) == krate
4110-
&& self.tcx.item_name(c.def_id) == name
4111-
})
4112-
.map(|c| (c.def_id, c.import_ids.get(0).cloned()))
4113-
.collect();
4114-
if candidates.is_empty() {
4103+
let hir::Node::Expr(rcvr) = self.tcx.hir_node(hir_id) else {
41154104
return false;
4116-
}
4117-
let item_span = self.tcx.def_span(item_def_id);
4118-
let msg = format!(
4119-
"there are multiple different versions of crate `{krate}` in the dependency graph",
4120-
);
4121-
let trait_span = self.tcx.def_span(trait_def_id);
4122-
let mut multi_span: MultiSpan = trait_span.into();
4123-
multi_span.push_span_label(trait_span, "this is the trait that is needed".to_string());
4124-
let descr = self.tcx.associated_item(item_def_id).descr();
4125-
let rcvr_ty =
4126-
rcvr_ty.map(|t| format!("`{t}`")).unwrap_or_else(|| "the receiver".to_string());
4127-
multi_span
4128-
.push_span_label(item_span, format!("the {descr} is available for {rcvr_ty} here"));
4129-
for (def_id, import_def_id) in candidates {
4130-
if let Some(import_def_id) = import_def_id {
4131-
multi_span.push_span_label(
4132-
self.tcx.def_span(import_def_id),
4133-
format!(
4134-
"`{name}` imported here doesn't correspond to the right version of crate \
4135-
`{krate}`",
4136-
),
4137-
);
4138-
}
4139-
multi_span.push_span_label(
4140-
self.tcx.def_span(def_id),
4141-
"this is the trait that was imported".to_string(),
4142-
);
4143-
}
4144-
err.span_note(multi_span, msg);
4145-
true
4105+
};
4106+
let trait_ref = ty::TraitRef::new(self.tcx, trait_def_id, rcvr_ty.into_iter());
4107+
let trait_pred = ty::Binder::dummy(ty::TraitPredicate {
4108+
trait_ref,
4109+
polarity: ty::PredicatePolarity::Positive,
4110+
});
4111+
let obligation = Obligation::new(self.tcx, self.misc(rcvr.span), self.param_env, trait_ref);
4112+
self.err_ctxt().note_different_trait_with_same_name(err, &obligation, trait_pred)
41464113
}
41474114

41484115
/// issue #102320, for `unwrap_or` with closure as argument, suggest `unwrap_or_else`

compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs

Lines changed: 133 additions & 133 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
// ignore-tidy-filelength
12
use core::ops::ControlFlow;
23
use std::borrow::Cow;
34
use std::path::PathBuf;
@@ -467,7 +468,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
467468
span,
468469
leaf_trait_predicate,
469470
);
470-
self.note_trait_version_mismatch(&mut err, leaf_trait_predicate);
471+
self.note_different_trait_with_same_name(&mut err, &obligation, leaf_trait_predicate);
471472
self.note_adt_version_mismatch(&mut err, leaf_trait_predicate);
472473
self.suggest_remove_await(&obligation, &mut err);
473474
self.suggest_derive(&obligation, &mut err, leaf_trait_predicate);
@@ -1949,115 +1950,6 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
19491950
impl_candidates
19501951
};
19511952

1952-
// We'll check for the case where the reason for the mismatch is that the trait comes from
1953-
// one crate version and the type comes from another crate version, even though they both
1954-
// are from the same crate.
1955-
let trait_def_id = trait_pred.def_id();
1956-
let trait_name = self.tcx.item_name(trait_def_id);
1957-
let crate_name = self.tcx.crate_name(trait_def_id.krate);
1958-
if let Some(other_trait_def_id) = self.tcx.all_traits_including_private().find(|def_id| {
1959-
trait_name == self.tcx.item_name(trait_def_id)
1960-
&& trait_def_id.krate != def_id.krate
1961-
&& crate_name == self.tcx.crate_name(def_id.krate)
1962-
}) {
1963-
// We've found two different traits with the same name, same crate name, but
1964-
// different crate `DefId`. We highlight the traits.
1965-
1966-
let found_type =
1967-
if let ty::Adt(def, _) = trait_pred.self_ty().skip_binder().peel_refs().kind() {
1968-
Some(def.did())
1969-
} else {
1970-
None
1971-
};
1972-
let candidates = if impl_candidates.is_empty() {
1973-
alternative_candidates(trait_def_id)
1974-
} else {
1975-
impl_candidates.into_iter().map(|cand| cand.trait_ref).collect()
1976-
};
1977-
let mut span: MultiSpan = self.tcx.def_span(trait_def_id).into();
1978-
span.push_span_label(self.tcx.def_span(trait_def_id), "this is the required trait");
1979-
for (sp, label) in [trait_def_id, other_trait_def_id]
1980-
.iter()
1981-
// The current crate-version might depend on another version of the same crate
1982-
// (Think "semver-trick"). Do not call `extern_crate` in that case for the local
1983-
// crate as that doesn't make sense and ICEs (#133563).
1984-
.filter(|def_id| !def_id.is_local())
1985-
.filter_map(|def_id| self.tcx.extern_crate(def_id.krate))
1986-
.map(|data| {
1987-
let dependency = if data.dependency_of == LOCAL_CRATE {
1988-
"direct dependency of the current crate".to_string()
1989-
} else {
1990-
let dep = self.tcx.crate_name(data.dependency_of);
1991-
format!("dependency of crate `{dep}`")
1992-
};
1993-
(
1994-
data.span,
1995-
format!("one version of crate `{crate_name}` used here, as a {dependency}"),
1996-
)
1997-
})
1998-
{
1999-
span.push_span_label(sp, label);
2000-
}
2001-
let mut points_at_type = false;
2002-
if let Some(found_type) = found_type {
2003-
span.push_span_label(
2004-
self.tcx.def_span(found_type),
2005-
"this type doesn't implement the required trait",
2006-
);
2007-
for trait_ref in candidates {
2008-
if let ty::Adt(def, _) = trait_ref.self_ty().peel_refs().kind()
2009-
&& let candidate_def_id = def.did()
2010-
&& let Some(name) = self.tcx.opt_item_name(candidate_def_id)
2011-
&& let Some(found) = self.tcx.opt_item_name(found_type)
2012-
&& name == found
2013-
&& candidate_def_id.krate != found_type.krate
2014-
&& self.tcx.crate_name(candidate_def_id.krate)
2015-
== self.tcx.crate_name(found_type.krate)
2016-
{
2017-
// A candidate was found of an item with the same name, from two separate
2018-
// versions of the same crate, let's clarify.
2019-
let candidate_span = self.tcx.def_span(candidate_def_id);
2020-
span.push_span_label(
2021-
candidate_span,
2022-
"this type implements the required trait",
2023-
);
2024-
points_at_type = true;
2025-
}
2026-
}
2027-
}
2028-
span.push_span_label(self.tcx.def_span(other_trait_def_id), "this is the found trait");
2029-
err.highlighted_span_note(
2030-
span,
2031-
vec![
2032-
StringPart::normal("there are ".to_string()),
2033-
StringPart::highlighted("multiple different versions".to_string()),
2034-
StringPart::normal(" of crate `".to_string()),
2035-
StringPart::highlighted(format!("{crate_name}")),
2036-
StringPart::normal("` in the dependency graph".to_string()),
2037-
],
2038-
);
2039-
if points_at_type {
2040-
// We only clarify that the same type from different crate versions are not the
2041-
// same when we *find* the same type coming from different crate versions, otherwise
2042-
// it could be that it was a type provided by a different crate than the one that
2043-
// provides the trait, and mentioning this adds verbosity without clarification.
2044-
err.highlighted_note(vec![
2045-
StringPart::normal(
2046-
"two types coming from two different versions of the same crate are \
2047-
different types "
2048-
.to_string(),
2049-
),
2050-
StringPart::highlighted("even if they look the same".to_string()),
2051-
]);
2052-
}
2053-
err.highlighted_help(vec![
2054-
StringPart::normal("you can use `".to_string()),
2055-
StringPart::highlighted("cargo tree".to_string()),
2056-
StringPart::normal("` to explore your dependency tree".to_string()),
2057-
]);
2058-
return true;
2059-
}
2060-
20611953
if let [single] = &impl_candidates {
20621954
// If we have a single implementation, try to unify it with the trait ref
20631955
// that failed. This should uncover a better hint for what *is* implemented.
@@ -2422,10 +2314,18 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
24222314
}
24232315
}
24242316

2425-
/// If the `Self` type of the unsatisfied trait `trait_ref` implements a trait
2426-
/// with the same path as `trait_ref`, a help message about
2427-
/// a probable version mismatch is added to `err`
2428-
fn note_trait_version_mismatch(
2317+
fn get_extern_crate_renamed_symbol(&self, trait_def_id: DefId) -> Option<Symbol> {
2318+
if !trait_def_id.is_local()
2319+
&& let Some(data) = self.tcx.extern_crate(trait_def_id.krate)
2320+
&& let rustc_session::cstore::ExternCrateSource::Extern(def_id) = data.src
2321+
{
2322+
self.tcx.opt_item_name(def_id)
2323+
} else {
2324+
None
2325+
}
2326+
}
2327+
2328+
fn check_same_trait_different_version(
24292329
&self,
24302330
err: &mut Diag<'_>,
24312331
trait_pred: ty::PolyTraitPredicate<'tcx>,
@@ -2442,40 +2342,72 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
24422342
trait_impls
24432343
};
24442344

2445-
let required_trait_path = self.tcx.def_path_str(trait_pred.def_id());
2345+
let krate = self.tcx.crate_name(trait_pred.def_id().krate);
2346+
let name = self.tcx.item_name(trait_pred.def_id());
2347+
let locally_renamed_krate = self
2348+
.get_extern_crate_renamed_symbol(trait_pred.def_id())
2349+
.map_or(None, |s| if s != krate { Some(s) } else { None });
24462350
let traits_with_same_path: UnordSet<_> = self
24472351
.tcx
24482352
.visible_traits()
2449-
.filter(|trait_def_id| *trait_def_id != trait_pred.def_id())
2353+
.filter(|trait_def_id| {
2354+
trait_def_id.krate != trait_pred.def_id().krate
2355+
&& (locally_renamed_krate
2356+
== self.get_extern_crate_renamed_symbol(*trait_def_id)
2357+
|| self.tcx.crate_name(trait_def_id.krate) == krate)
2358+
&& self.tcx.item_name(trait_def_id) == name
2359+
})
24502360
.map(|trait_def_id| (self.tcx.def_path_str(trait_def_id), trait_def_id))
2451-
.filter(|(p, _)| *p == required_trait_path)
24522361
.collect();
24532362

24542363
let traits_with_same_path =
24552364
traits_with_same_path.into_items().into_sorted_stable_ord_by_key(|(p, _)| p);
24562365
let mut suggested = false;
2457-
for (_, trait_with_same_path) in traits_with_same_path {
2458-
let trait_impls = get_trait_impls(trait_with_same_path);
2459-
if trait_impls.is_empty() {
2460-
continue;
2461-
}
2462-
let impl_spans: Vec<_> =
2463-
trait_impls.iter().map(|impl_def_id| self.tcx.def_span(*impl_def_id)).collect();
2464-
err.span_help(
2465-
impl_spans,
2466-
format!("trait impl{} with same name found", pluralize!(trait_impls.len())),
2366+
let mut trait_is_impl = false;
2367+
2368+
if !traits_with_same_path.is_empty() {
2369+
let mut span: MultiSpan = self.tcx.def_span(trait_pred.def_id()).into();
2370+
span.push_span_label(
2371+
self.tcx.def_span(trait_pred.def_id()),
2372+
"this is the required trait",
24672373
);
2468-
self.note_two_crate_versions(trait_with_same_path, err);
24692374
suggested = true;
2375+
for (_, trait_with_same_path) in &traits_with_same_path {
2376+
let trait_impls = get_trait_impls(*trait_with_same_path);
2377+
if trait_impls.is_empty() {
2378+
continue;
2379+
}
2380+
2381+
for candidate_def_id in trait_impls {
2382+
let Some(impl_trait_header) = self.tcx.impl_trait_header(candidate_def_id)
2383+
else {
2384+
continue;
2385+
};
2386+
let candidate_span =
2387+
self.tcx.def_span(impl_trait_header.trait_ref.skip_binder().def_id);
2388+
span.push_span_label(candidate_span, "this is the implemented trait");
2389+
trait_is_impl = true;
2390+
}
2391+
}
2392+
if !trait_is_impl {
2393+
for (_, def_id) in traits_with_same_path {
2394+
span.push_span_label(
2395+
self.tcx.def_span(def_id),
2396+
"this is the trait that was imported",
2397+
);
2398+
}
2399+
}
2400+
self.note_two_crate_versions(trait_pred.def_id(), span, err);
24702401
}
24712402
suggested
24722403
}
24732404

2474-
fn note_two_crate_versions(&self, did: DefId, err: &mut Diag<'_>) {
2405+
fn note_two_crate_versions(&self, did: DefId, sp: impl Into<MultiSpan>, err: &mut Diag<'_>) {
24752406
let crate_name = self.tcx.crate_name(did.krate);
2476-
let crate_msg =
2477-
format!("perhaps two different versions of crate `{crate_name}` are being used?");
2478-
err.note(crate_msg);
2407+
let crate_msg = format!(
2408+
"there are multiple different versions of crate `{crate_name}` in the dependency graph"
2409+
);
2410+
err.span_note(sp, crate_msg);
24792411
}
24802412

24812413
fn note_adt_version_mismatch(
@@ -2536,8 +2468,76 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
25362468

25372469
for (similar_item, _) in similar_items {
25382470
err.span_help(self.tcx.def_span(similar_item), "item with same name found");
2539-
self.note_two_crate_versions(similar_item, err);
2471+
self.note_two_crate_versions(similar_item, MultiSpan::new(), err);
2472+
}
2473+
}
2474+
2475+
fn check_same_name_different_path(
2476+
&self,
2477+
err: &mut Diag<'_>,
2478+
obligation: &PredicateObligation<'tcx>,
2479+
trait_pred: ty::PolyTraitPredicate<'tcx>,
2480+
) -> bool {
2481+
let mut suggested = false;
2482+
let trait_def_id = trait_pred.def_id();
2483+
let trait_has_same_params = |other_trait_def_id: DefId| -> bool {
2484+
let trait_generics = self.tcx.generics_of(trait_def_id);
2485+
let other_trait_generics = self.tcx.generics_of(other_trait_def_id);
2486+
2487+
if trait_generics.count() != other_trait_generics.count() {
2488+
return false;
2489+
}
2490+
trait_generics.own_params.iter().zip(other_trait_generics.own_params.iter()).all(
2491+
|(a, b)| {
2492+
(matches!(a.kind, ty::GenericParamDefKind::Type { .. })
2493+
&& matches!(b.kind, ty::GenericParamDefKind::Type { .. }))
2494+
|| (matches!(a.kind, ty::GenericParamDefKind::Lifetime,)
2495+
&& matches!(b.kind, ty::GenericParamDefKind::Lifetime))
2496+
|| (matches!(a.kind, ty::GenericParamDefKind::Const { .. })
2497+
&& matches!(b.kind, ty::GenericParamDefKind::Const { .. }))
2498+
},
2499+
)
2500+
};
2501+
let trait_name = self.tcx.item_name(trait_def_id);
2502+
if let Some(other_trait_def_id) = self.tcx.all_traits_including_private().find(|def_id| {
2503+
trait_def_id != *def_id
2504+
&& trait_name == self.tcx.item_name(def_id)
2505+
&& trait_has_same_params(*def_id)
2506+
&& self.predicate_must_hold_modulo_regions(&Obligation::new(
2507+
self.tcx,
2508+
obligation.cause.clone(),
2509+
obligation.param_env,
2510+
trait_pred.map_bound(|tr| ty::TraitPredicate {
2511+
trait_ref: ty::TraitRef::new(self.tcx, *def_id, tr.trait_ref.args),
2512+
..tr
2513+
}),
2514+
))
2515+
}) {
2516+
err.note(format!(
2517+
"`{}` implements similarly named `{}`, but not `{}`",
2518+
trait_pred.self_ty(),
2519+
self.tcx.def_path_str(other_trait_def_id),
2520+
trait_pred.print_modifiers_and_trait_path()
2521+
));
2522+
suggested = true;
2523+
}
2524+
suggested
2525+
}
2526+
2527+
/// If the `Self` type of the unsatisfied trait `trait_ref` implements a trait
2528+
/// with the same path as `trait_ref`, a help message about a multiple different
2529+
/// versions of the same crate is added to `err`. Otherwise if it implements another
2530+
/// trait with the same name, a note message about a similarly named trait is added to `err`.
2531+
pub fn note_different_trait_with_same_name(
2532+
&self,
2533+
err: &mut Diag<'_>,
2534+
obligation: &PredicateObligation<'tcx>,
2535+
trait_pred: ty::PolyTraitPredicate<'tcx>,
2536+
) -> bool {
2537+
if self.check_same_trait_different_version(err, trait_pred) {
2538+
return true;
25402539
}
2540+
self.check_same_name_different_path(err, obligation, trait_pred)
25412541
}
25422542

25432543
/// Add a `::` prefix when comparing paths so that paths with just one item

compiler/rustc_trait_selection/src/error_reporting/traits/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
pub mod ambiguity;
22
pub mod call_kind;
3-
mod fulfillment_errors;
3+
pub mod fulfillment_errors;
44
pub mod on_unimplemented;
55
pub mod on_unimplemented_condition;
66
pub mod on_unimplemented_format;

0 commit comments

Comments
 (0)