Skip to content

Commit de2b67f

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.
1 parent 94ecb52 commit de2b67f

File tree

10 files changed

+246
-243
lines changed

10 files changed

+246
-243
lines changed

compiler/rustc_hir_typeck/src/method/suggest.rs

Lines changed: 13 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,19 @@ 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 Some(rcvr_ty) = rcvr_ty 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 hir::Node::Expr(rcvr) = self.tcx.hir_node(hir_id) else {
4107+
return false;
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: 98 additions & 130 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.
@@ -2422,11 +2313,13 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
24222313
}
24232314

24242315
/// 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(
2316+
/// with the same path as `trait_ref`, a help message about a multiple differents
2317+
/// versions of the same crate is added to `err`. Otherwise if it implements another
2318+
/// trait with the same name, a note message about a similarly named trait is added to `err`.
2319+
pub fn note_different_trait_with_same_name(
24282320
&self,
24292321
err: &mut Diag<'_>,
2322+
obligation: &PredicateObligation<'tcx>,
24302323
trait_pred: ty::PolyTraitPredicate<'tcx>,
24312324
) -> bool {
24322325
let get_trait_impls = |trait_def_id| {
@@ -2441,33 +2334,108 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
24412334
trait_impls
24422335
};
24432336

2337+
let krate = self.tcx.crate_name(trait_pred.def_id().krate);
2338+
let name = self.tcx.item_name(trait_pred.def_id());
24442339
let required_trait_path = self.tcx.def_path_str(trait_pred.def_id());
24452340
let traits_with_same_path: UnordSet<_> = self
24462341
.tcx
24472342
.visible_traits()
2448-
.filter(|trait_def_id| *trait_def_id != trait_pred.def_id())
2343+
.filter(|trait_def_id| {
2344+
trait_def_id.krate != trait_pred.def_id().krate
2345+
&& (self.tcx.def_path_str(trait_def_id) == required_trait_path
2346+
|| self.tcx.crate_name(trait_def_id.krate) == krate
2347+
&& self.tcx.item_name(trait_def_id) == name)
2348+
})
24492349
.map(|trait_def_id| (self.tcx.def_path_str(trait_def_id), trait_def_id))
2450-
.filter(|(p, _)| *p == required_trait_path)
24512350
.collect();
24522351

24532352
let traits_with_same_path =
24542353
traits_with_same_path.into_items().into_sorted_stable_ord_by_key(|(p, _)| p);
24552354
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())),
2355+
let mut trait_is_impl = false;
2356+
2357+
if !traits_with_same_path.is_empty() {
2358+
let msg = format!(
2359+
"there are multiple different versions of crate `{krate}` in the dependency graph"
24662360
);
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);
2361+
let mut span: MultiSpan = self.tcx.def_span(trait_pred.def_id()).into();
2362+
span.push_span_label(
2363+
self.tcx.def_span(trait_pred.def_id()),
2364+
"this is the required trait",
2365+
);
2366+
suggested = true;
2367+
for (_, trait_with_same_path) in &traits_with_same_path {
2368+
let trait_impls = get_trait_impls(*trait_with_same_path);
2369+
if trait_impls.is_empty() {
2370+
continue;
2371+
}
2372+
2373+
for candidate_def_id in trait_impls {
2374+
let Some(impl_trait_header) = self.tcx.impl_trait_header(candidate_def_id)
2375+
else {
2376+
continue;
2377+
};
2378+
let candidate_span =
2379+
self.tcx.def_span(impl_trait_header.trait_ref.skip_binder().def_id);
2380+
span.push_span_label(candidate_span, "this is the implemented trait");
2381+
trait_is_impl = true;
2382+
}
2383+
}
2384+
if !trait_is_impl {
2385+
for (_, def_id) in traits_with_same_path {
2386+
span.push_span_label(
2387+
self.tcx.def_span(def_id),
2388+
"this is the trait that was imported",
2389+
);
2390+
}
2391+
}
2392+
err.span_note(span, msg);
2393+
}
2394+
2395+
if suggested {
2396+
return true;
2397+
}
2398+
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

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;

tests/run-make/crate-loading-crate-depends-on-itself/foo.stderr

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,22 +7,14 @@ error[E0277]: the trait bound `foo::Struct: Trait` is not satisfied
77
note: there are multiple different versions of crate `foo` in the dependency graph
88
--> foo-current.rs:7:1
99
|
10-
4 | extern crate foo;
11-
| ----------------- one version of crate `foo` used here, as a direct dependency of the current crate
12-
5 |
13-
6 | pub struct Struct;
14-
| ----------------- this type implements the required trait
1510
7 | pub trait Trait {}
1611
| ^^^^^^^^^^^^^^^ this is the required trait
1712
|
1813
::: foo-prev.rs:X:Y
1914
|
20-
4 | pub struct Struct;
21-
| ----------------- this type doesn't implement the required trait
2215
5 | pub trait Trait {}
23-
| --------------- this is the found trait
24-
= note: two types coming from two different versions of the same crate are different types even if they look the same
25-
= help: you can use `cargo tree` to explore your dependency tree
16+
| --------------- this is the implemented trait
17+
= help: the trait `Trait` is implemented for `Struct`
2618
note: required by a bound in `check_trait`
2719
--> foo-current.rs:10:19
2820
|

0 commit comments

Comments
 (0)