Skip to content

Commit f6d5d3e

Browse files
committed
compiler: Hint at multiple crate versions if trait impl is for wrong ADT
If a user does e.g. impl From<Bar> for foo::Foo and get a compilation error about that `From<Bar>` is not implemented for `Foo`, check if multiple versions of the crate with `Foo` is present in the dependency graph. If so, give a hint about it. I encountered this case in the wild and didn't realize I had multiple versions of a crate in my dependency graph. So I was a bit confused at first. This fix will make life easier for others.
1 parent e1ae7b7 commit f6d5d3e

File tree

3 files changed

+87
-6
lines changed

3 files changed

+87
-6
lines changed

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

Lines changed: 79 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -467,7 +467,8 @@ 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_trait_version_mismatch(&mut err, leaf_trait_predicate);
471+
self.note_adt_version_mismatch(&mut err, leaf_trait_predicate);
471472
self.suggest_remove_await(&obligation, &mut err);
472473
self.suggest_derive(&obligation, &mut err, leaf_trait_predicate);
473474

@@ -2406,7 +2407,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
24062407
/// If the `Self` type of the unsatisfied trait `trait_ref` implements a trait
24072408
/// with the same path as `trait_ref`, a help message about
24082409
/// a probable version mismatch is added to `err`
2409-
fn note_version_mismatch(
2410+
fn note_trait_version_mismatch(
24102411
&self,
24112412
err: &mut Diag<'_>,
24122413
trait_pred: ty::PolyTraitPredicate<'tcx>,
@@ -2446,15 +2447,87 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
24462447
impl_spans,
24472448
format!("trait impl{} with same name found", pluralize!(trait_impls.len())),
24482449
);
2449-
let trait_crate = self.tcx.crate_name(trait_with_same_path.krate);
2450-
let crate_msg =
2451-
format!("perhaps two different versions of crate `{trait_crate}` are being used?");
2452-
err.note(crate_msg);
2450+
self.note_two_crate_versions(trait_with_same_path, err);
24532451
suggested = true;
24542452
}
24552453
suggested
24562454
}
24572455

2456+
fn note_two_crate_versions(&self, did: DefId, err: &mut Diag<'_>) {
2457+
let crate_name = self.tcx.crate_name(did.krate);
2458+
let crate_msg =
2459+
format!("perhaps two different versions of crate `{crate_name}` are being used?");
2460+
err.note(crate_msg);
2461+
}
2462+
2463+
fn note_adt_version_mismatch(
2464+
&self,
2465+
err: &mut Diag<'_>,
2466+
trait_pred: ty::PolyTraitPredicate<'tcx>,
2467+
) {
2468+
let ty::Adt(impl_self_def, _) = trait_pred.self_ty().skip_binder().peel_refs().kind()
2469+
else {
2470+
return;
2471+
};
2472+
2473+
let impl_self_did = impl_self_def.did();
2474+
2475+
// We only want to warn about different versions of a dependency.
2476+
// If no dependency is involved, bail.
2477+
if impl_self_did.krate == LOCAL_CRATE {
2478+
return;
2479+
}
2480+
2481+
let impl_self_path = self.comparable_path(impl_self_did);
2482+
let impl_self_crate_name = self.tcx.crate_name(impl_self_did.krate);
2483+
let similar_items: UnordSet<_> = self
2484+
.tcx
2485+
.visible_parent_map(())
2486+
.items()
2487+
.filter_map(|(&item, _)| {
2488+
// If we found ourselves, ignore.
2489+
if impl_self_did == item {
2490+
return None;
2491+
}
2492+
// We only want to warn about different versions of a dependency.
2493+
// Ignore items from our own crate.
2494+
if item.krate == LOCAL_CRATE {
2495+
return None;
2496+
}
2497+
// We want to warn about different versions of a dependency.
2498+
// So make sure the crate names are the same.
2499+
if impl_self_crate_name != self.tcx.crate_name(item.krate) {
2500+
return None;
2501+
}
2502+
// Filter out e.g. constructors that often have the same path
2503+
// str as the relevant ADT.
2504+
if !self.tcx.def_kind(item).is_adt() {
2505+
return None;
2506+
}
2507+
let path = self.comparable_path(item);
2508+
// We don't know if our item or the one we found is the re-exported one.
2509+
// Check both cases.
2510+
let is_similar = path.ends_with(&impl_self_path) || impl_self_path.ends_with(&path);
2511+
is_similar.then_some((item, path))
2512+
})
2513+
.collect();
2514+
2515+
let mut similar_items =
2516+
similar_items.into_items().into_sorted_stable_ord_by_key(|(_, path)| path);
2517+
similar_items.dedup();
2518+
2519+
for (similar_item, _) in similar_items {
2520+
err.span_help(self.tcx.def_span(similar_item), "item with same name found");
2521+
self.note_two_crate_versions(similar_item, err);
2522+
}
2523+
}
2524+
2525+
/// Add a `::` prefix when comparing paths so that paths with just one item
2526+
/// like "Foo" does not equal the end of "OtherFoo".
2527+
fn comparable_path(&self, did: DefId) -> String {
2528+
format!("::{}", self.tcx.def_path_str(did))
2529+
}
2530+
24582531
/// Creates a `PredicateObligation` with `new_self_ty` replacing the existing type in the
24592532
/// `trait_ref`.
24602533
///

tests/run-make/duplicate-dependency/main.stderr

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,12 @@ LL | re_export_foo::into_foo(Bar);
66
| |
77
| required by a bound introduced by this call
88
|
9+
help: item with same name found
10+
--> $DIR/foo-v1.rs:1:1
11+
|
12+
LL | pub struct Foo;
13+
| ^^^^^^^^^^^^^^
14+
= note: perhaps two different versions of crate `foo` are being used?
915
= note: required for `Bar` to implement `Into<re_export_foo::foo::Foo>`
1016
note: required by a bound in `into_foo`
1117
--> $DIR/re-export-foo.rs:3:25

tests/run-make/duplicate-dependency/rmake.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
//@ needs-target-std
2+
13
use run_make_support::{Rustc, cwd, diff, rust_lib_name, rustc};
24

35
fn rustc_with_common_args() -> Rustc {

0 commit comments

Comments
 (0)