Skip to content

Commit 1181c3f

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 94ecb52 commit 1181c3f

File tree

10 files changed

+264
-245
lines changed

10 files changed

+264
-245
lines changed

compiler/rustc_hir_typeck/src/method/suggest.rs

Lines changed: 14 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -3478,7 +3478,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
34783478
err,
34793479
pick.item.def_id,
34803480
rcvr.hir_id,
3481-
Some(*rcvr_ty),
3481+
*rcvr_ty,
34823482
);
34833483
if pick.autoderefs == 0 && !trait_in_other_version_found {
34843484
err.span_label(
@@ -3686,6 +3686,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
36863686
// same crate.
36873687

36883688
let rcvr_ty = self.node_ty_opt(ty.hir_id);
3689+
let Some(rcvr_ty) = rcvr_ty else {
3690+
return;
3691+
};
36893692
trait_in_other_version_found = self
36903693
.detect_and_explain_multiple_crate_versions_of_trait_item(
36913694
err,
@@ -4089,7 +4092,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
40894092
err: &mut Diag<'_>,
40904093
item_def_id: DefId,
40914094
hir_id: hir::HirId,
4092-
rcvr_ty: Option<Ty<'_>>,
4095+
rcvr_ty: Ty<'tcx>,
40934096
) -> bool {
40944097
let hir_id = self.tcx.parent_hir_id(hir_id);
40954098
let Some(traits) = self.tcx.in_scope_traits(hir_id) else { return false };
@@ -4100,49 +4103,16 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
41004103
if !self.tcx.is_trait(trait_def_id) {
41014104
return false;
41024105
}
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() {
4106+
let hir::Node::Expr(rcvr) = self.tcx.hir_node(hir_id) else {
41154107
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
4108+
};
4109+
let trait_ref = ty::TraitRef::new(self.tcx, trait_def_id, [rcvr_ty]);
4110+
let trait_pred = ty::Binder::dummy(ty::TraitPredicate {
4111+
trait_ref,
4112+
polarity: ty::PredicatePolarity::Positive,
4113+
});
4114+
let obligation = Obligation::new(self.tcx, self.misc(rcvr.span), self.param_env, trait_ref);
4115+
self.err_ctxt().note_different_trait_with_same_name(err, &obligation, trait_pred)
41464116
}
41474117

41484118
/// 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: 115 additions & 131 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use rustc_errors::{
1212
Applicability, Diag, ErrorGuaranteed, Level, MultiSpan, StashKey, StringPart, Suggestions,
1313
pluralize, struct_span_code_err,
1414
};
15-
use rustc_hir::def_id::{DefId, LOCAL_CRATE, LocalDefId};
15+
use rustc_hir::def_id::{DefId, LocalDefId};
1616
use rustc_hir::intravisit::Visitor;
1717
use rustc_hir::{self as hir, LangItem, Node};
1818
use rustc_infer::infer::{InferOk, TypeTrace};
@@ -467,7 +467,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
467467
span,
468468
leaf_trait_predicate,
469469
);
470-
self.note_version_mismatch(&mut err, leaf_trait_predicate);
470+
self.note_different_trait_with_same_name(&mut err, &obligation, leaf_trait_predicate);
471471
self.suggest_remove_await(&obligation, &mut err);
472472
self.suggest_derive(&obligation, &mut err, leaf_trait_predicate);
473473

@@ -1948,115 +1948,6 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
19481948
impl_candidates
19491949
};
19501950

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

