Skip to content

Commit a4883ba

Browse files
committed
Account for multiple trait bounds with missing associated types
1 parent e21be12 commit a4883ba

File tree

6 files changed

+341
-109
lines changed

6 files changed

+341
-109
lines changed

src/librustc_typeck/astconv.rs

Lines changed: 165 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ use syntax::errors::pluralize;
2727
use syntax::feature_gate::feature_err;
2828
use syntax::util::lev_distance::find_best_match_for_name;
2929
use syntax::symbol::sym;
30-
use syntax_pos::{DUMMY_SP, Span, MultiSpan};
30+
use syntax_pos::{DUMMY_SP, MultiSpan, Span};
3131
use crate::util::common::ErrorReported;
3232
use crate::util::nodemap::FxHashMap;
3333

@@ -1393,20 +1393,23 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o {
13931393
}
13941394

13951395
// Use a `BTreeSet` to keep output in a more consistent order.
1396-
let mut associated_types = BTreeSet::default();
1396+
let mut associated_types: FxHashMap<Span, BTreeSet<DefId>> = FxHashMap::default();
13971397

1398-
let regular_traits_refs = bounds.trait_bounds
1398+
let regular_traits_refs_spans = bounds.trait_bounds
13991399
.into_iter()
1400-
.filter(|(trait_ref, _)| !tcx.trait_is_auto(trait_ref.def_id()))
1401-
.map(|(trait_ref, _)| trait_ref);
1402-
for trait_ref in traits::elaborate_trait_refs(tcx, regular_traits_refs) {
1400+
.filter(|(trait_ref, _)| !tcx.trait_is_auto(trait_ref.def_id()));
1401+
1402+
for (base_trait_ref, span) in regular_traits_refs_spans {
1403+
debug!("conv_object_ty_poly_trait_ref regular_trait_ref `{:?}`", base_trait_ref);
1404+
let mut new_bounds = vec![];
1405+
for trait_ref in traits::elaborate_trait_ref(tcx, base_trait_ref) {
14031406
debug!("conv_object_ty_poly_trait_ref: observing object predicate `{:?}`", trait_ref);
14041407
match trait_ref {
14051408
ty::Predicate::Trait(pred) => {
1406-
associated_types
1409+
associated_types.entry(span).or_default()
14071410
.extend(tcx.associated_items(pred.def_id())
1408-
.filter(|item| item.kind == ty::AssocKind::Type)
1409-
.map(|item| item.def_id));
1411+
.filter(|item| item.kind == ty::AssocKind::Type)
1412+
.map(|item| item.def_id));
14101413
}
14111414
ty::Predicate::Projection(pred) => {
14121415
// A `Self` within the original bound will be substituted with a
@@ -1432,19 +1435,22 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o {
14321435
// which is uglier but works. See the discussion in #56288 for alternatives.
14331436
if !references_self {
14341437
// Include projections defined on supertraits.
1435-
bounds.projection_bounds.push((pred, DUMMY_SP))
1438+
new_bounds.push((pred, span));
14361439
}
14371440
}
14381441
_ => ()
14391442
}
1443+
}
1444+
bounds.projection_bounds.extend(new_bounds);
14401445
}
14411446

14421447
for (projection_bound, _) in &bounds.projection_bounds {
1443-
associated_types.remove(&projection_bound.projection_def_id());
1448+
for (_, def_ids) in &mut associated_types {
1449+
def_ids.remove(&projection_bound.projection_def_id());
1450+
}
14441451
}
14451452

14461453
self.complain_about_missing_associated_types(
1447-
span,
14481454
associated_types,
14491455
potential_assoc_types,
14501456
trait_bounds,
@@ -1530,125 +1536,188 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o {
15301536

15311537
fn complain_about_missing_associated_types(
15321538
&self,
1533-
span: Span,
1534-
associated_types: BTreeSet<DefId>,
1539+
mut associated_types: FxHashMap<Span, BTreeSet<DefId>>,
15351540
potential_assoc_types: Vec<Span>,
15361541
trait_bounds: &[hir::PolyTraitRef],
15371542
) {
1538-
if associated_types.is_empty() {
1543+
if !associated_types.values().any(|v| v.len() > 0) {
15391544
return;
15401545
}
1541-
// Account for things like `dyn Foo + 'a` by pointing at the `TraitRef.path`
1542-
// `Span` instead of the `PolyTraitRef` `Span`. That way the suggestion will
1543-
// be valid, otherwise we would suggest `dyn Foo + 'a<A = Type>`. See tests
1544-
// `issue-22434.rs` and `issue-22560.rs` for examples.
1545-
let sugg_span = match (&potential_assoc_types[..], &trait_bounds) {
1546+
let tcx = self.tcx();
1547+
let mut names = vec![];
1548+
1549+
// Account for things like `dyn Foo + 'a`, like in tests `issue-22434.rs` and
1550+
// `issue-22560.rs`.
1551+
let mut trait_bound_spans: Vec<Span> = vec![];
1552+
for (span, item_def_ids) in &associated_types {
1553+
if !item_def_ids.is_empty() {
1554+
trait_bound_spans.push(*span);
1555+
}
1556+
for item_def_id in item_def_ids {
1557+
let assoc_item = tcx.associated_item(*item_def_id);
1558+
let trait_def_id = assoc_item.container.id();
1559+
names.push(format!(
1560+
"`{}` (from trait `{}`)",
1561+
assoc_item.ident,
1562+
tcx.def_path_str(trait_def_id),
1563+
));
1564+
}
1565+
}
1566+
1567+
match (&potential_assoc_types[..], &trait_bounds) {
15461568
([], [bound]) => match &bound.trait_ref.path.segments[..] {
15471569
// FIXME: `trait_ref.path.span` can point to a full path with multiple
15481570
// segments, even though `trait_ref.path.segments` is of length `1`. Work
15491571
// around that bug here, even though it should be fixed elsewhere.
15501572
// This would otherwise cause an invalid suggestion. For an example, look at
1551-
// `src/test/ui/issues/issue-28344.rs`.
1552-
[segment] if segment.args.is_none() => segment.ident.span,
1553-
_ => bound.trait_ref.path.span,
1573+
// `src/test/ui/issues/issue-28344.rs` where instead of the following:
1574+
//
1575+
// error[E0191]: the value of the associated type `Output`
1576+
// (from trait `std::ops::BitXor`) must be specified
1577+
// --> $DIR/issue-28344.rs:4:17
1578+
// |
1579+
// LL | let x: u8 = BitXor::bitor(0 as u8, 0 as u8);
1580+
// | ^^^^^^ help: specify the associated type:
1581+
// | `BitXor<Output = Type>`
1582+
//
1583+
// we would output:
1584+
//
1585+
// error[E0191]: the value of the associated type `Output`
1586+
// (from trait `std::ops::BitXor`) must be specified
1587+
// --> $DIR/issue-28344.rs:4:17
1588+
// |
1589+
// LL | let x: u8 = BitXor::bitor(0 as u8, 0 as u8);
1590+
// | ^^^^^^^^^^^^^ help: specify the associated type:
1591+
// | `BitXor::bitor<Output = Type>`
1592+
[segment] if segment.args.is_none() => {
1593+
trait_bound_spans = vec![segment.ident.span];
1594+
associated_types = associated_types.into_iter()
1595+
.map(|(_, defs)| (segment.ident.span, defs))
1596+
.collect();
1597+
}
1598+
_ => {}
15541599
},
1555-
_ => span,
1556-
};
1557-
let tcx = self.tcx();
1558-
let names = associated_types.iter().map(|item_def_id| {
1559-
let assoc_item = tcx.associated_item(*item_def_id);
1560-
let trait_def_id = assoc_item.container.id();
1561-
format!("`{}` (from trait `{}`)", assoc_item.ident, tcx.def_path_str(trait_def_id))
1562-
}).collect::<Vec<_>>().join(", ");
1600+
_ => {}
1601+
}
15631602
let mut err = struct_span_err!(
15641603
tcx.sess,
1565-
sugg_span,
1604+
trait_bound_spans,
15661605
E0191,
15671606
"the value of the associated type{} {} must be specified",
1568-
pluralize!(associated_types.len()),
1569-
names,
1607+
pluralize!(names.len()),
1608+
names.join(", "),
15701609
);
1571-
let mut suggestions = Vec::new();
1572-
let mut applicability = Applicability::MaybeIncorrect;
1573-
for (i, item_def_id) in associated_types.iter().enumerate() {
1574-
let assoc_item = tcx.associated_item(*item_def_id);
1575-
if let Some(sp) = tcx.hir().span_if_local(*item_def_id) {
1576-
err.span_label(sp, format!("`{}` defined here", assoc_item.ident));
1577-
}
1578-
if potential_assoc_types.len() == associated_types.len() {
1610+
let mut suggestions = vec![];
1611+
let mut types_count = 0;
1612+
let mut where_constraints = vec![];
1613+
for (span, def_ids) in &associated_types {
1614+
let assoc_items: Vec<_> = def_ids.iter()
1615+
.map(|def_id| tcx.associated_item(*def_id))
1616+
.collect();
1617+
let mut names: FxHashMap<_, usize> = FxHashMap::default();
1618+
for item in &assoc_items {
1619+
types_count += 1;
1620+
*names.entry(item.ident.name).or_insert(0) += 1;
1621+
}
1622+
let mut dupes = false;
1623+
for item in &assoc_items {
1624+
let prefix = if names[&item.ident.name] > 1 {
1625+
let trait_def_id = item.container.id();
1626+
dupes = true;
1627+
format!("{}::", tcx.def_path_str(trait_def_id))
1628+
} else {
1629+
String::new()
1630+
};
1631+
if let Some(sp) = tcx.hir().span_if_local(item.def_id) {
1632+
err.span_label(sp, format!("`{}{}` defined here", prefix, item.ident));
1633+
}
1634+
}
1635+
if potential_assoc_types.len() == assoc_items.len() {
15791636
// Only suggest when the amount of missing associated types equals the number of
15801637
// extra type arguments present, as that gives us a relatively high confidence
15811638
// that the user forgot to give the associtated type's name. The canonical
15821639
// example would be trying to use `Iterator<isize>` instead of
15831640
// `Iterator<Item = isize>`.
1584-
if let Ok(snippet) = tcx.sess.source_map().span_to_snippet(
1585-
potential_assoc_types[i],
1586-
) {
1587-
suggestions.push((
1588-
potential_assoc_types[i],
1589-
format!("{} = {}", assoc_item.ident, snippet),
1590-
));
1641+
for (potential, item) in potential_assoc_types.iter().zip(assoc_items.iter()) {
1642+
if let Ok(snippet) = tcx.sess.source_map().span_to_snippet(
1643+
*potential,
1644+
) {
1645+
suggestions.push((
1646+
*potential,
1647+
format!("{} = {}", item.ident, snippet),
1648+
));
1649+
}
15911650
}
1592-
}
1593-
}
1594-
let mut suggestions_len = suggestions.len();
1595-
if let Ok(snippet) = tcx.sess.source_map().span_to_snippet(sugg_span) {
1596-
let assoc_types: Vec<String> = associated_types.iter()
1597-
.map(|item_def_id| {
1598-
let assoc_item = tcx.associated_item(*item_def_id);
1599-
format!("{} = Type", assoc_item.ident)
1600-
})
1601-
.collect();
1602-
let dedup = assoc_types.clone().drain(..).collect::<FxHashSet<_>>();
1603-
1604-
if dedup.len() != assoc_types.len() && trait_bounds.len() == 1 {
1605-
// If there are duplicates associated type names and a single trait bound do not
1606-
// use structured suggestion, it means that there are multiple super-traits with
1607-
// the same associated type name.
1608-
err.help("consider introducing a new type parameter, adding `where` constraints \
1609-
using the fully-qualified path to the associated type");
1610-
} else if dedup.len() == assoc_types.len() &&
1611-
potential_assoc_types.is_empty() &&
1612-
trait_bounds.len() == 1 &&
1613-
// Do not attempt to suggest when we don't know which path segment needs the
1614-
// type parameter set.
1615-
trait_bounds[0].trait_ref.path.segments.len() == 1
1616-
{
1617-
applicability = Applicability::HasPlaceholders;
1618-
let sugg = assoc_types.join(", ");
1619-
if snippet.ends_with('>') {
1651+
} else if let (Ok(snippet), false) = (
1652+
tcx.sess.source_map().span_to_snippet(*span),
1653+
dupes,
1654+
) {
1655+
let types: Vec<_> = assoc_items.iter()
1656+
.map(|item| format!("{} = Type", item.ident))
1657+
.collect();
1658+
let code = if snippet.ends_with(">") {
16201659
// The user wrote `Trait<'a>` or similar and we don't have a type we can
16211660
// suggest, but at least we can clue them to the correct syntax
16221661
// `Trait<'a, Item = Type>` while accounting for the `<'a>` in the
16231662
// suggestion.
1624-
suggestions.push((sugg_span, format!(
1625-
"{}, {}>",
1626-
&snippet[..snippet.len()-1],
1627-
sugg,
1628-
)));
1663+
format!("{}, {}>", &snippet[..snippet.len() - 1], types.join(", "))
16291664
} else {
16301665
// The user wrote `Iterator`, so we don't have a type we can suggest, but at
16311666
// least we can clue them to the correct syntax `Iterator<Item = Type>`.
1632-
suggestions.push((sugg_span, format!("{}<{}>", snippet, sugg)));
1633-
}
1634-
suggestions_len = assoc_types.len();
1667+
format!("{}<{}>", snippet, types.join(", "))
1668+
};
1669+
suggestions.push((*span, code));
1670+
} else if dupes {
1671+
where_constraints.push(*span);
16351672
}
16361673
}
1674+
let where_msg = "consider introducing a new type parameter, adding `where` constraints \
1675+
using the fully-qualified path to the associated types";
1676+
if !where_constraints.is_empty() && suggestions.is_empty() {
1677+
// If there are duplicates associated type names and a single trait bound do not
1678+
// use structured suggestion, it means that there are multiple super-traits with
1679+
// the same associated type name.
1680+
err.help(where_msg);
1681+
}
16371682
if suggestions.len() != 1 {
16381683
// We don't need this label if there's an inline suggestion, show otherwise.
1639-
let names = associated_types.iter()
1640-
.map(|t| format!("`{}`", tcx.associated_item(*t).ident))
1641-
.collect::<Vec<_>>()
1642-
.join(", ");
1643-
err.span_label(sugg_span, format!(
1644-
"associated type{} {} must be specified",
1645-
pluralize!(associated_types.len()),
1646-
names,
1647-
));
1684+
for (span, def_ids) in &associated_types {
1685+
let assoc_items: Vec<_> = def_ids.iter()
1686+
.map(|def_id| tcx.associated_item(*def_id))
1687+
.collect();
1688+
let mut names: FxHashMap<_, usize> = FxHashMap::default();
1689+
for item in &assoc_items {
1690+
types_count += 1;
1691+
*names.entry(item.ident.name).or_insert(0) += 1;
1692+
}
1693+
let mut label = vec![];
1694+
for item in &assoc_items {
1695+
let postfix = if names[&item.ident.name] > 1 {
1696+
let trait_def_id = item.container.id();
1697+
format!(" (from trait `{}`)", tcx.def_path_str(trait_def_id))
1698+
} else {
1699+
String::new()
1700+
};
1701+
label.push(format!("`{}`{}", item.ident, postfix));
1702+
}
1703+
if !label.is_empty() {
1704+
err.span_label(*span, format!(
1705+
"associated type{} {} must be specified",
1706+
pluralize!(label.len()),
1707+
label.join(", "),
1708+
));
1709+
}
1710+
}
16481711
}
16491712
if !suggestions.is_empty() {
1650-
let msg = format!("specify the associated type{}", pluralize!(suggestions_len));
1651-
err.multipart_suggestion(&msg, suggestions, applicability);
1713+
err.multipart_suggestion(
1714+
&format!("specify the associated type{}", pluralize!(types_count)),
1715+
suggestions,
1716+
Applicability::HasPlaceholders,
1717+
);
1718+
if !where_constraints.is_empty() {
1719+
err.span_help(where_constraints, where_msg);
1720+
}
16521721
}
16531722
err.emit();
16541723
}

src/test/ui/associated-type/associated-type-projection-from-multiple-supertraits.stderr

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -49,15 +49,15 @@ error[E0191]: the value of the associated types `Color` (from trait `Vehicle`),
4949
--> $DIR/associated-type-projection-from-multiple-supertraits.rs:23:30
5050
|
5151
LL | type Color;
52-
| ----------- `Color` defined here
52+
| ----------- `Vehicle::Color` defined here
5353
...
5454
LL | type Color;
55-
| ----------- `Color` defined here
55+
| ----------- `Box::Color` defined here
5656
...
5757
LL | fn dent_object<COLOR>(c: dyn BoxCar<Color=COLOR>) {
58-
| ^^^^^^^^^^^^^^^^^^^ associated types `Color`, `Color` must be specified
58+
| ^^^^^^^^^^^^^^^^^^^ associated types `Color` (from trait `Vehicle`), `Color` (from trait `Box`) must be specified
5959
|
60-
= help: consider introducing a new type parameter, adding `where` constraints using the fully-qualified path to the associated type
60+
= help: consider introducing a new type parameter, adding `where` constraints using the fully-qualified path to the associated types
6161

6262
error[E0221]: ambiguous associated type `Color` in bounds of `C`
6363
--> $DIR/associated-type-projection-from-multiple-supertraits.rs:28:29
@@ -84,15 +84,15 @@ error[E0191]: the value of the associated types `Color` (from trait `Vehicle`),
8484
--> $DIR/associated-type-projection-from-multiple-supertraits.rs:32:32
8585
|
8686
LL | type Color;
87-
| ----------- `Color` defined here
87+
| ----------- `Vehicle::Color` defined here
8888
...
8989
LL | type Color;
90-
| ----------- `Color` defined here
90+
| ----------- `Box::Color` defined here
9191
...
9292
LL | fn dent_object_2<COLOR>(c: dyn BoxCar) where <dyn BoxCar as Vehicle>::Color = COLOR {
93-
| ^^^^^^ associated types `Color`, `Color` must be specified
93+
| ^^^^^^ associated types `Color` (from trait `Vehicle`), `Color` (from trait `Box`) must be specified
9494
|
95-
= help: consider introducing a new type parameter, adding `where` constraints using the fully-qualified path to the associated type
95+
= help: consider introducing a new type parameter, adding `where` constraints using the fully-qualified path to the associated types
9696

9797
error: aborting due to 6 previous errors
9898

0 commit comments

Comments
 (0)