From 22bb4fe147127d72f31466e91768fb1de6347fab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Wed, 31 Dec 2025 23:40:16 +0000 Subject: [PATCH] Detect cases where `?` is applied on a type that could be coming from a different crate version than expected ``` error[E0277]: `?` couldn't convert the error to `dependency::Error` --> replaced | LL | fn main() -> Result<(), Error> { | ----------------- expected `dependency::Error` because of this ... LL | Err(Error2)?; | -----------^ the trait `From` is not implemented for `dependency::Error` | | | this can't be annotated with `?` because it has type `Result<_, Error2>` | = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait help: the trait `From` is not implemented for `dependency::Error` but trait `From<()>` is implemented for it --> replaced | LL | impl From<()> for Error { | ^^^^^^^^^^^^^^^^^^^^^^^ = help: for that trait implementation, expected `()`, found `Error2` = note: there are multiple different versions of crate `dependency` in the dependency graph = help: you can use `cargo tree` to explore your dependency tree ``` The existing checks rely on having access to the actual types/traits that diverged to detect they are called the same, come from different crates with the same name. The new check is less specific, merely looking to see if the crate name the involved type belongs has multiple crates. --- .../src/rmeta/decoder/cstore_impl.rs | 9 ++++ compiler/rustc_middle/src/query/mod.rs | 9 ++++ .../traits/fulfillment_errors.rs | 22 +++++++-- .../src/error_reporting/traits/mod.rs | 2 +- .../run-make/crate-loading/dep-2-reexport.rs | 16 ++++++- tests/run-make/crate-loading/dependency-1.rs | 24 ++++++++++ tests/run-make/crate-loading/dependency-2.rs | 24 ++++++++++ .../crate-loading/multiple-dep-versions.rs | 13 ++++-- .../multiple-dep-versions.stderr | 46 ++++++++++++++++++- 9 files changed, 156 insertions(+), 9 deletions(-) diff --git a/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs b/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs index 36fe7f380069..7bd3f7db55f9 100644 --- a/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs +++ b/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs @@ -559,6 +559,15 @@ pub(in crate::rmeta) fn provide(providers: &mut Providers) { .filter_map(|(cnum, data)| data.used().then_some(cnum)), ) }, + duplicate_crate_names: |tcx, c: CrateNum| { + let name = tcx.crate_name(c); + tcx.arena.alloc_from_iter( + tcx.crates(()) + .into_iter() + .filter(|k| tcx.crate_name(**k) == name && **k != c) + .map(|c| *c), + ) + }, ..providers.queries }; provide_extern(&mut providers.extern_queries); diff --git a/compiler/rustc_middle/src/query/mod.rs b/compiler/rustc_middle/src/query/mod.rs index 676f0d82e4fb..88917ad8db0d 100644 --- a/compiler/rustc_middle/src/query/mod.rs +++ b/compiler/rustc_middle/src/query/mod.rs @@ -2339,6 +2339,7 @@ rustc_queries! { eval_always desc { "fetching all foreign CrateNum instances" } } + // Crates that are loaded non-speculatively (not for diagnostics or doc links). // FIXME: This is currently only used for collecting lang items, but should be used instead of // `crates` in most other cases too. @@ -2347,6 +2348,14 @@ rustc_queries! { desc { "fetching `CrateNum`s for all crates loaded non-speculatively" } } + /// All crates that share the same name as crate `c`. + /// + /// This normally occurs when multiple versions of the same dependency are present in the + /// dependency tree. + query duplicate_crate_names(c: CrateNum) -> &'tcx [CrateNum] { + desc { "fetching `CrateNum`s with same name as `{c:?}`" } + } + /// A list of all traits in a crate, used by rustdoc and error reporting. query traits(_: CrateNum) -> &'tcx [DefId] { desc { "fetching all traits in a crate" } diff --git a/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs b/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs index d96a1b0a8c0e..8d779f40a13a 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs @@ -33,6 +33,7 @@ use rustc_middle::ty::{ TypeVisitableExt, Upcast, }; use rustc_middle::{bug, span_bug}; +use rustc_span::def_id::CrateNum; use rustc_span::{BytePos, DUMMY_SP, STDLIB_STABLE_CRATES, Span, Symbol, sym}; use tracing::{debug, instrument}; @@ -2172,6 +2173,13 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { self.suggest_function_pointers_impl(None, &exp_found, err); } + if let ty::Adt(def, _) = trait_pred.self_ty().skip_binder().peel_refs().kind() + && let crates = self.tcx.duplicate_crate_names(def.did().krate) + && !crates.is_empty() + { + self.note_two_crate_versions(def.did().krate, MultiSpan::new(), err); + err.help("you can use `cargo tree` to explore your dependency tree"); + } true }) }) { @@ -2282,6 +2290,14 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { } )); } + + if let ty::Adt(def, _) = trait_pred.self_ty().skip_binder().peel_refs().kind() + && let crates = self.tcx.duplicate_crate_names(def.did().krate) + && !crates.is_empty() + { + self.note_two_crate_versions(def.did().krate, MultiSpan::new(), err); + err.help("you can use `cargo tree` to explore your dependency tree"); + } true }; @@ -2445,11 +2461,11 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { pub fn note_two_crate_versions( &self, - did: DefId, + krate: CrateNum, sp: impl Into, err: &mut Diag<'_>, ) { - let crate_name = self.tcx.crate_name(did.krate); + let crate_name = self.tcx.crate_name(krate); let crate_msg = format!( "there are multiple different versions of crate `{crate_name}` in the dependency graph" ); @@ -2514,7 +2530,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { 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, MultiSpan::new(), err); + self.note_two_crate_versions(similar_item.krate, MultiSpan::new(), err); } } diff --git a/compiler/rustc_trait_selection/src/error_reporting/traits/mod.rs b/compiler/rustc_trait_selection/src/error_reporting/traits/mod.rs index 3ba984b90a1d..a7a685d62b34 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/traits/mod.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/traits/mod.rs @@ -442,7 +442,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { ); } } - self.note_two_crate_versions(expected_did, span, err); + self.note_two_crate_versions(expected_did.krate, span, err); err.help("you can use `cargo tree` to explore your dependency tree"); } suggested diff --git a/tests/run-make/crate-loading/dep-2-reexport.rs b/tests/run-make/crate-loading/dep-2-reexport.rs index 07444511472f..8b6ee6749dcc 100644 --- a/tests/run-make/crate-loading/dep-2-reexport.rs +++ b/tests/run-make/crate-loading/dep-2-reexport.rs @@ -2,9 +2,23 @@ #![crate_type = "rlib"] extern crate dependency; -pub use dependency::{Trait2, Type, do_something_trait, do_something_type}; +pub use dependency::{Error, OtherError, Trait2, Type, do_something_trait, do_something_type}; pub struct OtherType; impl dependency::Trait for OtherType { fn foo(&self) {} fn bar() {} } +#[derive(Debug)] +pub struct Error2; + +impl From for Error2 { + fn from(_: Error) -> Error2 { + Error2 + } +} + +impl From for Error2 { + fn from(_: OtherError) -> Error2 { + Error2 + } +} diff --git a/tests/run-make/crate-loading/dependency-1.rs b/tests/run-make/crate-loading/dependency-1.rs index bfeabccf5c14..0eea7a7fae8b 100644 --- a/tests/run-make/crate-loading/dependency-1.rs +++ b/tests/run-make/crate-loading/dependency-1.rs @@ -13,3 +13,27 @@ impl Trait for Type { pub fn do_something(_: X) {} pub fn do_something_type(_: Type) {} pub fn do_something_trait(_: Box) {} + +#[derive(Debug)] +pub struct Error; + +impl From<()> for Error { + fn from(t: ()) -> Error { + Error + } +} + +#[derive(Debug)] +pub struct OtherError; + +impl From<()> for OtherError { + fn from(t: ()) -> OtherError { + OtherError + } +} + +impl From for OtherError { + fn from(t: i32) -> OtherError { + OtherError + } +} diff --git a/tests/run-make/crate-loading/dependency-2.rs b/tests/run-make/crate-loading/dependency-2.rs index 682d1ff64b82..a07873297c54 100644 --- a/tests/run-make/crate-loading/dependency-2.rs +++ b/tests/run-make/crate-loading/dependency-2.rs @@ -14,3 +14,27 @@ impl Trait for Type { pub fn do_something(_: X) {} pub fn do_something_type(_: Type) {} pub fn do_something_trait(_: Box) {} + +#[derive(Debug)] +pub struct Error; + +impl From<()> for Error { + fn from(t: ()) -> Error { + Error + } +} + +#[derive(Debug)] +pub struct OtherError; + +impl From<()> for OtherError { + fn from(_: ()) -> OtherError { + OtherError + } +} + +impl From for OtherError { + fn from(_: i32) -> OtherError { + OtherError + } +} diff --git a/tests/run-make/crate-loading/multiple-dep-versions.rs b/tests/run-make/crate-loading/multiple-dep-versions.rs index 3a4a20d38fc8..69b8360a8c92 100644 --- a/tests/run-make/crate-loading/multiple-dep-versions.rs +++ b/tests/run-make/crate-loading/multiple-dep-versions.rs @@ -1,13 +1,20 @@ extern crate dep_2_reexport; extern crate dependency; -use dep_2_reexport::{OtherType, Trait2, Type}; -use dependency::{Trait, do_something, do_something_trait, do_something_type}; +use dep_2_reexport::{Error2, OtherType, Trait2, Type}; +use dependency::{Error, OtherError, Trait, do_something, do_something_trait, do_something_type}; -fn main() { +fn main() -> Result<(), Error> { do_something(Type); Type.foo(); Type::bar(); do_something(OtherType); do_something_type(Type); do_something_trait(Box::new(Type) as Box); + Err(Error2)?; + Ok(()) +} + +fn foo() -> Result<(), OtherError> { + Err(Error2)?; + Ok(()) } diff --git a/tests/run-make/crate-loading/multiple-dep-versions.stderr b/tests/run-make/crate-loading/multiple-dep-versions.stderr index 235a44a8ce89..f8f8bfaaff6f 100644 --- a/tests/run-make/crate-loading/multiple-dep-versions.stderr +++ b/tests/run-make/crate-loading/multiple-dep-versions.stderr @@ -144,7 +144,51 @@ note: function defined here LL | pub fn do_something_trait(_: Box) {} | ^^^^^^^^^^^^^^^^^^ -error: aborting due to 6 previous errors +error[E0277]: `?` couldn't convert the error to `dependency::Error` + --> replaced + | +LL | fn main() -> Result<(), Error> { + | ----------------- expected `dependency::Error` because of this +... +LL | Err(Error2)?; + | -----------^ the trait `From` is not implemented for `dependency::Error` + | | + | this can't be annotated with `?` because it has type `Result<_, Error2>` + | + = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait +help: the trait `From` is not implemented for `dependency::Error` + but trait `From<()>` is implemented for it + --> replaced + | +LL | impl From<()> for Error { + | ^^^^^^^^^^^^^^^^^^^^^^^ + = help: for that trait implementation, expected `()`, found `Error2` + = note: there are multiple different versions of crate `dependency` in the dependency graph + = help: you can use `cargo tree` to explore your dependency tree + +error[E0277]: `?` couldn't convert the error to `dependency::OtherError` + --> replaced + | +LL | fn foo() -> Result<(), OtherError> { + | ---------------------- expected `dependency::OtherError` because of this +LL | Err(Error2)?; + | -----------^ the trait `From` is not implemented for `dependency::OtherError` + | | + | this can't be annotated with `?` because it has type `Result<_, Error2>` + | + = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait +help: the following other types implement trait `From` + --> replaced + | +LL | impl From<()> for OtherError { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `dependency::OtherError` implements `From<()>` +... +LL | impl From for OtherError { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `dependency::OtherError` implements `From` + = note: there are multiple different versions of crate `dependency` in the dependency graph + = help: you can use `cargo tree` to explore your dependency tree + +error: aborting due to 8 previous errors Some errors have detailed explanations: E0277, E0308, E0599. For more information about an error, try `rustc --explain E0277`. \ No newline at end of file