Rollup merge of #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/rust#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
try-job: aarch64-msvc-1
This commit is contained in:
@@ -467,7 +467,8 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
|
||||
span,
|
||||
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_derive(&obligation, &mut err, leaf_trait_predicate);
|
||||
|
||||
@@ -2424,7 +2425,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
|
||||
/// If the `Self` type of the unsatisfied trait `trait_ref` implements a trait
|
||||
/// with the same path as `trait_ref`, a help message about
|
||||
/// a probable version mismatch is added to `err`
|
||||
fn note_version_mismatch(
|
||||
fn note_trait_version_mismatch(
|
||||
&self,
|
||||
err: &mut Diag<'_>,
|
||||
trait_pred: ty::PolyTraitPredicate<'tcx>,
|
||||
@@ -2464,15 +2465,87 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
|
||||
impl_spans,
|
||||
format!("trait impl{} with same name found", pluralize!(trait_impls.len())),
|
||||
);
|
||||
let trait_crate = self.tcx.crate_name(trait_with_same_path.krate);
|
||||
let crate_msg =
|
||||
format!("perhaps two different versions of crate `{trait_crate}` are being used?");
|
||||
err.note(crate_msg);
|
||||
self.note_two_crate_versions(trait_with_same_path, err);
|
||||
suggested = true;
|
||||
}
|
||||
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
|
||||
/// `trait_ref`.
|
||||
///
|
||||
|
||||
1
tests/run-make/duplicate-dependency/foo-v1.rs
Normal file
1
tests/run-make/duplicate-dependency/foo-v1.rs
Normal file
@@ -0,0 +1 @@
|
||||
pub struct Foo;
|
||||
1
tests/run-make/duplicate-dependency/foo-v2.rs
Normal file
1
tests/run-make/duplicate-dependency/foo-v2.rs
Normal file
@@ -0,0 +1 @@
|
||||
pub struct Foo;
|
||||
15
tests/run-make/duplicate-dependency/main.rs
Normal file
15
tests/run-make/duplicate-dependency/main.rs
Normal file
@@ -0,0 +1,15 @@
|
||||
struct Bar;
|
||||
|
||||
impl From<Bar> for foo::Foo {
|
||||
fn from(_: Bar) -> Self {
|
||||
foo::Foo
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// The user might wrongly expect this to work since From<Bar> for Foo
|
||||
// implies Into<Foo> for Bar. What the user missed is that different
|
||||
// versions of Foo exist in the dependency graph, and the impl is for the
|
||||
// wrong version.
|
||||
re_export_foo::into_foo(Bar);
|
||||
}
|
||||
24
tests/run-make/duplicate-dependency/main.stderr
Normal file
24
tests/run-make/duplicate-dependency/main.stderr
Normal file
@@ -0,0 +1,24 @@
|
||||
error[E0277]: the trait bound `re_export_foo::foo::Foo: From<Bar>` is not satisfied
|
||||
--> main.rs:14:29
|
||||
|
|
||||
LL | re_export_foo::into_foo(Bar);
|
||||
| ----------------------- ^^^ the trait `From<Bar>` is not implemented for `re_export_foo::foo::Foo`
|
||||
| |
|
||||
| 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 by a bound in `into_foo`
|
||||
--> $DIR/re-export-foo.rs:3:25
|
||||
|
|
||||
LL | pub fn into_foo(_: impl Into<foo::Foo>) {}
|
||||
| ^^^^^^^^^^^^^^ required by this bound in `into_foo`
|
||||
|
||||
error: aborting due to 1 previous error
|
||||
|
||||
For more information about this error, try `rustc --explain E0277`.
|
||||
3
tests/run-make/duplicate-dependency/re-export-foo.rs
Normal file
3
tests/run-make/duplicate-dependency/re-export-foo.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
pub use foo;
|
||||
|
||||
pub fn into_foo(_: impl Into<foo::Foo>) {}
|
||||
45
tests/run-make/duplicate-dependency/rmake.rs
Normal file
45
tests/run-make/duplicate-dependency/rmake.rs
Normal file
@@ -0,0 +1,45 @@
|
||||
//@ needs-target-std
|
||||
|
||||
use run_make_support::{Rustc, cwd, diff, rust_lib_name, rustc};
|
||||
|
||||
fn rustc_with_common_args() -> Rustc {
|
||||
let mut rustc = rustc();
|
||||
rustc.remap_path_prefix(cwd(), "$DIR");
|
||||
rustc.edition("2018"); // Don't require `extern crate`
|
||||
rustc
|
||||
}
|
||||
|
||||
fn main() {
|
||||
rustc_with_common_args()
|
||||
.input("foo-v1.rs")
|
||||
.crate_type("rlib")
|
||||
.crate_name("foo")
|
||||
.extra_filename("-v1")
|
||||
.metadata("-v1")
|
||||
.run();
|
||||
|
||||
rustc_with_common_args()
|
||||
.input("foo-v2.rs")
|
||||
.crate_type("rlib")
|
||||
.crate_name("foo")
|
||||
.extra_filename("-v2")
|
||||
.metadata("-v2")
|
||||
.run();
|
||||
|
||||
rustc_with_common_args()
|
||||
.input("re-export-foo.rs")
|
||||
.crate_type("rlib")
|
||||
.extern_("foo", rust_lib_name("foo-v2"))
|
||||
.run();
|
||||
|
||||
let stderr = rustc_with_common_args()
|
||||
.input("main.rs")
|
||||
.extern_("foo", rust_lib_name("foo-v1"))
|
||||
.extern_("re_export_foo", rust_lib_name("re_export_foo"))
|
||||
.library_search_path(cwd())
|
||||
.ui_testing()
|
||||
.run_fail()
|
||||
.stderr_utf8();
|
||||
|
||||
diff().expected_file("main.stderr").normalize(r"\\", "/").actual_text("(rustc)", &stderr).run();
|
||||
}
|
||||
Reference in New Issue
Block a user