about summary refs log tree commit diff
path: root/compiler
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2024-08-17 14:40:04 +0000
committerbors <bors@rust-lang.org>2024-08-17 14:40:04 +0000
commit9b318d2e93ce35e7ba32d8cfa96a1dbe63a7bed1 (patch)
tree2dd72d118de1410abacbc3cad76da93abb3c5585 /compiler
parentc6f81a452e291dd4ff5bcbc6ecb9cd6f2271f164 (diff)
parent110b19b2b6a9896d4d73b8bd8b735fa9c2503d1e (diff)
downloadrust-9b318d2e93ce35e7ba32d8cfa96a1dbe63a7bed1.tar.gz
rust-9b318d2e93ce35e7ba32d8cfa96a1dbe63a7bed1.zip
Auto merge of #128786 - estebank:multiple-crate-versions, r=fee1-dead
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: there are multiple different versions of crate `dependency` in the 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
```

Fix #128569, fix #110926, fix #109161, fix #81659, fix #51458, fix #32611. Follow up to #124944.
Diffstat (limited to 'compiler')
-rw-r--r--compiler/rustc_hir_typeck/src/method/suggest.rs97
-rw-r--r--compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs4
2 files changed, 96 insertions, 5 deletions
diff --git a/compiler/rustc_hir_typeck/src/method/suggest.rs b/compiler/rustc_hir_typeck/src/method/suggest.rs
index 3b71e2f19b1..b3cf73bac1a 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 trait_in_other_version_found = false;
         if let (SelfSource::MethodCall(rcvr), false) = (source, unsatisfied_bounds) {
             debug!(
                 "suggest_traits_to_import: span={:?}, item_name={:?}, rcvr_ty={:?}, rcvr={:?}",
@@ -3489,8 +3490,17 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                         // self types and rely on the suggestion to `use` the trait from
                         // `suggest_valid_traits`.
                         let did = Some(pick.item.container_id(self.tcx));
-                        let skip = skippable.contains(&did);
-                        if pick.autoderefs == 0 && !skip {
+                        if skippable.contains(&did) {
+                            continue;
+                        }
+                        trait_in_other_version_found = self
+                            .detect_and_explain_multiple_crate_versions(
+                                err,
+                                pick.item.def_id,
+                                rcvr.hir_id,
+                                *rcvr_ty,
+                            );
+                        if pick.autoderefs == 0 && !trait_in_other_version_found {
                             err.span_label(
                                 pick.item.ident(self.tcx).span,
                                 format!("the method is available for `{rcvr_ty}` here"),
@@ -3675,7 +3685,32 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                 }
             }
         }
-        if self.suggest_valid_traits(err, item_name, valid_out_of_scope_traits, true) {
+
+        if let SelfSource::QPath(ty) = source
+            && !valid_out_of_scope_traits.is_empty()
+            && let hir::TyKind::Path(path) = ty.kind
+            && let hir::QPath::Resolved(_, path) = path
+            && let Some(def_id) = path.res.opt_def_id()
+            && let Some(assoc) = self
+                .tcx
+                .associated_items(valid_out_of_scope_traits[0])
+                .filter_by_name_unhygienic(item_name.name)
+                .next()
+        {
+            // See if the `Type::function(val)` where `function` wasn't found corresponds to a
+            // `Trait` that is imported directly, but `Type` came from a different version of the
+            // same crate.
+            let rcvr_ty = self.tcx.type_of(def_id).instantiate_identity();
+            trait_in_other_version_found = self.detect_and_explain_multiple_crate_versions(
+                err,
+                assoc.def_id,
+                ty.hir_id,
+                rcvr_ty,
+            );
+        }
+        if !trait_in_other_version_found
+            && self.suggest_valid_traits(err, item_name, valid_out_of_scope_traits, true)
+        {
             return;
         }
 
@@ -4040,6 +4075,62 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
         }
     }
 
+    fn detect_and_explain_multiple_crate_versions(
+        &self,
+        err: &mut Diag<'_>,
+        item_def_id: DefId,
+        hir_id: hir::HirId,
+        rcvr_ty: Ty<'_>,
+    ) -> bool {
+        let hir_id = self.tcx.parent_hir_id(hir_id);
+        let Some(traits) = self.tcx.in_scope_traits(hir_id) else { return false };
+        if traits.is_empty() {
+            return false;
+        }
+        let trait_def_id = self.tcx.parent(item_def_id);
+        let krate = self.tcx.crate_name(trait_def_id.krate);
+        let name = self.tcx.item_name(trait_def_id);
+        let candidates: Vec<_> = traits
+            .iter()
+            .filter(|c| {
+                c.def_id.krate != trait_def_id.krate
+                    && self.tcx.crate_name(c.def_id.krate) == krate
+                    && self.tcx.item_name(c.def_id) == name
+            })
+            .map(|c| (c.def_id, c.import_ids.get(0).cloned()))
+            .collect();
+        if candidates.is_empty() {
+            return false;
+        }
+        let item_span = self.tcx.def_span(item_def_id);
+        let msg = format!(
+            "there are multiple different versions of crate `{krate}` in the dependency graph",
+        );
+        let trait_span = self.tcx.def_span(trait_def_id);
+        let mut multi_span: MultiSpan = trait_span.into();
+        multi_span.push_span_label(trait_span, format!("this is the trait that is needed"));
+        let descr = self.tcx.associated_item(item_def_id).descr();
+        multi_span
+            .push_span_label(item_span, format!("the {descr} is available for `{rcvr_ty}` here"));
+        for (def_id, import_def_id) in candidates {
+            if let Some(import_def_id) = import_def_id {
+                multi_span.push_span_label(
+                    self.tcx.def_span(import_def_id),
+                    format!(
+                        "`{name}` imported here doesn't correspond to the right version of crate \
+                         `{krate}`",
+                    ),
+                );
+            }
+            multi_span.push_span_label(
+                self.tcx.def_span(def_id),
+                format!("this is the trait that was imported"),
+            );
+        }
+        err.span_note(multi_span, 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/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs b/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs
index 326a0e4e35c..04769bf79c9 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
@@ -1689,11 +1689,11 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
             err.highlighted_span_help(
                 span,
                 vec![
-                    StringPart::normal("you have ".to_string()),
+                    StringPart::normal("there are ".to_string()),
                     StringPart::highlighted("multiple different versions".to_string()),
                     StringPart::normal(" of crate `".to_string()),
                     StringPart::highlighted(format!("{name}")),
-                    StringPart::normal("` in your dependency graph".to_string()),
+                    StringPart::normal("` the your dependency graph".to_string()),
                 ],
             );
             let candidates = if impl_candidates.is_empty() {