2424-
/// If the `Self` type of the unsatisfied trait `trait_ref` implements a trait
2425-
/// with the same path as `trait_ref`, a help message about
2426-
/// a probable version mismatch is added to `err`
2427-
fn note_version_mismatch(
2315+
fn check_same_trait_different_version(
24282316
&self,
24292317
err: &mut Diag<'_>,
24302318
trait_pred: ty::PolyTraitPredicate<'tcx>,
@@ -2441,38 +2329,134 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
24412329
trait_impls
24422330
};
24432331

2332+
let krate = self.tcx.crate_name(trait_pred.def_id().krate);
2333+
let name = self.tcx.item_name(trait_pred.def_id());
24442334
let required_trait_path = self.tcx.def_path_str(trait_pred.def_id());
24452335
let traits_with_same_path: UnordSet<_> = self
24462336
.tcx
24472337
.visible_traits()
2448-
.filter(|trait_def_id| *trait_def_id != trait_pred.def_id())
2338+
.filter(|trait_def_id| {
2339+
trait_def_id.krate != trait_pred.def_id().krate
2340+
&& (self.tcx.def_path_str(trait_def_id) == required_trait_path
2341+
|| self.tcx.crate_name(trait_def_id.krate) == krate
2342+
&& self.tcx.item_name(trait_def_id) == name)
2343+
})
24492344
.map(|trait_def_id| (self.tcx.def_path_str(trait_def_id), trait_def_id))
2450-
.filter(|(p, _)| *p == required_trait_path)
24512345
.collect();
24522346

24532347
let traits_with_same_path =
24542348
traits_with_same_path.into_items().into_sorted_stable_ord_by_key(|(p, _)| p);
24552349
let mut suggested = false;
2456-
for (_, trait_with_same_path) in traits_with_same_path {
2457-
let trait_impls = get_trait_impls(trait_with_same_path);
2458-
if trait_impls.is_empty() {
2459-
continue;
2460-
}
2461-
let impl_spans: Vec<_> =
2462-
trait_impls.iter().map(|impl_def_id| self.tcx.def_span(*impl_def_id)).collect();
2463-
err.span_help(
2464-
impl_spans,
2465-
format!("trait impl{} with same name found", pluralize!(trait_impls.len())),
2350+
let mut trait_is_impl = false;
2351+
2352+
if !traits_with_same_path.is_empty() {
2353+
let msg = format!(
2354+
"there are multiple different versions of crate `{krate}` in the dependency graph"
24662355
);
2467-
let trait_crate = self.tcx.crate_name(trait_with_same_path.krate);
2468-
let crate_msg =
2469-
format!("perhaps two different versions of crate `{trait_crate}` are being used?");
2470-
err.note(crate_msg);
2356+
let mut span: MultiSpan = self.tcx.def_span(trait_pred.def_id()).into();
2357+
span.push_span_label(
2358+
self.tcx.def_span(trait_pred.def_id()),
2359+
"this is the required trait",
2360+
);
2361+
suggested = true;
2362+
for (_, trait_with_same_path) in &traits_with_same_path {
2363+
let trait_impls = get_trait_impls(*trait_with_same_path);
2364+
if trait_impls.is_empty() {
2365+
continue;
2366+
}
2367+
2368+
for candidate_def_id in trait_impls {
2369+
let Some(impl_trait_header) = self.tcx.impl_trait_header(candidate_def_id)
2370+
else {
2371+
continue;
2372+
};
2373+
let candidate_span =
2374+
self.tcx.def_span(impl_trait_header.trait_ref.skip_binder().def_id);
2375+
span.push_span_label(candidate_span, "this is the implemented trait");
2376+
trait_is_impl = true;
2377+
}
2378+
}
2379+
if !trait_is_impl {
2380+
for (_, def_id) in traits_with_same_path {
2381+
span.push_span_label(
2382+
self.tcx.def_span(def_id),
2383+
"this is the trait that was imported",
2384+
);
2385+
}
2386+
}
2387+
err.span_note(span, msg);
2388+
}
2389+
suggested
2390+
}
2391+
2392+
fn check_same_name_different_path(
2393+
&self,
2394+
err: &mut Diag<'_>,
2395+
obligation: &PredicateObligation<'tcx>,
2396+
trait_pred: ty::PolyTraitPredicate<'tcx>,
2397+
) -> bool {
2398+
let mut suggested = false;
2399+
let trait_def_id = trait_pred.def_id();
2400+
let trait_has_same_params = |other_trait_def_id: DefId| -> bool {
2401+
let trait_generics = self.tcx.generics_of(trait_def_id);
2402+
let other_trait_generics = self.tcx.generics_of(other_trait_def_id);
2403+
2404+
if trait_generics.count() != other_trait_generics.count() {
2405+
return false;
2406+
}
2407+
trait_generics.own_params.iter().zip(other_trait_generics.own_params.iter()).all(
2408+
|(a, b)| {
2409+
(matches!(a.kind, ty::GenericParamDefKind::Type { .. })
2410+
&& matches!(b.kind, ty::GenericParamDefKind::Type { .. }))
2411+
|| (matches!(a.kind, ty::GenericParamDefKind::Lifetime,)
2412+
&& matches!(b.kind, ty::GenericParamDefKind::Lifetime))
2413+
|| (matches!(a.kind, ty::GenericParamDefKind::Const { .. })
2414+
&& matches!(b.kind, ty::GenericParamDefKind::Const { .. }))
2415+
},
2416+
)
2417+
};
2418+
let trait_name = self.tcx.item_name(trait_def_id);
2419+
if let Some(other_trait_def_id) = self.tcx.all_traits_including_private().find(|def_id| {
2420+
trait_def_id != *def_id
2421+
&& trait_name == self.tcx.item_name(def_id)
2422+
&& trait_has_same_params(*def_id)
2423+
&& self.predicate_must_hold_modulo_regions(&Obligation::new(
2424+
self.tcx,
2425+
obligation.cause.clone(),
2426+
obligation.param_env,
2427+
trait_pred.map_bound(|tr| ty::TraitPredicate {
2428+
trait_ref: ty::TraitRef::new(self.tcx, *def_id, tr.trait_ref.args),
2429+
..tr
2430+
}),
2431+
))
2432+
}) {
2433+
err.note(format!(
2434+
"`{}` implements similarly named `{}`, but not `{}`",
2435+
trait_pred.self_ty(),
2436+
self.tcx.def_path_str(other_trait_def_id),
2437+
trait_pred.print_modifiers_and_trait_path()
2438+
));
24712439
suggested = true;
24722440
}
24732441
suggested
24742442
}
24752443

2444+
/// If the `Self` type of the unsatisfied trait `trait_ref` implements a trait
2445+
/// with the same path as `trait_ref`, a help message about a multiple different
2446+
/// versions of the same crate is added to `err`. Otherwise if it implements another
2447+
/// trait with the same name, a note message about a similarly named trait is added to `err`.
2448+
pub fn note_different_trait_with_same_name(
2449+
&self,
2450+
err: &mut Diag<'_>,
2451+
obligation: &PredicateObligation<'tcx>,
2452+
trait_pred: ty::PolyTraitPredicate<'tcx>,
2453+
) -> bool {
2454+
if self.check_same_trait_different_version(err, trait_pred) {
2455+
return true;
2456+
}
2457+
self.check_same_name_different_path(err, obligation, trait_pred)
2458+
}
2459+
24762460
/// Creates a `PredicateObligation` with `new_self_ty` replacing the existing type in the
24772461
/// `trait_ref`.
24782462
///

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)