From b2e7ae1f65fd5e1579692a9a7a8c95fb95aa1b04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Wed, 7 Aug 2024 15:08:43 +0000 Subject: [PATCH] Detect multiple crate versions on method not found When a type comes indirectly from one crate version but the imported trait comes from a separate crate version, the called method won't be found. We now show additional context: ``` error[E0599]: no method named `foo` found for struct `dep_2_reexport::Type` in the current scope --> multiple-dep-versions.rs:8:10 | 8 | Type.foo(); | ^^^ method not found in `Type` | note: you have multiple different versions of crate `dependency` in your dependency graph --> multiple-dep-versions.rs:4:32 | 4 | use dependency::{do_something, Trait}; | ^^^^^ `dependency` imported here doesn't correspond to the right crate version | ::: ~/rust/build/x86_64-unknown-linux-gnu/test/run-make/crate-loading/rmake_out/multiple-dep-versions-1.rs:4:1 | 4 | pub trait Trait { | --------------- this is the trait that was imported | ::: ~/rust/build/x86_64-unknown-linux-gnu/test/run-make/crate-loading/rmake_out/multiple-dep-versions-2.rs:4:1 | 4 | pub trait Trait { | --------------- this is the trait that is needed 5 | fn foo(&self); | --- the method is available for `dep_2_reexport::Type` here ``` --- .../rustc_hir_typeck/src/method/suggest.rs | 69 +++++++++++++++++-- .../crate-loading/multiple-dep-versions-1.rs | 10 ++- .../crate-loading/multiple-dep-versions-2.rs | 10 ++- .../crate-loading/multiple-dep-versions-3.rs | 5 ++ .../crate-loading/multiple-dep-versions.rs | 5 +- tests/run-make/crate-loading/rmake.rs | 14 +++- 6 files changed, 99 insertions(+), 14 deletions(-) create mode 100644 tests/run-make/crate-loading/multiple-dep-versions-3.rs diff --git a/compiler/rustc_hir_typeck/src/method/suggest.rs b/compiler/rustc_hir_typeck/src/method/suggest.rs index 61287d98676..d4fcbcad7ae 100644 --- a/compiler/rustc_hir_typeck/src/method/suggest.rs +++ b/compiler/rustc_hir_typeck/src/method/suggest.rs @@ -3448,6 +3448,7 @@ fn suggest_traits_to_import( trait_missing_method: bool, ) { let mut alt_rcvr_sugg = false; + let mut suggest = true; if let (SelfSource::MethodCall(rcvr), false) = (source, unsatisfied_bounds) { debug!( "suggest_traits_to_import: span={:?}, item_name={:?}, rcvr_ty={:?}, rcvr={:?}", @@ -3491,10 +3492,18 @@ fn suggest_traits_to_import( let did = Some(pick.item.container_id(self.tcx)); let skip = skippable.contains(&did); if pick.autoderefs == 0 && !skip { - err.span_label( - pick.item.ident(self.tcx).span, - format!("the method is available for `{rcvr_ty}` here"), + suggest = self.detect_and_explain_multiple_crate_versions( + err, + &pick.item, + rcvr.hir_id.owner, + *rcvr_ty, ); + if suggest { + err.span_label( + pick.item.ident(self.tcx).span, + format!("the method is available for `{rcvr_ty}` here"), + ); + } } break; } @@ -3675,7 +3684,7 @@ fn suggest_traits_to_import( } } } - if self.suggest_valid_traits(err, item_name, valid_out_of_scope_traits, true) { + if suggest && self.suggest_valid_traits(err, item_name, valid_out_of_scope_traits, true) { return; } @@ -4040,6 +4049,58 @@ enum Introducer { } } + fn detect_and_explain_multiple_crate_versions( + &self, + err: &mut Diag<'_>, + item: &ty::AssocItem, + owner: hir::OwnerId, + rcvr_ty: Ty<'_>, + ) -> bool { + let pick_name = self.tcx.crate_name(item.def_id.krate); + if let Some(map) = self.tcx.in_scope_traits_map(owner) { + for trait_candidate in map.to_sorted_stable_ord().into_iter().flat_map(|v| v.1.iter()) { + let name = self.tcx.crate_name(trait_candidate.def_id.krate); + if trait_candidate.def_id.krate != item.def_id.krate && name == pick_name { + let msg = format!( + "you have multiple different versions of crate `{name}` in your \ + dependency graph", + ); + let tdid = self.tcx.parent(item.def_id); + if self.tcx.item_name(trait_candidate.def_id) == self.tcx.item_name(tdid) + && let Some(def_id) = trait_candidate.import_ids.get(0) + { + let span = self.tcx.def_span(*def_id); + let mut multi_span: MultiSpan = span.into(); + multi_span.push_span_label( + span, + format!( + "`{name}` imported here doesn't correspond to the right crate \ + version", + ), + ); + multi_span.push_span_label( + self.tcx.def_span(trait_candidate.def_id), + format!("this is the trait that was imported"), + ); + multi_span.push_span_label( + self.tcx.def_span(tdid), + format!("this is the trait that is needed"), + ); + multi_span.push_span_label( + item.ident(self.tcx).span, + format!("the method is available for `{rcvr_ty}` here"), + ); + err.span_note(multi_span, msg); + return false; + } else { + err.note(msg); + } + } + } + } + true + } + /// issue #102320, for `unwrap_or` with closure as argument, suggest `unwrap_or_else` /// FIXME: currently not working for suggesting `map_or_else`, see #102408 pub(crate) fn suggest_else_fn_with_closure( diff --git a/tests/run-make/crate-loading/multiple-dep-versions-1.rs b/tests/run-make/crate-loading/multiple-dep-versions-1.rs index 2d351633829..94f30ca326f 100644 --- a/tests/run-make/crate-loading/multiple-dep-versions-1.rs +++ b/tests/run-make/crate-loading/multiple-dep-versions-1.rs @@ -1,6 +1,10 @@ #![crate_name = "dependency"] #![crate_type = "rlib"] -pub struct Type; -pub trait Trait {} -impl Trait for Type {} +pub struct Type(pub i32); +pub trait Trait { + fn foo(&self); +} +impl Trait for Type { + fn foo(&self) {} +} pub fn do_something(_: X) {} diff --git a/tests/run-make/crate-loading/multiple-dep-versions-2.rs b/tests/run-make/crate-loading/multiple-dep-versions-2.rs index a5df3dc61ed..0a4626be560 100644 --- a/tests/run-make/crate-loading/multiple-dep-versions-2.rs +++ b/tests/run-make/crate-loading/multiple-dep-versions-2.rs @@ -1,6 +1,10 @@ #![crate_name = "dependency"] #![crate_type = "rlib"] -pub struct Type(pub i32); -pub trait Trait {} -impl Trait for Type {} +pub struct Type; +pub trait Trait { + fn foo(&self); +} +impl Trait for Type { + fn foo(&self) {} +} pub fn do_something(_: X) {} diff --git a/tests/run-make/crate-loading/multiple-dep-versions-3.rs b/tests/run-make/crate-loading/multiple-dep-versions-3.rs new file mode 100644 index 00000000000..07d888e9f10 --- /dev/null +++ b/tests/run-make/crate-loading/multiple-dep-versions-3.rs @@ -0,0 +1,5 @@ +#![crate_name = "foo"] +#![crate_type = "rlib"] + +extern crate dependency; +pub use dependency::Type; diff --git a/tests/run-make/crate-loading/multiple-dep-versions.rs b/tests/run-make/crate-loading/multiple-dep-versions.rs index 5a6cb03aaa4..113a6b76d7d 100644 --- a/tests/run-make/crate-loading/multiple-dep-versions.rs +++ b/tests/run-make/crate-loading/multiple-dep-versions.rs @@ -1,8 +1,9 @@ extern crate dep_2_reexport; extern crate dependency; -use dep_2_reexport::do_something; -use dependency::Type; +use dep_2_reexport::Type; +use dependency::{do_something, Trait}; fn main() { do_something(Type); + Type.foo(); } diff --git a/tests/run-make/crate-loading/rmake.rs b/tests/run-make/crate-loading/rmake.rs index d7abd5872c9..5da706624ae 100644 --- a/tests/run-make/crate-loading/rmake.rs +++ b/tests/run-make/crate-loading/rmake.rs @@ -7,11 +7,15 @@ fn main() { rustc().input("multiple-dep-versions-1.rs").run(); rustc().input("multiple-dep-versions-2.rs").extra_filename("2").metadata("2").run(); + rustc() + .input("multiple-dep-versions-3.rs") + .extern_("dependency", rust_lib_name("dependency2")) + .run(); rustc() .input("multiple-dep-versions.rs") .extern_("dependency", rust_lib_name("dependency")) - .extern_("dep_2_reexport", rust_lib_name("dependency2")) + .extern_("dep_2_reexport", rust_lib_name("foo")) .run_fail() .assert_stderr_contains( "you have multiple different versions of crate `dependency` in your dependency graph", @@ -22,5 +26,11 @@ fn main() { ) .assert_stderr_contains("this type doesn't implement the required trait") .assert_stderr_contains("this type implements the required trait") - .assert_stderr_contains("this is the required trait"); + .assert_stderr_contains("this is the required trait") + .assert_stderr_contains( + "`dependency` imported here doesn't correspond to the right crate version", + ) + .assert_stderr_contains("this is the trait that was imported") + .assert_stderr_contains("this is the trait that is needed") + .assert_stderr_contains("the method is available for `dep_2_reexport::Type` here"); }