Skip to content

Commit 3a9190b

Browse files
committed
Auto merge of #124944 - estebank:multiple-crate-versions, r=fee1-dead
On trait bound mismatch, detect multiple crate versions in dep tree When encountering an E0277, if the type and the trait both come from a crate with the same name but different crate number, we explain that there are multiple crate versions in the dependency tree. If there's a type that fulfills the bound, and it has the same name as the passed in type and has the same crate name, we explain that the same type in two different versions of the same crate *are different*. ``` error[E0277]: the trait bound `Type: dependency::Trait` is not satisfied --> src/main.rs:4:18 | 4 | do_something(Type); | ------------ ^^^^ the trait `dependency::Trait` is not implemented for `Type` | | | required by a bound introduced by this call | help: you have multiple different versions of crate `dependency` in your dependency graph --> src/main.rs:1:5 | 1 | use bar::do_something; | ^^^ one version of crate `dependency` is used here, as a dependency of crate `bar` 2 | use dependency::Type; | ^^^^^^^^^^ one version of crate `dependency` is used here, as a direct dependency of the current crate note: two types coming from two different versions of the same crate are different types even if they look the same --> /home/gh-estebank/crate_versions/baz-2/src/lib.rs:1:1 | 1 | pub struct Type; | ^^^^^^^^^^^^^^^ this type doesn't implement the required trait | ::: /home/gh-estebank/crate_versions/baz/src/lib.rs:1:1 | 1 | pub struct Type; | ^^^^^^^^^^^^^^^ this type implements the required trait 2 | pub trait Trait {} | --------------- this is the required trait note: required by a bound in `bar::do_something` --> /home/gh-estebank/crate_versions/baz/src/lib.rs:4:24 | 4 | pub fn do_something<X: Trait>(_: X) {} | ^^^^^ required by this bound in `do_something` ``` Address #22750.
2 parents 982c9c1 + 782cba8 commit 3a9190b

File tree

8 files changed

+280
-35
lines changed

8 files changed

+280
-35
lines changed

compiler/rustc_errors/src/diagnostic.rs

+21
Original file line numberDiff line numberDiff line change
@@ -737,6 +737,16 @@ impl<'a, G: EmissionGuarantee> Diag<'a, G> {
737737
self
738738
}
739739

740+
#[rustc_lint_diagnostics]
741+
pub fn highlighted_span_note(
742+
&mut self,
743+
span: impl Into<MultiSpan>,
744+
msg: Vec<StringPart>,
745+
) -> &mut Self {
746+
self.sub_with_highlights(Level::Note, msg, span.into());
747+
self
748+
}
749+
740750
/// This is like [`Diag::note()`], but it's only printed once.
741751
#[rustc_lint_diagnostics]
742752
pub fn note_once(&mut self, msg: impl Into<SubdiagMessage>) -> &mut Self {
@@ -811,6 +821,17 @@ impl<'a, G: EmissionGuarantee> Diag<'a, G> {
811821
self
812822
}
813823

824+
/// Add a help message attached to this diagnostic with a customizable highlighted message.
825+
#[rustc_lint_diagnostics]
826+
pub fn highlighted_span_help(
827+
&mut self,
828+
span: impl Into<MultiSpan>,
829+
msg: Vec<StringPart>,
830+
) -> &mut Self {
831+
self.sub_with_highlights(Level::Help, msg, span.into());
832+
self
833+
}
834+
814835
/// Prints the span with some help above it.
815836
/// This is like [`Diag::help()`], but it gets its own span.
816837
#[rustc_lint_diagnostics]

compiler/rustc_errors/src/emitter.rs

+5-2
Original file line numberDiff line numberDiff line change
@@ -1328,7 +1328,7 @@ impl HumanEmitter {
13281328
buffer.append(0, ": ", header_style);
13291329
label_width += 2;
13301330
}
1331-
for (text, _) in msgs.iter() {
1331+
for (text, style) in msgs.iter() {
13321332
let text = self.translate_message(text, args).map_err(Report::new).unwrap();
13331333
// Account for newlines to align output to its label.
13341334
for (line, text) in normalize_whitespace(&text).lines().enumerate() {
@@ -1339,7 +1339,10 @@ impl HumanEmitter {
13391339
if line == 0 { String::new() } else { " ".repeat(label_width) },
13401340
text
13411341
),
1342-
header_style,
1342+
match style {
1343+
Style::Highlight => *style,
1344+
_ => header_style,
1345+
},
13431346
);
13441347
}
13451348
}

compiler/rustc_trait_selection/src/traits/error_reporting/type_err_ctxt_ext.rs

+119-33
Original file line numberDiff line numberDiff line change
@@ -1939,9 +1939,125 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
19391939
other: bool,
19401940
param_env: ty::ParamEnv<'tcx>,
19411941
) -> bool {
1942-
// If we have a single implementation, try to unify it with the trait ref
1943-
// that failed. This should uncover a better hint for what *is* implemented.
1942+
let alternative_candidates = |def_id: DefId| {
1943+
let mut impl_candidates: Vec<_> = self
1944+
.tcx
1945+
.all_impls(def_id)
1946+
// Ignore automatically derived impls and `!Trait` impls.
1947+
.filter_map(|def_id| self.tcx.impl_trait_header(def_id))
1948+
.filter_map(|header| {
1949+
(header.polarity != ty::ImplPolarity::Negative
1950+
|| self.tcx.is_automatically_derived(def_id))
1951+
.then(|| header.trait_ref.instantiate_identity())
1952+
})
1953+
.filter(|trait_ref| {
1954+
let self_ty = trait_ref.self_ty();
1955+
// Avoid mentioning type parameters.
1956+
if let ty::Param(_) = self_ty.kind() {
1957+
false
1958+
}
1959+
// Avoid mentioning types that are private to another crate
1960+
else if let ty::Adt(def, _) = self_ty.peel_refs().kind() {
1961+
// FIXME(compiler-errors): This could be generalized, both to
1962+
// be more granular, and probably look past other `#[fundamental]`
1963+
// types, too.
1964+
self.tcx.visibility(def.did()).is_accessible_from(body_def_id, self.tcx)
1965+
} else {
1966+
true
1967+
}
1968+
})
1969+
.collect();
1970+
1971+
impl_candidates.sort_by_key(|tr| tr.to_string());
1972+
impl_candidates.dedup();
1973+
impl_candidates
1974+
};
1975+
1976+
// We'll check for the case where the reason for the mismatch is that the trait comes from
1977+
// one crate version and the type comes from another crate version, even though they both
1978+
// are from the same crate.
1979+
let trait_def_id = trait_ref.def_id();
1980+
if let ty::Adt(def, _) = trait_ref.self_ty().skip_binder().peel_refs().kind()
1981+
&& let found_type = def.did()
1982+
&& trait_def_id.krate != found_type.krate
1983+
&& self.tcx.crate_name(trait_def_id.krate) == self.tcx.crate_name(found_type.krate)
1984+
{
1985+
let name = self.tcx.crate_name(trait_def_id.krate);
1986+
let spans: Vec<_> = [trait_def_id, found_type]
1987+
.into_iter()
1988+
.filter_map(|def_id| self.tcx.extern_crate(def_id))
1989+
.map(|data| {
1990+
let dependency = if data.dependency_of == LOCAL_CRATE {
1991+
"direct dependency of the current crate".to_string()
1992+
} else {
1993+
let dep = self.tcx.crate_name(data.dependency_of);
1994+
format!("dependency of crate `{dep}`")
1995+
};
1996+
(
1997+
data.span,
1998+
format!("one version of crate `{name}` is used here, as a {dependency}"),
1999+
)
2000+
})
2001+
.collect();
2002+
let mut span: MultiSpan = spans.iter().map(|(sp, _)| *sp).collect::<Vec<Span>>().into();
2003+
for (sp, label) in spans.into_iter() {
2004+
span.push_span_label(sp, label);
2005+
}
2006+
err.highlighted_span_help(
2007+
span,
2008+
vec![
2009+
StringPart::normal("you have ".to_string()),
2010+
StringPart::highlighted("multiple different versions".to_string()),
2011+
StringPart::normal(" of crate `".to_string()),
2012+
StringPart::highlighted(format!("{name}")),
2013+
StringPart::normal("` in your dependency graph".to_string()),
2014+
],
2015+
);
2016+
let candidates = if impl_candidates.is_empty() {
2017+
alternative_candidates(trait_def_id)
2018+
} else {
2019+
impl_candidates.into_iter().map(|cand| cand.trait_ref).collect()
2020+
};
2021+
if let Some((sp_candidate, sp_found)) = candidates.iter().find_map(|trait_ref| {
2022+
if let ty::Adt(def, _) = trait_ref.self_ty().peel_refs().kind()
2023+
&& let candidate_def_id = def.did()
2024+
&& let Some(name) = self.tcx.opt_item_name(candidate_def_id)
2025+
&& let Some(found) = self.tcx.opt_item_name(found_type)
2026+
&& name == found
2027+
&& candidate_def_id.krate != found_type.krate
2028+
&& self.tcx.crate_name(candidate_def_id.krate)
2029+
== self.tcx.crate_name(found_type.krate)
2030+
{
2031+
// A candidate was found of an item with the same name, from two separate
2032+
// versions of the same crate, let's clarify.
2033+
Some((self.tcx.def_span(candidate_def_id), self.tcx.def_span(found_type)))
2034+
} else {
2035+
None
2036+
}
2037+
}) {
2038+
let mut span: MultiSpan = vec![sp_candidate, sp_found].into();
2039+
span.push_span_label(self.tcx.def_span(trait_def_id), "this is the required trait");
2040+
span.push_span_label(sp_candidate, "this type implements the required trait");
2041+
span.push_span_label(sp_found, "this type doesn't implement the required trait");
2042+
err.highlighted_span_note(
2043+
span,
2044+
vec![
2045+
StringPart::normal(
2046+
"two types coming from two different versions of the same crate are \
2047+
different types "
2048+
.to_string(),
2049+
),
2050+
StringPart::highlighted("even if they look the same".to_string()),
2051+
],
2052+
);
2053+
}
2054+
err.help("you can use `cargo tree` to explore your dependency tree");
2055+
return true;
2056+
}
2057+
19442058
if let [single] = &impl_candidates {
2059+
// If we have a single implementation, try to unify it with the trait ref
2060+
// that failed. This should uncover a better hint for what *is* implemented.
19452061
if self.probe(|_| {
19462062
let ocx = ObligationCtxt::new(self);
19472063

@@ -2096,37 +2212,7 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
20962212
// Mentioning implementers of `Copy`, `Debug` and friends is not useful.
20972213
return false;
20982214
}
2099-
let mut impl_candidates: Vec<_> = self
2100-
.tcx
2101-
.all_impls(def_id)
2102-
// Ignore automatically derived impls and `!Trait` impls.
2103-
.filter_map(|def_id| self.tcx.impl_trait_header(def_id))
2104-
.filter_map(|header| {
2105-
(header.polarity != ty::ImplPolarity::Negative
2106-
|| self.tcx.is_automatically_derived(def_id))
2107-
.then(|| header.trait_ref.instantiate_identity())
2108-
})
2109-
.filter(|trait_ref| {
2110-
let self_ty = trait_ref.self_ty();
2111-
// Avoid mentioning type parameters.
2112-
if let ty::Param(_) = self_ty.kind() {
2113-
false
2114-
}
2115-
// Avoid mentioning types that are private to another crate
2116-
else if let ty::Adt(def, _) = self_ty.peel_refs().kind() {
2117-
// FIXME(compiler-errors): This could be generalized, both to
2118-
// be more granular, and probably look past other `#[fundamental]`
2119-
// types, too.
2120-
self.tcx.visibility(def.did()).is_accessible_from(body_def_id, self.tcx)
2121-
} else {
2122-
true
2123-
}
2124-
})
2125-
.collect();
2126-
2127-
impl_candidates.sort_by_key(|tr| tr.to_string());
2128-
impl_candidates.dedup();
2129-
return report(impl_candidates, err);
2215+
return report(alternative_candidates(def_id), err);
21302216
}
21312217

21322218
// Sort impl candidates so that ordering is consistent for UI tests.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
//@ edition:2021
2+
//@ aux-build:multiple-dep-versions-2.rs
3+
extern crate dependency;
4+
pub use dependency::do_something;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#![crate_name="dependency"]
2+
//@ edition:2021
3+
//@ compile-flags: -C metadata=1 -C extra-filename=-1
4+
pub struct Type;
5+
pub trait Trait {}
6+
impl Trait for Type {}
7+
pub fn do_something<X: Trait>(_: X) { }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#![crate_name="dependency"]
2+
//@ edition:2021
3+
//@ compile-flags: -C metadata=2 -C extra-filename=-2
4+
pub struct Type(pub i32);
5+
pub trait Trait {}
6+
impl Trait for Type {}
7+
pub fn do_something<X: Trait>(_: X) {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
//@ aux-build:dep-2-reexport.rs
2+
//@ aux-build:multiple-dep-versions-1.rs
3+
//@ edition:2021
4+
//@ compile-flags: --error-format=human --color=always --crate-type bin --extern dependency={{build-base}}/crate-loading/multiple-dep-versions/auxiliary/libdependency-1.so --extern dep_2_reexport={{build-base}}/crate-loading/multiple-dep-versions/auxiliary/libdep_2_reexport.so
5+
//@ ignore-windows
6+
7+
extern crate dependency;
8+
extern crate dep_2_reexport;
9+
use dependency::Type;
10+
use dep_2_reexport::do_something;
11+
12+
fn main() {
13+
do_something(Type);
14+
}
Loading

0 commit comments

Comments
 (0)