Skip to content

Commit b2e7ae1

Browse files
committed
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 ```
1 parent 91376f4 commit b2e7ae1

File tree

6 files changed

+99
-14
lines changed

6 files changed

+99
-14
lines changed

compiler/rustc_hir_typeck/src/method/suggest.rs

Lines changed: 65 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3448,6 +3448,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
34483448
trait_missing_method: bool,
34493449
) {
34503450
let mut alt_rcvr_sugg = false;
3451+
let mut suggest = true;
34513452
if let (SelfSource::MethodCall(rcvr), false) = (source, unsatisfied_bounds) {
34523453
debug!(
34533454
"suggest_traits_to_import: span={:?}, item_name={:?}, rcvr_ty={:?}, rcvr={:?}",
@@ -3491,10 +3492,18 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
34913492
let did = Some(pick.item.container_id(self.tcx));
34923493
let skip = skippable.contains(&did);
34933494
if pick.autoderefs == 0 && !skip {
3494-
err.span_label(
3495-
pick.item.ident(self.tcx).span,
3496-
format!("the method is available for `{rcvr_ty}` here"),
3495+
suggest = self.detect_and_explain_multiple_crate_versions(
3496+
err,
3497+
&pick.item,
3498+
rcvr.hir_id.owner,
3499+
*rcvr_ty,
34973500
);
3501+
if suggest {
3502+
err.span_label(
3503+
pick.item.ident(self.tcx).span,
3504+
format!("the method is available for `{rcvr_ty}` here"),
3505+
);
3506+
}
34983507
}
34993508
break;
35003509
}
@@ -3675,7 +3684,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
36753684
}
36763685
}
36773686
}
3678-
if self.suggest_valid_traits(err, item_name, valid_out_of_scope_traits, true) {
3687+
if suggest && self.suggest_valid_traits(err, item_name, valid_out_of_scope_traits, true) {
36793688
return;
36803689
}
36813690

@@ -4040,6 +4049,58 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
40404049
}
40414050
}
40424051

4052+
fn detect_and_explain_multiple_crate_versions(
4053+
&self,
4054+
err: &mut Diag<'_>,
4055+
item: &ty::AssocItem,
4056+
owner: hir::OwnerId,
4057+
rcvr_ty: Ty<'_>,
4058+
) -> bool {
4059+
let pick_name = self.tcx.crate_name(item.def_id.krate);
4060+
if let Some(map) = self.tcx.in_scope_traits_map(owner) {
4061+
for trait_candidate in map.to_sorted_stable_ord().into_iter().flat_map(|v| v.1.iter()) {
4062+
let name = self.tcx.crate_name(trait_candidate.def_id.krate);
4063+
if trait_candidate.def_id.krate != item.def_id.krate && name == pick_name {
4064+
let msg = format!(
4065+
"you have multiple different versions of crate `{name}` in your \
4066+
dependency graph",
4067+
);
4068+
let tdid = self.tcx.parent(item.def_id);
4069+
if self.tcx.item_name(trait_candidate.def_id) == self.tcx.item_name(tdid)
4070+
&& let Some(def_id) = trait_candidate.import_ids.get(0)
4071+
{
4072+
let span = self.tcx.def_span(*def_id);
4073+
let mut multi_span: MultiSpan = span.into();
4074+
multi_span.push_span_label(
4075+
span,
4076+
format!(
4077+
"`{name}` imported here doesn't correspond to the right crate \
4078+
version",
4079+
),
4080+
);
4081+
multi_span.push_span_label(
4082+
self.tcx.def_span(trait_candidate.def_id),
4083+
format!("this is the trait that was imported"),
4084+
);
4085+
multi_span.push_span_label(
4086+
self.tcx.def_span(tdid),
4087+
format!("this is the trait that is needed"),
4088+
);
4089+
multi_span.push_span_label(
4090+
item.ident(self.tcx).span,
4091+
format!("the method is available for `{rcvr_ty}` here"),
4092+
);
4093+
err.span_note(multi_span, msg);
4094+
return false;
4095+
} else {
4096+
err.note(msg);
4097+
}
4098+
}
4099+
}
4100+
}
4101+
true
4102+
}
4103+
40434104
/// issue #102320, for `unwrap_or` with closure as argument, suggest `unwrap_or_else`
40444105
/// FIXME: currently not working for suggesting `map_or_else`, see #102408
40454106
pub(crate) fn suggest_else_fn_with_closure(
Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
#![crate_name = "dependency"]
22
#![crate_type = "rlib"]
3-
pub struct Type;
4-
pub trait Trait {}
5-
impl Trait for Type {}
3+
pub struct Type(pub i32);
4+
pub trait Trait {
5+
fn foo(&self);
6+
}
7+
impl Trait for Type {
8+
fn foo(&self) {}
9+
}
610
pub fn do_something<X: Trait>(_: X) {}
Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
#![crate_name = "dependency"]
22
#![crate_type = "rlib"]
3-
pub struct Type(pub i32);
4-
pub trait Trait {}
5-
impl Trait for Type {}
3+
pub struct Type;
4+
pub trait Trait {
5+
fn foo(&self);
6+
}
7+
impl Trait for Type {
8+
fn foo(&self) {}
9+
}
610
pub fn do_something<X: Trait>(_: X) {}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#![crate_name = "foo"]
2+
#![crate_type = "rlib"]
3+
4+
extern crate dependency;
5+
pub use dependency::Type;
Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
extern crate dep_2_reexport;
22
extern crate dependency;
3-
use dep_2_reexport::do_something;
4-
use dependency::Type;
3+
use dep_2_reexport::Type;
4+
use dependency::{do_something, Trait};
55

66
fn main() {
77
do_something(Type);
8+
Type.foo();
89
}

tests/run-make/crate-loading/rmake.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,15 @@ use run_make_support::{rust_lib_name, rustc};
77
fn main() {
88
rustc().input("multiple-dep-versions-1.rs").run();
99
rustc().input("multiple-dep-versions-2.rs").extra_filename("2").metadata("2").run();
10+
rustc()
11+
.input("multiple-dep-versions-3.rs")
12+
.extern_("dependency", rust_lib_name("dependency2"))
13+
.run();
1014

1115
rustc()
1216
.input("multiple-dep-versions.rs")
1317
.extern_("dependency", rust_lib_name("dependency"))
14-
.extern_("dep_2_reexport", rust_lib_name("dependency2"))
18+
.extern_("dep_2_reexport", rust_lib_name("foo"))
1519
.run_fail()
1620
.assert_stderr_contains(
1721
"you have multiple different versions of crate `dependency` in your dependency graph",
@@ -22,5 +26,11 @@ fn main() {
2226
)
2327
.assert_stderr_contains("this type doesn't implement the required trait")
2428
.assert_stderr_contains("this type implements the required trait")
25-
.assert_stderr_contains("this is the required trait");
29+
.assert_stderr_contains("this is the required trait")
30+
.assert_stderr_contains(
31+
"`dependency` imported here doesn't correspond to the right crate version",
32+
)
33+
.assert_stderr_contains("this is the trait that was imported")
34+
.assert_stderr_contains("this is the trait that is needed")
35+
.assert_stderr_contains("the method is available for `dep_2_reexport::Type` here");
2636
}

0 commit comments

Comments
 (0)