Fixed detection of multiple non-auto traits.

This commit is contained in:
Alexander Regueiro 2019-04-29 03:58:24 +01:00
parent 5f6c2127d7
commit aea954b74d
4 changed files with 210 additions and 154 deletions

View file

@ -60,9 +60,12 @@ pub use self::specialize::specialization_graph::FutureCompatOverlapError;
pub use self::specialize::specialization_graph::FutureCompatOverlapErrorKind;
pub use self::engine::{TraitEngine, TraitEngineExt};
pub use self::util::{elaborate_predicates, elaborate_trait_ref, elaborate_trait_refs};
pub use self::util::{supertraits, supertrait_def_ids, transitive_bounds,
Supertraits, SupertraitDefIds};
pub use self::util::{expand_trait_refs, TraitRefExpander};
pub use self::util::{
supertraits, supertrait_def_ids, transitive_bounds, Supertraits, SupertraitDefIds,
};
pub use self::util::{
expand_trait_aliases, TraitAliasExpander, TraitAliasExpansionInfoDignosticBuilder,
};
pub use self::chalk_fulfill::{
CanonicalGoal as ChalkCanonicalGoal,

View file

@ -1,3 +1,5 @@
use errors::DiagnosticBuilder;
use smallvec::SmallVec;
use syntax_pos::Span;
use crate::hir;
@ -43,7 +45,7 @@ fn anonymize_predicate<'a, 'gcx, 'tcx>(tcx: TyCtxt<'a, 'gcx, 'tcx>,
}
}
struct PredicateSet<'a, 'gcx: 'a+'tcx, 'tcx: 'a> {
struct PredicateSet<'a, 'gcx: 'a + 'tcx, 'tcx: 'a> {
tcx: TyCtxt<'a, 'gcx, 'tcx>,
set: FxHashSet<ty::Predicate<'tcx>>,
}
@ -53,6 +55,10 @@ impl<'a, 'gcx, 'tcx> PredicateSet<'a, 'gcx, 'tcx> {
PredicateSet { tcx: tcx, set: Default::default() }
}
fn contains(&mut self, pred: &ty::Predicate<'tcx>) -> bool {
self.set.contains(&anonymize_predicate(self.tcx, pred))
}
fn insert(&mut self, pred: &ty::Predicate<'tcx>) -> bool {
// We have to be careful here because we want
//
@ -66,6 +72,18 @@ impl<'a, 'gcx, 'tcx> PredicateSet<'a, 'gcx, 'tcx> {
// regions before we throw things into the underlying set.
self.set.insert(anonymize_predicate(self.tcx, pred))
}
fn remove(&mut self, pred: &ty::Predicate<'tcx>) -> bool {
self.set.remove(&anonymize_predicate(self.tcx, pred))
}
}
impl<'a, 'gcx, 'tcx, T: AsRef<ty::Predicate<'tcx>>> Extend<T> for PredicateSet<'a, 'gcx, 'tcx> {
fn extend<I: IntoIterator<Item = T>>(&mut self, iter: I) {
for pred in iter.into_iter() {
self.insert(pred.as_ref());
}
}
}
///////////////////////////////////////////////////////////////////////////
@ -230,10 +248,16 @@ impl<'cx, 'gcx, 'tcx> Iterator for Elaborator<'cx, 'gcx, 'tcx> {
}
fn next(&mut self) -> Option<ty::Predicate<'tcx>> {
self.stack.pop().map(|item| {
self.push(&item);
item
})
// Extract next item from top-most stack frame, if any.
let next_predicate = match self.stack.pop() {
Some(predicate) => predicate,
None => {
// No more stack frames. Done.
return None;
}
};
self.push(&next_predicate);
return Some(next_predicate);
}
}
@ -256,96 +280,140 @@ pub fn transitive_bounds<'cx, 'gcx, 'tcx>(tcx: TyCtxt<'cx, 'gcx, 'tcx>,
}
///////////////////////////////////////////////////////////////////////////
// `TraitRefExpander` iterator
// `TraitAliasExpander` iterator
///////////////////////////////////////////////////////////////////////////
/// "Trait reference expansion" is the process of expanding a sequence of trait
/// "Trait alias expansion" is the process of expanding a sequence of trait
/// references into another sequence by transitively following all trait
/// aliases. e.g. If you have bounds like `Foo + Send`, a trait alias
/// `trait Foo = Bar + Sync;`, and another trait alias
/// `trait Bar = Read + Write`, then the bounds would expand to
/// `Read + Write + Sync + Send`.
pub struct TraitRefExpander<'a, 'gcx: 'a + 'tcx, 'tcx: 'a> {
stack: Vec<TraitRefExpansionInfo<'tcx>>,
pub struct TraitAliasExpander<'a, 'gcx: 'a + 'tcx, 'tcx: 'a> {
stack: Vec<TraitAliasExpansionInfo<'tcx>>,
/// The set of predicates visited from the root directly to the current point in the
/// expansion tree.
visited: PredicateSet<'a, 'gcx, 'tcx>,
}
#[derive(Debug, Clone)]
pub struct TraitRefExpansionInfo<'tcx> {
pub top_level_trait_ref: ty::PolyTraitRef<'tcx>,
pub top_level_span: Span,
pub trait_ref: ty::PolyTraitRef<'tcx>,
pub span: Span,
pub struct TraitAliasExpansionInfo<'tcx> {
pub items: SmallVec<[(ty::PolyTraitRef<'tcx>, Span); 4]>,
}
pub fn expand_trait_refs<'cx, 'gcx, 'tcx>(
impl<'tcx> TraitAliasExpansionInfo<'tcx> {
fn new(trait_ref: ty::PolyTraitRef<'tcx>, span: Span) -> TraitAliasExpansionInfo<'tcx> {
TraitAliasExpansionInfo {
items: smallvec![(trait_ref, span)]
}
}
fn push(&self, trait_ref: ty::PolyTraitRef<'tcx>, span: Span) -> TraitAliasExpansionInfo<'tcx> {
let mut items = self.items.clone();
items.push((trait_ref, span));
TraitAliasExpansionInfo {
items
}
}
pub fn trait_ref(&self) -> &ty::PolyTraitRef<'tcx> {
&self.top().0
}
pub fn top(&self) -> &(ty::PolyTraitRef<'tcx>, Span) {
self.items.last().unwrap()
}
pub fn bottom(&self) -> &(ty::PolyTraitRef<'tcx>, Span) {
self.items.first().unwrap()
}
}
pub trait TraitAliasExpansionInfoDignosticBuilder {
fn label_with_exp_info<'tcx>(&mut self,
info: &TraitAliasExpansionInfo<'tcx>,
top_label: &str
) -> &mut Self;
}
impl<'a> TraitAliasExpansionInfoDignosticBuilder for DiagnosticBuilder<'a> {
fn label_with_exp_info<'tcx>(&mut self,
info: &TraitAliasExpansionInfo<'tcx>,
top_label: &str
) -> &mut Self {
self.span_label(info.top().1, top_label);
if info.items.len() > 1 {
for (_, sp) in info.items[1..(info.items.len() - 1)].iter().rev() {
self.span_label(*sp, "referenced here");
}
}
self
}
}
pub fn expand_trait_aliases<'cx, 'gcx, 'tcx>(
tcx: TyCtxt<'cx, 'gcx, 'tcx>,
trait_refs: impl IntoIterator<Item = (ty::PolyTraitRef<'tcx>, Span)>
) -> TraitRefExpander<'cx, 'gcx, 'tcx> {
let mut visited = PredicateSet::new(tcx);
let mut items: Vec<_> =
trait_refs
.into_iter()
.map(|(trait_ref, span)| TraitRefExpansionInfo {
top_level_trait_ref: trait_ref.clone(),
top_level_span: span,
trait_ref,
span,
})
.collect();
items.retain(|item| visited.insert(&item.trait_ref.to_predicate()));
TraitRefExpander { stack: items, visited }
) -> TraitAliasExpander<'cx, 'gcx, 'tcx> {
let items: Vec<_> = trait_refs
.into_iter()
.map(|(trait_ref, span)| TraitAliasExpansionInfo::new(trait_ref, span))
.collect();
TraitAliasExpander { stack: items, visited: PredicateSet::new(tcx) }
}
impl<'cx, 'gcx, 'tcx> TraitRefExpander<'cx, 'gcx, 'tcx> {
/// If `item` refers to a trait alias, adds the components of the trait alias to the stack,
/// and returns `false`.
/// If `item` refers to an ordinary trait, simply returns `true`.
fn push(&mut self, item: &TraitRefExpansionInfo<'tcx>) -> bool {
impl<'cx, 'gcx, 'tcx> TraitAliasExpander<'cx, 'gcx, 'tcx> {
/// If `item` is a trait alias and its predicate has not yet been visited, then expands `item`
/// to the definition and pushes the resulting expansion onto `self.stack`, and returns `false`.
/// Otherwise, immediately returns `true` if `item` is a regular trait and `false` if it is a
/// trait alias.
/// The return value indicates whether `item` should not be yielded to the user.
fn push(&mut self, item: &TraitAliasExpansionInfo<'tcx>) -> bool {
let tcx = self.visited.tcx;
let trait_ref = item.trait_ref();
let pred = trait_ref.to_predicate();
if !tcx.is_trait_alias(item.trait_ref.def_id()) {
return true;
debug!("expand_trait_aliases: trait_ref={:?}", trait_ref);
self.visited.remove(&pred);
let is_alias = tcx.is_trait_alias(trait_ref.def_id());
if !is_alias || self.visited.contains(&pred) {
return !is_alias;
}
// Get components of the trait alias.
let predicates = tcx.super_predicates_of(item.trait_ref.def_id());
// Get components of trait alias.
let predicates = tcx.super_predicates_of(trait_ref.def_id());
let mut items: Vec<_> = predicates.predicates
let items: Vec<_> = predicates.predicates
.iter()
.rev()
.filter_map(|(pred, span)| {
pred.subst_supertrait(tcx, &item.trait_ref)
pred.subst_supertrait(tcx, &trait_ref)
.to_opt_poly_trait_ref()
.map(|trait_ref|
TraitRefExpansionInfo {
trait_ref,
span: *span,
..*item
}
)
.map(|trait_ref| item.push(trait_ref, *span))
})
.collect();
debug!("trait_ref_expander: trait_ref={:?} items={:?}",
item.trait_ref, items);
// Only keep those items that we haven't already seen.
items.retain(|i| self.visited.insert(&i.trait_ref.to_predicate()));
debug!("expand_trait_aliases: items={:?}", items);
self.stack.extend(items);
// Record predicate into set of already-visited.
self.visited.insert(&pred);
false
}
}
impl<'cx, 'gcx, 'tcx> Iterator for TraitRefExpander<'cx, 'gcx, 'tcx> {
type Item = TraitRefExpansionInfo<'tcx>;
impl<'cx, 'gcx, 'tcx> Iterator for TraitAliasExpander<'cx, 'gcx, 'tcx> {
type Item = TraitAliasExpansionInfo<'tcx>;
fn size_hint(&self) -> (usize, Option<usize>) {
(self.stack.len(), None)
}
fn next(&mut self) -> Option<TraitRefExpansionInfo<'tcx>> {
fn next(&mut self) -> Option<TraitAliasExpansionInfo<'tcx>> {
while let Some(item) = self.stack.pop() {
if self.push(&item) {
return Some(item);
@ -386,8 +454,8 @@ impl<'cx, 'gcx, 'tcx> Iterator for SupertraitDefIds<'cx, 'gcx, 'tcx> {
self.stack.extend(
predicates.predicates
.iter()
.filter_map(|(p, _)| p.to_opt_poly_trait_ref())
.map(|t| t.def_id())
.filter_map(|(pred, _)| pred.to_opt_poly_trait_ref())
.map(|trait_ref| trait_ref.def_id())
.filter(|&super_def_id| visited.insert(super_def_id)));
Some(def_id)
}
@ -413,17 +481,12 @@ impl<'tcx, I: Iterator<Item = ty::Predicate<'tcx>>> Iterator for FilterToTraits<
type Item = ty::PolyTraitRef<'tcx>;
fn next(&mut self) -> Option<ty::PolyTraitRef<'tcx>> {
loop {
match self.base_iterator.next() {
None => {
return None;
}
Some(ty::Predicate::Trait(data)) => {
return Some(data.to_poly_trait_ref());
}
Some(_) => {}
while let Some(pred) = self.base_iterator.next() {
if let ty::Predicate::Trait(data) = pred {
return Some(data.to_poly_trait_ref());
}
}
None
}
fn size_hint(&self) -> (usize, Option<usize>) {

View file

@ -504,7 +504,6 @@ impl<'a> Visitor<'a> for AstValidator<'a> {
any_lifetime_bounds = true;
}
}
self.no_questions_in_bounds(bounds, "trait object types", false);
}
TyKind::ImplTrait(_, ref bounds) => {
if self.is_impl_trait_banned {

View file

@ -11,7 +11,7 @@ use crate::lint;
use crate::middle::resolve_lifetime as rl;
use crate::namespace::Namespace;
use rustc::lint::builtin::AMBIGUOUS_ASSOCIATED_ITEMS;
use rustc::traits;
use rustc::traits::{self, TraitAliasExpansionInfoDignosticBuilder};
use rustc::ty::{self, DefIdTree, Ty, TyCtxt, ToPredicate, TypeFoldable};
use rustc::ty::{GenericParamDef, GenericParamDefKind};
use rustc::ty::subst::{Kind, Subst, InternalSubsts, SubstsRef};
@ -965,30 +965,6 @@ impl<'o, 'gcx: 'tcx, 'tcx> dyn AstConv<'gcx, 'tcx> + 'o {
ty::ExistentialTraitRef::erase_self_ty(self.tcx(), trait_ref)
}
fn expand_trait_refs(&self,
trait_refs: impl IntoIterator<Item = (ty::PolyTraitRef<'tcx>, Span)>
) -> Vec<DefId> {
let tcx = self.tcx();
// Expand trait aliases recursively and check that only one regular (non-auto) trait
// is used.
let expanded_traits = traits::expand_trait_refs(tcx, trait_refs);
let (auto_traits, regular_traits): (Vec<_>, Vec<_>) =
expanded_traits.partition(|i| tcx.trait_is_auto(i.trait_ref.def_id()));
if regular_traits.len() > 1 {
let extra_trait = &regular_traits[1];
let mut err = struct_span_err!(tcx.sess, extra_trait.top_level_span, E0225,
"only auto traits can be used as additional traits in a trait object");
err.span_label(extra_trait.span, "non-auto additional trait");
if extra_trait.span != extra_trait.top_level_span {
err.span_label(extra_trait.top_level_span, "expanded from this trait alias");
}
err.emit();
}
auto_traits.into_iter().map(|i| i.trait_ref.def_id()).collect()
}
fn conv_object_ty_poly_trait_ref(&self,
span: Span,
trait_bounds: &[hir::PolyTraitRef],
@ -997,52 +973,69 @@ impl<'o, 'gcx: 'tcx, 'tcx> dyn AstConv<'gcx, 'tcx> + 'o {
{
let tcx = self.tcx();
if trait_bounds.is_empty() {
let mut projection_bounds = Vec::new();
let mut potential_assoc_types = Vec::new();
let dummy_self = self.tcx().types.trait_object_dummy_self;
let bound_trait_refs: Vec<_> = trait_bounds
.iter()
.rev()
.map(|trait_bound| {
let (trait_ref, cur_potential_assoc_types) = self.instantiate_poly_trait_ref(
trait_bound,
dummy_self,
&mut projection_bounds
);
potential_assoc_types.extend(cur_potential_assoc_types.into_iter().flatten());
(trait_ref, trait_bound.span)
}).collect();
// Expand trait aliases recursively and check that only one regular (non-auto) trait
// is used and no 'maybe' bounds are used.
let expanded_traits = traits::expand_trait_aliases(tcx, bound_trait_refs.clone());
let (mut auto_traits, regular_traits): (Vec<_>, Vec<_>) =
expanded_traits.partition(|i| tcx.trait_is_auto(i.trait_ref().def_id()));
if regular_traits.len() > 1 {
let extra_trait = &regular_traits[1];
let mut err = struct_span_err!(tcx.sess, extra_trait.bottom().1, E0225,
"only auto traits can be used as additional traits in a trait object");
err.label_with_exp_info(extra_trait, "additional non-auto trait");
err.span_label(regular_traits[0].top().1, "first non-auto trait");
err.emit();
}
if regular_traits.is_empty() && auto_traits.is_empty() {
span_err!(tcx.sess, span, E0224,
"at least one non-builtin trait is required for an object type");
return tcx.types.err;
}
let mut projection_bounds = Vec::new();
let dummy_self = self.tcx().types.trait_object_dummy_self;
let (principal, potential_assoc_types) = self.instantiate_poly_trait_ref(
&trait_bounds[0],
dummy_self,
&mut projection_bounds,
);
debug!("principal: {:?}", principal);
let mut bound_trait_refs = Vec::with_capacity(trait_bounds.len());
for trait_bound in trait_bounds[1..].iter().rev() {
// Sanity check for non-principal trait bounds.
let (tr, _) = self.instantiate_poly_trait_ref(
trait_bound,
dummy_self,
&mut Vec::new()
);
bound_trait_refs.push((tr, trait_bound.span));
}
bound_trait_refs.push((principal, trait_bounds[0].span));
let mut auto_traits = self.expand_trait_refs(bound_trait_refs);
// Check that there are no gross object safety violations;
// most importantly, that the supertraits don't contain `Self`,
// to avoid ICEs.
let object_safety_violations =
tcx.global_tcx().astconv_object_safety_violations(principal.def_id());
if !object_safety_violations.is_empty() {
tcx.report_object_safety_error(span, principal.def_id(), object_safety_violations)
.map(|mut err| err.emit());
return tcx.types.err;
for item in &regular_traits {
let object_safety_violations =
tcx.global_tcx().astconv_object_safety_violations(item.trait_ref().def_id());
if !object_safety_violations.is_empty() {
tcx.report_object_safety_error(
span,
item.trait_ref().def_id(),
object_safety_violations
)
.map(|mut err| err.emit());
return tcx.types.err;
}
}
// Use a `BTreeSet` to keep output in a more consistent order.
let mut associated_types = BTreeSet::default();
for tr in traits::elaborate_trait_ref(tcx, principal) {
debug!("conv_object_ty_poly_trait_ref: observing object predicate `{:?}`", tr);
match tr {
let regular_traits_refs = bound_trait_refs
.into_iter()
.filter(|(trait_ref, _)| !tcx.trait_is_auto(trait_ref.def_id()))
.map(|(trait_ref, _)| trait_ref);
for trait_ref in traits::elaborate_trait_refs(tcx, regular_traits_refs) {
debug!("conv_object_ty_poly_trait_ref: observing object predicate `{:?}`", trait_ref);
match trait_ref {
ty::Predicate::Trait(pred) => {
associated_types
.extend(tcx.associated_items(pred.def_id())
@ -1102,20 +1095,18 @@ impl<'o, 'gcx: 'tcx, 'tcx> dyn AstConv<'gcx, 'tcx> + 'o {
if associated_types.len() == 1 { "" } else { "s" },
names,
);
let mut suggest = false;
let mut potential_assoc_types_spans = vec![];
if let Some(potential_assoc_types) = potential_assoc_types {
let (suggest, potential_assoc_types_spans) =
if potential_assoc_types.len() == associated_types.len() {
// Only suggest when the amount of missing associated types is equals to the
// Only suggest when the amount of missing associated types equals the number of
// extra type arguments present, as that gives us a relatively high confidence
// that the user forgot to give the associtated type's name. The canonical
// example would be trying to use `Iterator<isize>` instead of
// `Iterator<Item=isize>`.
suggest = true;
potential_assoc_types_spans = potential_assoc_types;
}
}
let mut suggestions = vec![];
// `Iterator<Item = isize>`.
(true, potential_assoc_types)
} else {
(false, Vec::new())
};
let mut suggestions = Vec::new();
for (i, item_def_id) in associated_types.iter().enumerate() {
let assoc_item = tcx.associated_item(*item_def_id);
err.span_label(
@ -1151,9 +1142,16 @@ impl<'o, 'gcx: 'tcx, 'tcx> dyn AstConv<'gcx, 'tcx> + 'o {
err.emit();
}
// De-duplicate auto traits so that, e.g., `dyn Trait + Send + Send` is the same as
// `dyn Trait + Send`.
auto_traits.sort_by_key(|i| i.trait_ref().def_id());
auto_traits.dedup_by_key(|i| i.trait_ref().def_id());
debug!("regular_traits: {:?}", regular_traits);
debug!("auto_traits: {:?}", auto_traits);
// Erase the `dummy_self` (`trait_object_dummy_self`) used above.
let existential_principal = principal.map_bound(|trait_ref| {
self.trait_ref_to_existential(trait_ref)
let existential_trait_refs = regular_traits.iter().map(|i| {
i.trait_ref().map_bound(|trait_ref| self.trait_ref_to_existential(trait_ref))
});
let existential_projections = projection_bounds.iter().map(|(bound, _)| {
bound.map_bound(|b| {
@ -1166,21 +1164,14 @@ impl<'o, 'gcx: 'tcx, 'tcx> dyn AstConv<'gcx, 'tcx> + 'o {
})
});
// De-duplicate auto traits so that, e.g., `dyn Trait + Send + Send` is the same as
// `dyn Trait + Send`.
auto_traits.sort();
auto_traits.dedup();
debug!("auto_traits: {:?}", auto_traits);
// Calling `skip_binder` is okay because the predicates are re-bound.
let principal = if tcx.trait_is_auto(existential_principal.def_id()) {
ty::ExistentialPredicate::AutoTrait(existential_principal.def_id())
} else {
ty::ExistentialPredicate::Trait(*existential_principal.skip_binder())
};
let regular_trait_predicates = existential_trait_refs.map(
|trait_ref| ty::ExistentialPredicate::Trait(*trait_ref.skip_binder()));
let auto_trait_predicates = auto_traits.into_iter().map(
|trait_ref| ty::ExistentialPredicate::AutoTrait(trait_ref.trait_ref().def_id()));
let mut v =
iter::once(principal)
.chain(auto_traits.into_iter().map(ty::ExistentialPredicate::AutoTrait))
regular_trait_predicates
.chain(auto_trait_predicates)
.chain(existential_projections
.map(|x| ty::ExistentialPredicate::Projection(*x.skip_binder())))
.collect::<SmallVec<[_; 8]>>();