Skip to content

Commit 8f15d55

Browse files
authored
Rollup merge of rust-lang#146874 - Enselic:multiple-adt-versions, r=jieyouxu
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. Note that a test is added as a separate commit so it is easy to see what effect the fix has on the emitted error message. This can be seen as a continuation of rust-lang#124944. I think this closes RUST-71693 but I haven't checked since it lacks a minimal reproducer. If this gets merged I'll ask that reporter if this fix works for them. ## Real world example 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. For reference, here is what that looked like. <details> <summary>Click to expand</summary> ### Before fix ``` error[E0277]: the trait bound `lambda_http::lambda_runtime::Diagnostic: From<Error>` is not satisfied --> src/main.rs:73:5 | 73 | lambda_http::run(service_fn(handle_event)).await | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `From<Error>` is not implemented for `lambda_http::lambda_runtime::Diagnostic` | = help: the following other types implement trait `From<T>`: `lambda_http::lambda_runtime::Diagnostic` implements `From<&str>` `lambda_http::lambda_runtime::Diagnostic` implements `From<Box<dyn StdError + Send + Sync>>` `lambda_http::lambda_runtime::Diagnostic` implements `From<Box<dyn StdError>>` `lambda_http::lambda_runtime::Diagnostic` implements `From<Infallible>` `lambda_http::lambda_runtime::Diagnostic` implements `From<lambda_runtime::deserializer::DeserializeError>` `lambda_http::lambda_runtime::Diagnostic` implements `From<std::io::Error>` `lambda_http::lambda_runtime::Diagnostic` implements `From<std::string::String>` = note: required for `Error` to implement `Into<lambda_http::lambda_runtime::Diagnostic>` note: required by a bound in `lambda_http::run` --> /home/martin/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/lambda_http-0.17.0/src/lib.rs:199:26 | 194 | pub async fn run<'a, R, S, E>(handler: S) -> Result<(), Error> | --- required by a bound in this function ... 199 | E: std::fmt::Debug + Into<Diagnostic>, | ^^^^^^^^^^^^^^^^ required by this bound in `run` error[E0277]: the trait bound `lambda_http::lambda_runtime::Diagnostic: From<Error>` is not satisfied --> src/main.rs:73:48 | 73 | lambda_http::run(service_fn(handle_event)).await | ^^^^^ the trait `From<Error>` is not implemented for `lambda_http::lambda_runtime::Diagnostic` | = help: the following other types implement trait `From<T>`: `lambda_http::lambda_runtime::Diagnostic` implements `From<&str>` `lambda_http::lambda_runtime::Diagnostic` implements `From<Box<dyn StdError + Send + Sync>>` `lambda_http::lambda_runtime::Diagnostic` implements `From<Box<dyn StdError>>` `lambda_http::lambda_runtime::Diagnostic` implements `From<Infallible>` `lambda_http::lambda_runtime::Diagnostic` implements `From<lambda_runtime::deserializer::DeserializeError>` `lambda_http::lambda_runtime::Diagnostic` implements `From<std::io::Error>` `lambda_http::lambda_runtime::Diagnostic` implements `From<std::string::String>` = note: required for `Error` to implement `Into<lambda_http::lambda_runtime::Diagnostic>` note: required by a bound in `lambda_http::run` --> /home/martin/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/lambda_http-0.17.0/src/lib.rs:199:26 | 194 | pub async fn run<'a, R, S, E>(handler: S) -> Result<(), Error> | --- required by a bound in this function ... 199 | E: std::fmt::Debug + Into<Diagnostic>, | ^^^^^^^^^^^^^^^^ required by this bound in `run` For more information about this error, try `rustc --explain E0277`. error: could not compile `auto-merge-dependabot-pull-requests-webhook` (bin "auto-merge-dependabot-pull-requests-webhook") due to 2 previous errors ``` ### After fix ``` Compiling auto-merge-dependabot-pull-requests-webhook v0.1.0 (/home/martin/src/auto-merge-dependabot-prs/rust-webhook) error[E0277]: the trait bound `lambda_http::lambda_runtime::Diagnostic: From<Error>` is not satisfied --> src/main.rs:73:5 | 73 | lambda_http::run(service_fn(handle_event)).await | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `From<Error>` is not implemented for `lambda_http::lambda_runtime::Diagnostic` | help: item with same name found --> /home/martin/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/lambda_runtime-0.13.0/src/diagnostic.rs:43:1 | 43 | pub struct Diagnostic { | ^^^^^^^^^^^^^^^^^^^^^ = note: perhaps two different versions of crate `lambda_runtime` are being used? = help: the following other types implement trait `From<T>`: `lambda_http::lambda_runtime::Diagnostic` implements `From<&str>` `lambda_http::lambda_runtime::Diagnostic` implements `From<Box<dyn StdError + Send + Sync>>` `lambda_http::lambda_runtime::Diagnostic` implements `From<Box<dyn StdError>>` `lambda_http::lambda_runtime::Diagnostic` implements `From<Infallible>` `lambda_http::lambda_runtime::Diagnostic` implements `From<lambda_runtime::deserializer::DeserializeError>` `lambda_http::lambda_runtime::Diagnostic` implements `From<std::io::Error>` `lambda_http::lambda_runtime::Diagnostic` implements `From<std::string::String>` = note: required for `Error` to implement `Into<lambda_http::lambda_runtime::Diagnostic>` note: required by a bound in `lambda_http::run` --> /home/martin/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/lambda_http-0.17.0/src/lib.rs:199:26 | 194 | pub async fn run<'a, R, S, E>(handler: S) -> Result<(), Error> | --- required by a bound in this function ... 199 | E: std::fmt::Debug + Into<Diagnostic>, | ^^^^^^^^^^^^^^^^ required by this bound in `run` error[E0277]: the trait bound `lambda_http::lambda_runtime::Diagnostic: From<Error>` is not satisfied --> src/main.rs:73:48 | 73 | lambda_http::run(service_fn(handle_event)).await | ^^^^^ the trait `From<Error>` is not implemented for `lambda_http::lambda_runtime::Diagnostic` | help: item with same name found --> /home/martin/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/lambda_runtime-0.13.0/src/diagnostic.rs:43:1 | 43 | pub struct Diagnostic { | ^^^^^^^^^^^^^^^^^^^^^ = note: perhaps two different versions of crate `lambda_runtime` are being used? = help: the following other types implement trait `From<T>`: `lambda_http::lambda_runtime::Diagnostic` implements `From<&str>` `lambda_http::lambda_runtime::Diagnostic` implements `From<Box<dyn StdError + Send + Sync>>` `lambda_http::lambda_runtime::Diagnostic` implements `From<Box<dyn StdError>>` `lambda_http::lambda_runtime::Diagnostic` implements `From<Infallible>` `lambda_http::lambda_runtime::Diagnostic` implements `From<lambda_runtime::deserializer::DeserializeError>` `lambda_http::lambda_runtime::Diagnostic` implements `From<std::io::Error>` `lambda_http::lambda_runtime::Diagnostic` implements `From<std::string::String>` = note: required for `Error` to implement `Into<lambda_http::lambda_runtime::Diagnostic>` note: required by a bound in `lambda_http::run` --> /home/martin/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/lambda_http-0.17.0/src/lib.rs:199:26 | 194 | pub async fn run<'a, R, S, E>(handler: S) -> Result<(), Error> | --- required by a bound in this function ... 199 | E: std::fmt::Debug + Into<Diagnostic>, | ^^^^^^^^^^^^^^^^ required by this bound in `run` For more information about this error, try `rustc --explain E0277`. error: could not compile `auto-merge-dependabot-pull-requests-webhook` (bin "auto-merge-dependabot-pull-requests-webhook") due to 2 previous errors ``` </details> try-job: dist-various-1
2 parents e2c96cc + f6d5d3e commit 8f15d55

