about summary refs log tree commit diff
diff options
context:
space:
mode:
authorEsteban Küber <esteban@kuber.com.ar>2024-08-07 15:08:43 +0000
committerEsteban Küber <esteban@kuber.com.ar>2024-08-12 19:29:47 +0000
commitb2e7ae1f65fd5e1579692a9a7a8c95fb95aa1b04 (patch)
tree95604b39904a1e74363d100da4f7f510c9b5e83e
parent91376f416222a238227c84a848d168835ede2cc3 (diff)
downloadrust-b2e7ae1f65fd5e1579692a9a7a8c95fb95aa1b04.tar.gz
rust-b2e7ae1f65fd5e1579692a9a7a8c95fb95aa1b04.zip
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
```
-rw-r--r--compiler/rustc_hir_typeck/src/method/suggest.rs69
-rw-r--r--tests/run-make/crate-loading/multiple-dep-versions-1.rs10
-rw-r--r--tests/run-make/crate-loading/multiple-dep-versions-2.rs10
-rw-r--r--tests/run-make/crate-loading/multiple-dep-versions-3.rs5
-rw-r--r--tests/run-make/crate-loading/multiple-dep-versions.rs5
-rw-r--r--tests/run-make/crate-loading/rmake.rs14
6 files changed, 99 insertions, 14 deletions
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 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
         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 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                         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 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                 }
             }
         }
-        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 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
         }
     }
 
+    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: Trait>(_: 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: Trait>(_: 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 @@ use run_make_support::{rust_lib_name, rustc};
 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");
 }