Skip to content

Commit 38e631e

Browse files
authored
Rollup merge of rust-lang#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 rust-lang#22750.
2 parents 60d1465 + 034b73b commit 38e631e

File tree

7 files changed

+209
-43
lines changed

7 files changed

+209
-43
lines changed

compiler/rustc_errors/src/diagnostic.rs

+21
Original file line numberDiff line numberDiff line change
@@ -741,6 +741,16 @@ impl<'a, G: EmissionGuarantee> Diag<'a, G> {
741741
self
742742
}
743743

744+
#[rustc_lint_diagnostics]
745+
pub fn highlighted_span_note(
746+
&mut self,
747+
span: impl Into<MultiSpan>,
748+
msg: Vec<StringPart>,
749+
) -> &mut Self {
750+
self.sub_with_highlights(Level::Note, msg, span.into());
751+
self
752+
}
753+
744754
/// This is like [`Diag::note()`], but it's only printed once.
745755
#[rustc_lint_diagnostics]
746756
pub fn note_once(&mut self, msg: impl Into<SubdiagMessage>) -> &mut Self {
@@ -815,6 +825,17 @@ impl<'a, G: EmissionGuarantee> Diag<'a, G> {
815825
self
816826
}
817827

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

compiler/rustc_errors/src/emitter.rs

+13-2
Original file line numberDiff line numberDiff line change
@@ -1347,7 +1347,7 @@ impl HumanEmitter {
13471347
label_width += 2;
13481348
}
13491349
let mut line = 0;
1350-
for (text, _) in msgs.iter() {
1350+
for (text, style) in msgs.iter() {
13511351
let text = self.translate_message(text, args).map_err(Report::new).unwrap();
13521352
// Account for newlines to align output to its label.
13531353
for text in normalize_whitespace(&text).lines() {
@@ -1358,10 +1358,21 @@ impl HumanEmitter {
13581358
if line == 0 { String::new() } else { " ".repeat(label_width) },
13591359
text
13601360
),
1361-
header_style,
1361+
match style {
1362+
Style::Highlight => *style,
1363+
_ => header_style,
1364+
},
13621365
);
13631366
line += 1;
13641367
}
1368+
// We add lines above, but if the last line has no explicit newline (which would
1369+
// yield an empty line), then we revert one line up to continue with the next
1370+
// styled text chunk on the same line as the last one from the prior one. Otherwise
1371+
// every `text` would appear on their own line (because even though they didn't end
1372+
// in '\n', they advanced `line` by one).
1373+
if line > 0 {
1374+
line -= 1;
1375+
}
13651376
}
13661377
if self.short_message {
13671378
let labels = msp

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

+128-41
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,11 @@ use rustc_data_structures::fx::FxHashMap;
55
use rustc_data_structures::unord::UnordSet;
66
use rustc_errors::codes::*;
77
use rustc_errors::{
8-
pluralize, struct_span_code_err, Applicability, Diag, ErrorGuaranteed, StashKey, StringPart,
8+
pluralize, struct_span_code_err, Applicability, Diag, ErrorGuaranteed, MultiSpan, StashKey,
9+
StringPart,
910
};
1011
use rustc_hir::def::Namespace;
11-
use rustc_hir::def_id::{DefId, LocalDefId};
12+
use rustc_hir::def_id::{DefId, LocalDefId, LOCAL_CRATE};
1213
use rustc_hir::intravisit::Visitor;
1314
use rustc_hir::{self as hir, LangItem, Node};
1415
use rustc_infer::infer::{InferOk, TypeTrace};
@@ -1624,9 +1625,131 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
16241625
other: bool,
16251626
param_env: ty::ParamEnv<'tcx>,
16261627
) -> bool {
1627-
// If we have a single implementation, try to unify it with the trait ref
1628-
// that failed. This should uncover a better hint for what *is* implemented.
1628+
let alternative_candidates = |def_id: DefId| {
1629+
let mut impl_candidates: Vec<_> = self
1630+
.tcx
1631+
.all_impls(def_id)
1632+
// ignore `do_not_recommend` items
1633+
.filter(|def_id| {
1634+
!self
1635+
.tcx
1636+
.has_attrs_with_path(*def_id, &[sym::diagnostic, sym::do_not_recommend])
1637+
})
1638+
// Ignore automatically derived impls and `!Trait` impls.
1639+
.filter_map(|def_id| self.tcx.impl_trait_header(def_id))
1640+
.filter_map(|header| {
1641+
(header.polarity != ty::ImplPolarity::Negative
1642+
|| self.tcx.is_automatically_derived(def_id))
1643+
.then(|| header.trait_ref.instantiate_identity())
1644+
})
1645+
.filter(|trait_ref| {
1646+
let self_ty = trait_ref.self_ty();
1647+
// Avoid mentioning type parameters.
1648+
if let ty::Param(_) = self_ty.kind() {
1649+
false
1650+
}
1651+
// Avoid mentioning types that are private to another crate
1652+
else if let ty::Adt(def, _) = self_ty.peel_refs().kind() {
1653+
// FIXME(compiler-errors): This could be generalized, both to
1654+
// be more granular, and probably look past other `#[fundamental]`
1655+
// types, too.
1656+
self.tcx.visibility(def.did()).is_accessible_from(body_def_id, self.tcx)
1657+
} else {
1658+
true
1659+
}
1660+
})
1661+
.collect();
1662+
1663+
impl_candidates.sort_by_key(|tr| tr.to_string());
1664+
impl_candidates.dedup();
1665+
impl_candidates
1666+
};
1667+
1668+
// We'll check for the case where the reason for the mismatch is that the trait comes from
1669+
// one crate version and the type comes from another crate version, even though they both
1670+
// are from the same crate.
1671+
let trait_def_id = trait_ref.def_id();
1672+
if let ty::Adt(def, _) = trait_ref.self_ty().skip_binder().peel_refs().kind()
1673+
&& let found_type = def.did()
1674+
&& trait_def_id.krate != found_type.krate
1675+
&& self.tcx.crate_name(trait_def_id.krate) == self.tcx.crate_name(found_type.krate)
1676+
{
1677+
let name = self.tcx.crate_name(trait_def_id.krate);
1678+
let spans: Vec<_> = [trait_def_id, found_type]
1679+
.into_iter()
1680+
.filter_map(|def_id| self.tcx.extern_crate(def_id))
1681+
.map(|data| {
1682+
let dependency = if data.dependency_of == LOCAL_CRATE {
1683+
"direct dependency of the current crate".to_string()
1684+
} else {
1685+
let dep = self.tcx.crate_name(data.dependency_of);
1686+
format!("dependency of crate `{dep}`")
1687+
};
1688+
(
1689+
data.span,
1690+
format!("one version of crate `{name}` is used here, as a {dependency}"),
1691+
)
1692+
})
1693+
.collect();
1694+
let mut span: MultiSpan = spans.iter().map(|(sp, _)| *sp).collect::<Vec<Span>>().into();
1695+
for (sp, label) in spans.into_iter() {
1696+
span.push_span_label(sp, label);
1697+
}
1698+
err.highlighted_span_help(
1699+
span,
1700+
vec![
1701+
StringPart::normal("you have ".to_string()),
1702+
StringPart::highlighted("multiple different versions".to_string()),
1703+
StringPart::normal(" of crate `".to_string()),
1704+
StringPart::highlighted(format!("{name}")),
1705+
StringPart::normal("` in your dependency graph".to_string()),
1706+
],
1707+
);
1708+
let candidates = if impl_candidates.is_empty() {
1709+
alternative_candidates(trait_def_id)
1710+
} else {
1711+
impl_candidates.into_iter().map(|cand| cand.trait_ref).collect()
1712+
};
1713+
if let Some((sp_candidate, sp_found)) = candidates.iter().find_map(|trait_ref| {
1714+
if let ty::Adt(def, _) = trait_ref.self_ty().peel_refs().kind()
1715+
&& let candidate_def_id = def.did()
1716+
&& let Some(name) = self.tcx.opt_item_name(candidate_def_id)
1717+
&& let Some(found) = self.tcx.opt_item_name(found_type)
1718+
&& name == found
1719+
&& candidate_def_id.krate != found_type.krate
1720+
&& self.tcx.crate_name(candidate_def_id.krate)
1721+
== self.tcx.crate_name(found_type.krate)
1722+
{
1723+
// A candidate was found of an item with the same name, from two separate
1724+
// versions of the same crate, let's clarify.
1725+
Some((self.tcx.def_span(candidate_def_id), self.tcx.def_span(found_type)))
1726+
} else {
1727+
None
1728+
}
1729+
}) {
1730+
let mut span: MultiSpan = vec![sp_candidate, sp_found].into();
1731+
span.push_span_label(self.tcx.def_span(trait_def_id), "this is the required trait");
1732+
span.push_span_label(sp_candidate, "this type implements the required trait");
1733+
span.push_span_label(sp_found, "this type doesn't implement the required trait");
1734+
err.highlighted_span_note(
1735+
span,
1736+
vec![
1737+
StringPart::normal(
1738+
"two types coming from two different versions of the same crate are \
1739+
different types "
1740+
.to_string(),
1741+
),
1742+
StringPart::highlighted("even if they look the same".to_string()),
1743+
],
1744+
);
1745+
}
1746+
err.help("you can use `cargo tree` to explore your dependency tree");
1747+
return true;
1748+
}
1749+
16291750
if let [single] = &impl_candidates {
1751+
// If we have a single implementation, try to unify it with the trait ref
1752+
// that failed. This should uncover a better hint for what *is* implemented.
16301753
if self.probe(|_| {
16311754
let ocx = ObligationCtxt::new(self);
16321755

@@ -1798,43 +1921,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
17981921
// Mentioning implementers of `Copy`, `Debug` and friends is not useful.
17991922
return false;
18001923
}
1801-
let mut impl_candidates: Vec<_> = self
1802-
.tcx
1803-
.all_impls(def_id)
1804-
// ignore `do_not_recommend` items
1805-
.filter(|def_id| {
1806-
!self
1807-
.tcx
1808-
.has_attrs_with_path(*def_id, &[sym::diagnostic, sym::do_not_recommend])
1809-
})
1810-
// Ignore automatically derived impls and `!Trait` impls.
1811-
.filter_map(|def_id| self.tcx.impl_trait_header(def_id))
1812-
.filter_map(|header| {
1813-
(header.polarity != ty::ImplPolarity::Negative
1814-
|| self.tcx.is_automatically_derived(def_id))
1815-
.then(|| header.trait_ref.instantiate_identity())
1816-
})
1817-
.filter(|trait_ref| {
1818-
let self_ty = trait_ref.self_ty();
1819-
// Avoid mentioning type parameters.
1820-
if let ty::Param(_) = self_ty.kind() {
1821-
false
1822-
}
1823-
// Avoid mentioning types that are private to another crate
1824-
else if let ty::Adt(def, _) = self_ty.peel_refs().kind() {
1825-
// FIXME(compiler-errors): This could be generalized, both to
1826-
// be more granular, and probably look past other `#[fundamental]`
1827-
// types, too.
1828-
self.tcx.visibility(def.did()).is_accessible_from(body_def_id, self.tcx)
1829-
} else {
1830-
true
1831-
}
1832-
})
1833-
.collect();
1834-
1835-
impl_candidates.sort_by_key(|tr| tr.to_string());
1836-
impl_candidates.dedup();
1837-
return report(impl_candidates, err);
1924+
return report(alternative_candidates(def_id), err);
18381925
}
18391926

18401927
// Sort impl candidates so that ordering is consistent for UI tests.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#![crate_name = "dependency"]
2+
#![crate_type = "rlib"]
3+
pub struct Type;
4+
pub trait Trait {}
5+
impl Trait for Type {}
6+
pub fn do_something<X: Trait>(_: X) {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#![crate_name = "dependency"]
2+
#![crate_type = "rlib"]
3+
pub struct Type(pub i32);
4+
pub trait Trait {}
5+
impl Trait for Type {}
6+
pub fn do_something<X: Trait>(_: X) {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
extern crate dep_2_reexport;
2+
extern crate dependency;
3+
use dep_2_reexport::do_something;
4+
use dependency::Type;
5+
6+
fn main() {
7+
do_something(Type);
8+
}

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

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
//@ only-linux
2+
//@ ignore-wasm32
3+
//@ ignore-wasm64
4+
5+
use run_make_support::rfs::copy;
6+
use run_make_support::{assert_contains, rust_lib_name, rustc};
7+
8+
fn main() {
9+
rustc().input("multiple-dep-versions-1.rs").run();
10+
rustc().input("multiple-dep-versions-2.rs").extra_filename("2").metadata("2").run();
11+
12+
let out = rustc()
13+
.input("multiple-dep-versions.rs")
14+
.extern_("dependency", rust_lib_name("dependency"))
15+
.extern_("dep_2_reexport", rust_lib_name("dependency2"))
16+
.run_fail()
17+
.assert_stderr_contains(
18+
"you have multiple different versions of crate `dependency` in your dependency graph",
19+
)
20+
.assert_stderr_contains(
21+
"two types coming from two different versions of the same crate are different types \
22+
even if they look the same",
23+
)
24+
.assert_stderr_contains("this type doesn't implement the required trait")
25+
.assert_stderr_contains("this type implements the required trait")
26+
.assert_stderr_contains("this is the required trait");
27+
}

0 commit comments

Comments
 (0)