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.
This commit is contained in:
@@ -467,7 +467,8 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
|
|||||||
span,
|
span,
|
||||||
leaf_trait_predicate,
|
leaf_trait_predicate,
|
||||||
);
|
);
|
||||||
self.note_version_mismatch(&mut err, leaf_trait_predicate);
|
self.note_trait_version_mismatch(&mut err, leaf_trait_predicate);
|
||||||
|
self.note_adt_version_mismatch(&mut err, leaf_trait_predicate);
|
||||||
self.suggest_remove_await(&obligation, &mut err);
|
self.suggest_remove_await(&obligation, &mut err);
|
||||||
self.suggest_derive(&obligation, &mut err, leaf_trait_predicate);
|
self.suggest_derive(&obligation, &mut err, leaf_trait_predicate);
|
||||||
|
|
||||||
@@ -2406,7 +2407,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
|
|||||||
/// If the `Self` type of the unsatisfied trait `trait_ref` implements a trait
|
/// If the `Self` type of the unsatisfied trait `trait_ref` implements a trait
|
||||||
/// with the same path as `trait_ref`, a help message about
|
/// with the same path as `trait_ref`, a help message about
|
||||||
/// a probable version mismatch is added to `err`
|
/// a probable version mismatch is added to `err`
|
||||||
fn note_version_mismatch(
|
fn note_trait_version_mismatch(
|
||||||
&self,
|
&self,
|
||||||
err: &mut Diag<'_>,
|
err: &mut Diag<'_>,
|
||||||
trait_pred: ty::PolyTraitPredicate<'tcx>,
|
trait_pred: ty::PolyTraitPredicate<'tcx>,
|
||||||
@@ -2446,15 +2447,87 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
|
|||||||
impl_spans,
|
impl_spans,
|
||||||
format!("trait impl{} with same name found", pluralize!(trait_impls.len())),
|
format!("trait impl{} with same name found", pluralize!(trait_impls.len())),
|
||||||
);
|
);
|
||||||
let trait_crate = self.tcx.crate_name(trait_with_same_path.krate);
|
self.note_two_crate_versions(trait_with_same_path, err);
|
||||||
let crate_msg =
|
|
||||||
format!("perhaps two different versions of crate `{trait_crate}` are being used?");
|
|
||||||
err.note(crate_msg);
|
|
||||||
suggested = true;
|
suggested = true;
|
||||||
}
|
}
|
||||||
suggested
|
suggested
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn note_two_crate_versions(&self, did: DefId, err: &mut Diag<'_>) {
|
||||||
|
let crate_name = self.tcx.crate_name(did.krate);
|
||||||
|
let crate_msg =
|
||||||
|
format!("perhaps two different versions of crate `{crate_name}` are being used?");
|
||||||
|
err.note(crate_msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn note_adt_version_mismatch(
|
||||||
|
&self,
|
||||||
|
err: &mut Diag<'_>,
|
||||||
|
trait_pred: ty::PolyTraitPredicate<'tcx>,
|
||||||
|
) {
|
||||||
|
let ty::Adt(impl_self_def, _) = trait_pred.self_ty().skip_binder().peel_refs().kind()
|
||||||
|
else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let impl_self_did = impl_self_def.did();
|
||||||
|
|
||||||
|
// We only want to warn about different versions of a dependency.
|
||||||
|
// If no dependency is involved, bail.
|
||||||
|
if impl_self_did.krate == LOCAL_CRATE {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let impl_self_path = self.comparable_path(impl_self_did);
|
||||||
|
let impl_self_crate_name = self.tcx.crate_name(impl_self_did.krate);
|
||||||
|
let similar_items: UnordSet<_> = self
|
||||||
|
.tcx
|
||||||
|
.visible_parent_map(())
|
||||||
|
.items()
|
||||||
|
.filter_map(|(&item, _)| {
|
||||||
|
// If we found ourselves, ignore.
|
||||||
|
if impl_self_did == item {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
// We only want to warn about different versions of a dependency.
|
||||||
|
// Ignore items from our own crate.
|
||||||
|
if item.krate == LOCAL_CRATE {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
// We want to warn about different versions of a dependency.
|
||||||
|
// So make sure the crate names are the same.
|
||||||
|
if impl_self_crate_name != self.tcx.crate_name(item.krate) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
// Filter out e.g. constructors that often have the same path
|
||||||
|
// str as the relevant ADT.
|
||||||
|
if !self.tcx.def_kind(item).is_adt() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let path = self.comparable_path(item);
|
||||||
|
// We don't know if our item or the one we found is the re-exported one.
|
||||||
|
// Check both cases.
|
||||||
|
let is_similar = path.ends_with(&impl_self_path) || impl_self_path.ends_with(&path);
|
||||||
|
is_similar.then_some((item, path))
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let mut similar_items =
|
||||||
|
similar_items.into_items().into_sorted_stable_ord_by_key(|(_, path)| path);
|
||||||
|
similar_items.dedup();
|
||||||
|
|
||||||
|
for (similar_item, _) in similar_items {
|
||||||
|
err.span_help(self.tcx.def_span(similar_item), "item with same name found");
|
||||||
|
self.note_two_crate_versions(similar_item, err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add a `::` prefix when comparing paths so that paths with just one item
|
||||||
|
/// like "Foo" does not equal the end of "OtherFoo".
|
||||||
|
fn comparable_path(&self, did: DefId) -> String {
|
||||||
|
format!("::{}", self.tcx.def_path_str(did))
|
||||||
|
}
|
||||||
|
|
||||||
/// Creates a `PredicateObligation` with `new_self_ty` replacing the existing type in the
|
/// Creates a `PredicateObligation` with `new_self_ty` replacing the existing type in the
|
||||||
/// `trait_ref`.
|
/// `trait_ref`.
|
||||||
///
|
///
|
||||||
|
|||||||
@@ -6,6 +6,12 @@ LL | re_export_foo::into_foo(Bar);
|
|||||||
| |
|
| |
|
||||||
| required by a bound introduced by this call
|
| required by a bound introduced by this call
|
||||||
|
|
|
|
||||||
|
help: item with same name found
|
||||||
|
--> $DIR/foo-v1.rs:1:1
|
||||||
|
|
|
||||||
|
LL | pub struct Foo;
|
||||||
|
| ^^^^^^^^^^^^^^
|
||||||
|
= note: perhaps two different versions of crate `foo` are being used?
|
||||||
= note: required for `Bar` to implement `Into<re_export_foo::foo::Foo>`
|
= note: required for `Bar` to implement `Into<re_export_foo::foo::Foo>`
|
||||||
note: required by a bound in `into_foo`
|
note: required by a bound in `into_foo`
|
||||||
--> $DIR/re-export-foo.rs:3:25
|
--> $DIR/re-export-foo.rs:3:25
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
//@ needs-target-std
|
||||||
|
|
||||||
use run_make_support::{Rustc, cwd, diff, rust_lib_name, rustc};
|
use run_make_support::{Rustc, cwd, diff, rust_lib_name, rustc};
|
||||||
|
|
||||||
fn rustc_with_common_args() -> Rustc {
|
fn rustc_with_common_args() -> Rustc {
|
||||||
@@ -39,5 +41,5 @@ fn main() {
|
|||||||
.run_fail()
|
.run_fail()
|
||||||
.stderr_utf8();
|
.stderr_utf8();
|
||||||
|
|
||||||
diff().expected_file("main.stderr").actual_text("(rustc)", &stderr).run();
|
diff().expected_file("main.stderr").normalize(r"\\", "/").actual_text("(rustc)", &stderr).run();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user