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
```
This commit is contained in:
Esteban Küber 2024-08-07 15:08:43 +00:00
parent 91376f4162
commit b2e7ae1f65
6 changed files with 99 additions and 14 deletions

View File

@ -3448,6 +3448,7 @@ fn suggest_traits_to_import(
trait_missing_method: bool, trait_missing_method: bool,
) { ) {
let mut alt_rcvr_sugg = false; let mut alt_rcvr_sugg = false;
let mut suggest = true;
if let (SelfSource::MethodCall(rcvr), false) = (source, unsatisfied_bounds) { if let (SelfSource::MethodCall(rcvr), false) = (source, unsatisfied_bounds) {
debug!( debug!(
"suggest_traits_to_import: span={:?}, item_name={:?}, rcvr_ty={:?}, rcvr={:?}", "suggest_traits_to_import: span={:?}, item_name={:?}, rcvr_ty={:?}, rcvr={:?}",
@ -3491,11 +3492,19 @@ fn suggest_traits_to_import(
let did = Some(pick.item.container_id(self.tcx)); let did = Some(pick.item.container_id(self.tcx));
let skip = skippable.contains(&did); let skip = skippable.contains(&did);
if pick.autoderefs == 0 && !skip { if pick.autoderefs == 0 && !skip {
suggest = self.detect_and_explain_multiple_crate_versions(
err,
&pick.item,
rcvr.hir_id.owner,
*rcvr_ty,
);
if suggest {
err.span_label( err.span_label(
pick.item.ident(self.tcx).span, pick.item.ident(self.tcx).span,
format!("the method is available for `{rcvr_ty}` here"), format!("the method is available for `{rcvr_ty}` here"),
); );
} }
}
break; break;
} }
Err(MethodError::Ambiguity(_)) => { Err(MethodError::Ambiguity(_)) => {
@ -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; 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` /// issue #102320, for `unwrap_or` with closure as argument, suggest `unwrap_or_else`
/// FIXME: currently not working for suggesting `map_or_else`, see #102408 /// FIXME: currently not working for suggesting `map_or_else`, see #102408
pub(crate) fn suggest_else_fn_with_closure( pub(crate) fn suggest_else_fn_with_closure(

View File

@ -1,6 +1,10 @@
#![crate_name = "dependency"] #![crate_name = "dependency"]
#![crate_type = "rlib"] #![crate_type = "rlib"]
pub struct Type; pub struct Type(pub i32);
pub trait Trait {} pub trait Trait {
impl Trait for Type {} fn foo(&self);
}
impl Trait for Type {
fn foo(&self) {}
}
pub fn do_something<X: Trait>(_: X) {} pub fn do_something<X: Trait>(_: X) {}

View File

@ -1,6 +1,10 @@
#![crate_name = "dependency"] #![crate_name = "dependency"]
#![crate_type = "rlib"] #![crate_type = "rlib"]
pub struct Type(pub i32); pub struct Type;
pub trait Trait {} pub trait Trait {
impl Trait for Type {} fn foo(&self);
}
impl Trait for Type {
fn foo(&self) {}
}
pub fn do_something<X: Trait>(_: X) {} pub fn do_something<X: Trait>(_: X) {}

View File

@ -0,0 +1,5 @@
#![crate_name = "foo"]
#![crate_type = "rlib"]
extern crate dependency;
pub use dependency::Type;

View File

@ -1,8 +1,9 @@
extern crate dep_2_reexport; extern crate dep_2_reexport;
extern crate dependency; extern crate dependency;
use dep_2_reexport::do_something; use dep_2_reexport::Type;
use dependency::Type; use dependency::{do_something, Trait};
fn main() { fn main() {
do_something(Type); do_something(Type);
Type.foo();
} }

View File

@ -7,11 +7,15 @@
fn main() { fn main() {
rustc().input("multiple-dep-versions-1.rs").run(); 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-2.rs").extra_filename("2").metadata("2").run();
rustc()
.input("multiple-dep-versions-3.rs")
.extern_("dependency", rust_lib_name("dependency2"))
.run();
rustc() rustc()
.input("multiple-dep-versions.rs") .input("multiple-dep-versions.rs")
.extern_("dependency", rust_lib_name("dependency")) .extern_("dependency", rust_lib_name("dependency"))
.extern_("dep_2_reexport", rust_lib_name("dependency2")) .extern_("dep_2_reexport", rust_lib_name("foo"))
.run_fail() .run_fail()
.assert_stderr_contains( .assert_stderr_contains(
"you have multiple different versions of crate `dependency` in your dependency graph", "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 doesn't implement the required trait")
.assert_stderr_contains("this type implements 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");
} }