File tree

7 files changed

+168
-6
lines changed

7 files changed

+168
-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

@@ -2424,7 +2425,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
24242425
/// If the `Self` type of the unsatisfied trait `trait_ref` implements a trait
24252426
/// with the same path as `trait_ref`, a help message about
24262427
/// a probable version mismatch is added to `err`
2427-
fn note_version_mismatch(
2428+
fn note_trait_version_mismatch(
24282429
&self,
24292430
err: &mut Diag<'_>,
24302431
trait_pred: ty::PolyTraitPredicate<'tcx>,
@@ -2464,15 +2465,87 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
24642465
impl_spans,
24652466
format!("trait impl{} with same name found", pluralize!(trait_impls.len())),
24662467
);
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);
2468+
self.note_two_crate_versions(trait_with_same_path, err);
24712469
suggested = true;
24722470
}
24732471
suggested
24742472
}
24752473

2474+
fn note_two_crate_versions(&self, did: DefId, err: &mut Diag<'_>) {
2475+
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);
2479+
}
2480+
2481+
fn note_adt_version_mismatch(
2482+
&self,
2483+
err: &mut Diag<'_>,
2484+
trait_pred: ty::PolyTraitPredicate<'tcx>,
2485+
) {
2486+
let ty::Adt(impl_self_def, _) = trait_pred.self_ty().skip_binder().peel_refs().kind()
2487+
else {
2488+
return;
2489+
};
2490+
2491+
let impl_self_did = impl_self_def.did();
2492+
2493+
// We only want to warn about different versions of a dependency.
2494+
// If no dependency is involved, bail.
2495+
if impl_self_did.krate == LOCAL_CRATE {
2496+
return;
2497+
}
2498+
2499+
let impl_self_path = self.comparable_path(impl_self_did);
2500+
let impl_self_crate_name = self.tcx.crate_name(impl_self_did.krate);
2501+
let similar_items: UnordSet<_> = self
2502+
.tcx
2503+
.visible_parent_map(())
2504+
.items()
2505+
.filter_map(|(&item, _)| {
2506+
// If we found ourselves, ignore.
2507+
if impl_self_did == item {
2508+
return None;
2509+
}
2510+
// We only want to warn about different versions of a dependency.
2511+
// Ignore items from our own crate.
2512+
if item.krate == LOCAL_CRATE {
2513+
return None;
2514+
}
2515+
// We want to warn about different versions of a dependency.
2516+
// So make sure the crate names are the same.
2517+
if impl_self_crate_name != self.tcx.crate_name(item.krate) {
2518+
return None;
2519+
}
2520+
// Filter out e.g. constructors that often have the same path
2521+
// str as the relevant ADT.
2522+
if !self.tcx.def_kind(item).is_adt() {
2523+
return None;
2524+
}
2525+
let path = self.comparable_path(item);
2526+
// We don't know if our item or the one we found is the re-exported one.
2527+
// Check both cases.
2528+
let is_similar = path.ends_with(&impl_self_path) || impl_self_path.ends_with(&path);
2529+
is_similar.then_some((item, path))
2530+
})
2531+
.collect();
2532+
2533+
let mut similar_items =
2534+
similar_items.into_items().into_sorted_stable_ord_by_key(|(_, path)| path);
2535+
similar_items.dedup();
2536+
2537+
for (similar_item, _) in similar_items {
2538+
err.span_help(self.tcx.def_span(similar_item), "item with same name found");
2539+
self.note_two_crate_versions(similar_item, err);
2540+
}
2541+
}
2542+
2543+
/// Add a `::` prefix when comparing paths so that paths with just one item
2544+
/// like "Foo" does not equal the end of "OtherFoo".
2545+
fn comparable_path(&self, did: DefId) -> String {
2546+
format!("::{}", self.tcx.def_path_str(did))
2547+
}
2548+
24762549
/// Creates a `PredicateObligation` with `new_self_ty` replacing the existing type in the
24772550
/// `trait_ref`.
24782551
///
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pub struct Foo;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pub struct Foo;
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
struct Bar;
2+
3+
impl From<Bar> for foo::Foo {
4+
fn from(_: Bar) -> Self {
5+
foo::Foo
6+
}
7+
}
8+
9+
fn main() {
10+
// The user might wrongly expect this to work since From<Bar> for Foo
11+
// implies Into<Foo> for Bar. What the user missed is that different
12+
// versions of Foo exist in the dependency graph, and the impl is for the
13+
// wrong version.
14+
re_export_foo::into_foo(Bar);
15+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
error[E0277]: the trait bound `re_export_foo::foo::Foo: From<Bar>` is not satisfied
2+
--> main.rs:14:29
3+
|
4+
LL | re_export_foo::into_foo(Bar);
5+
| ----------------------- ^^^ the trait `From<Bar>` is not implemented for `re_export_foo::foo::Foo`
6+
| |
7+
| required by a bound introduced by this call
8+
|
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?
15+
= note: required for `Bar` to implement `Into<re_export_foo::foo::Foo>`
16+
note: required by a bound in `into_foo`
17+
--> $DIR/re-export-foo.rs:3:25
18+
|
19+
LL | pub fn into_foo(_: impl Into<foo::Foo>) {}
20+
| ^^^^^^^^^^^^^^ required by this bound in `into_foo`
21+
22+
error: aborting due to 1 previous error
23+
24+
For more information about this error, try `rustc --explain E0277`.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
pub use foo;
2+
3+
pub fn into_foo(_: impl Into<foo::Foo>) {}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
//@ needs-target-std
2+
3+
use run_make_support::{Rustc, cwd, diff, rust_lib_name, rustc};
4+
5+
fn rustc_with_common_args() -> Rustc {
6+
let mut rustc = rustc();
7+
rustc.remap_path_prefix(cwd(), "$DIR");
8+
rustc.edition("2018"); // Don't require `extern crate`
9+
rustc
10+
}
11+
12+
fn main() {
13+
rustc_with_common_args()
14+
.input("foo-v1.rs")
15+
.crate_type("rlib")
16+
.crate_name("foo")
17+
.extra_filename("-v1")
18+
.metadata("-v1")
19+
.run();
20+
21+
rustc_with_common_args()
22+
.input("foo-v2.rs")
23+
.crate_type("rlib")
24+
.crate_name("foo")
25+
.extra_filename("-v2")
26+
.metadata("-v2")
27+
.run();
28+
29+
rustc_with_common_args()
30+
.input("re-export-foo.rs")
31+
.crate_type("rlib")
32+
.extern_("foo", rust_lib_name("foo-v2"))
33+
.run();
34+
35+
let stderr = rustc_with_common_args()
36+
.input("main.rs")
37+
.extern_("foo", rust_lib_name("foo-v1"))
38+
.extern_("re_export_foo", rust_lib_name("re_export_foo"))
39+
.library_search_path(cwd())
40+
.ui_testing()
41+
.run_fail()
42+
.stderr_utf8();
43+
44+
diff().expected_file("main.stderr").actual_text("(rustc)", &stderr).run();
45+
}

0 commit comments

Comments
 (0)