Skip to content

Commit

Permalink
Fix exponential blowup on nested types
Browse files Browse the repository at this point in the history
  • Loading branch information
ishitatsuyuki committed Feb 22, 2018
1 parent 93e6b0d commit 619ad71
Show file tree
Hide file tree
Showing 2 changed files with 114 additions and 101 deletions.
6 changes: 3 additions & 3 deletions src/librustc/traits/mod.rs
Expand Up @@ -304,7 +304,7 @@ pub type SelectionResult<'tcx, T> = Result<Option<T>, SelectionError<'tcx>>;
/// ### The type parameter `N`
///
/// See explanation on `VtableImplData`.
#[derive(Clone, RustcEncodable, RustcDecodable)]
#[derive(Clone, PartialEq, Eq, RustcEncodable, RustcDecodable)]
pub enum Vtable<'tcx, N> {
/// Vtable identifying a particular impl.
VtableImpl(VtableImplData<'tcx, N>),
Expand Down Expand Up @@ -374,13 +374,13 @@ pub struct VtableClosureData<'tcx, N> {
pub nested: Vec<N>
}

#[derive(Clone, RustcEncodable, RustcDecodable)]
#[derive(Clone, PartialEq, Eq, RustcEncodable, RustcDecodable)]
pub struct VtableAutoImplData<N> {
pub trait_def_id: DefId,
pub nested: Vec<N>
}

#[derive(Clone, RustcEncodable, RustcDecodable)]
#[derive(Clone, PartialEq, Eq, RustcEncodable, RustcDecodable)]
pub struct VtableBuiltinData<N> {
pub nested: Vec<N>
}
Expand Down
209 changes: 111 additions & 98 deletions src/librustc/traits/project.rs
Expand Up @@ -16,6 +16,7 @@ use super::translate_substs;
use super::Obligation;
use super::ObligationCause;
use super::PredicateObligation;
use super::Selection;
use super::SelectionContext;
use super::SelectionError;
use super::VtableClosureData;
Expand Down Expand Up @@ -110,12 +111,59 @@ enum ProjectionTyCandidate<'tcx> {
TraitDef(ty::PolyProjectionPredicate<'tcx>),

// from a "impl" (or a "pseudo-impl" returned by select)
Select,
Select(Selection<'tcx>),
}

struct ProjectionTyCandidateSet<'tcx> {
vec: Vec<ProjectionTyCandidate<'tcx>>,
ambiguous: bool
enum ProjectionTyCandidateSet<'tcx> {
None,
Single(ProjectionTyCandidate<'tcx>),
Ambiguous,
Error(SelectionError<'tcx>),
}

impl<'tcx> ProjectionTyCandidateSet<'tcx> {
fn mark_ambiguous(&mut self) {
*self = ProjectionTyCandidateSet::Ambiguous;
}

fn mark_error(&mut self, err: SelectionError<'tcx>) {
*self = ProjectionTyCandidateSet::Error(err);
}

// Returns true if the push was successful, or false if the candidate
// was discarded -- this could be because of ambiguity, or because
// a higher-priority candidate is already there.
fn push_candidate(&mut self, candidate: ProjectionTyCandidate<'tcx>) -> bool {
use self::ProjectionTyCandidateSet::*;
use self::ProjectionTyCandidate::*;
match self {
None => {
*self = Single(candidate);
true
}
Single(current) => {
// No duplicates are expected.
assert_ne!(current, &candidate);
// Prefer where-clauses. As in select, if there are multiple
// candidates, we prefer where-clause candidates over impls. This
// may seem a bit surprising, since impls are the source of
// "truth" in some sense, but in fact some of the impls that SEEM
// applicable are not, because of nested obligations. Where
// clauses are the safer choice. See the comment on
// `select::SelectionCandidate` and #21974 for more details.
match (current, candidate) {
(ParamEnv(..), ParamEnv(..)) => { *self = Ambiguous; }
(ParamEnv(..), _) => {}
(_, ParamEnv(..)) => { unreachable!(); }
(_, _) => { *self = Ambiguous; }
}
false
}
Ambiguous | Error(..) => {
false
}
}
}
}

/// Evaluates constraints of the form:
Expand Down Expand Up @@ -803,11 +851,11 @@ fn project_type<'cx, 'gcx, 'tcx>(
return Ok(ProjectedTy::Progress(Progress::error(selcx.tcx())));
}

let mut candidates = ProjectionTyCandidateSet {
vec: Vec::new(),
ambiguous: false,
};
let mut candidates = ProjectionTyCandidateSet::None;

// Make sure that the following procedures are kept in order. ParamEnv
// needs to be first because it has highest priority, and Select checks
// the return value of push_candidate which assumes it's ran at last.
assemble_candidates_from_param_env(selcx,
obligation,
&obligation_trait_ref,
Expand All @@ -818,57 +866,27 @@ fn project_type<'cx, 'gcx, 'tcx>(
&obligation_trait_ref,
&mut candidates);

if let Err(e) = assemble_candidates_from_impls(selcx,
obligation,
&obligation_trait_ref,
&mut candidates) {
return Err(ProjectionTyError::TraitSelectionError(e));
}

debug!("{} candidates, ambiguous={}",
candidates.vec.len(),
candidates.ambiguous);

// Inherent ambiguity that prevents us from even enumerating the
// candidates.
if candidates.ambiguous {
return Err(ProjectionTyError::TooManyCandidates);
}

// Prefer where-clauses. As in select, if there are multiple
// candidates, we prefer where-clause candidates over impls. This
// may seem a bit surprising, since impls are the source of
// "truth" in some sense, but in fact some of the impls that SEEM
// applicable are not, because of nested obligations. Where
// clauses are the safer choice. See the comment on
// `select::SelectionCandidate` and #21974 for more details.
if candidates.vec.len() > 1 {
debug!("retaining param-env candidates only from {:?}", candidates.vec);
candidates.vec.retain(|c| match *c {
ProjectionTyCandidate::ParamEnv(..) => true,
ProjectionTyCandidate::TraitDef(..) |
ProjectionTyCandidate::Select => false,
});
debug!("resulting candidate set: {:?}", candidates.vec);
if candidates.vec.len() != 1 {
return Err(ProjectionTyError::TooManyCandidates);
}
}

assert!(candidates.vec.len() <= 1);
assemble_candidates_from_impls(selcx,
obligation,
&obligation_trait_ref,
&mut candidates);

match candidates {
ProjectionTyCandidateSet::Single(candidate) => Ok(ProjectedTy::Progress(
confirm_candidate(selcx,
obligation,
&obligation_trait_ref,
candidate))),
ProjectionTyCandidateSet::None => Ok(ProjectedTy::NoProgress(
selcx.tcx().mk_projection(
obligation.predicate.item_def_id,
obligation.predicate.substs))),
// Error occurred while trying to processing impls.
ProjectionTyCandidateSet::Error(e) => Err(ProjectionTyError::TraitSelectionError(e)),
// Inherent ambiguity that prevents us from even enumerating the
// candidates.
ProjectionTyCandidateSet::Ambiguous => Err(ProjectionTyError::TooManyCandidates),

match candidates.vec.pop() {
Some(candidate) => {
Ok(ProjectedTy::Progress(
confirm_candidate(selcx,
obligation,
&obligation_trait_ref,
candidate)))
}
None => Ok(ProjectedTy::NoProgress(
selcx.tcx().mk_projection(
obligation.predicate.item_def_id,
obligation.predicate.substs)))
}
}

Expand Down Expand Up @@ -918,7 +936,7 @@ fn assemble_candidates_from_trait_def<'cx, 'gcx, 'tcx>(
ty::TyInfer(ty::TyVar(_)) => {
// If the self-type is an inference variable, then it MAY wind up
// being a projected type, so induce an ambiguity.
candidate_set.ambiguous = true;
candidate_set.mark_ambiguous();
return;
}
_ => { return; }
Expand Down Expand Up @@ -952,7 +970,7 @@ fn assemble_candidates_from_predicates<'cx, 'gcx, 'tcx, I>(
debug!("assemble_candidates_from_predicates: predicate={:?}",
predicate);
match predicate {
ty::Predicate::Projection(ref data) => {
ty::Predicate::Projection(data) => {
let same_def_id =
data.0.projection_ty.item_def_id == obligation.predicate.item_def_id;

Expand All @@ -975,10 +993,10 @@ fn assemble_candidates_from_predicates<'cx, 'gcx, 'tcx, I>(
data, is_match, same_def_id);

if is_match {
candidate_set.vec.push(ctor(data.clone()));
candidate_set.push_candidate(ctor(data));
}
}
_ => { }
_ => {}
}
}
}
Expand All @@ -988,37 +1006,36 @@ fn assemble_candidates_from_impls<'cx, 'gcx, 'tcx>(
obligation: &ProjectionTyObligation<'tcx>,
obligation_trait_ref: &ty::TraitRef<'tcx>,
candidate_set: &mut ProjectionTyCandidateSet<'tcx>)
-> Result<(), SelectionError<'tcx>>
{
// If we are resolving `<T as TraitRef<...>>::Item == Type`,
// start out by selecting the predicate `T as TraitRef<...>`:
let poly_trait_ref = obligation_trait_ref.to_poly_trait_ref();
let trait_obligation = obligation.with(poly_trait_ref.to_poly_trait_predicate());
selcx.infcx().probe(|_| {
let _ = selcx.infcx().commit_if_ok(|_| {
let vtable = match selcx.select(&trait_obligation) {
Ok(Some(vtable)) => vtable,
Ok(None) => {
candidate_set.ambiguous = true;
return Ok(());
candidate_set.mark_ambiguous();
return Err(());
}
Err(e) => {
debug!("assemble_candidates_from_impls: selection error {:?}",
e);
return Err(e);
candidate_set.mark_error(e);
return Err(());
}
};

match vtable {
let eligible = match &vtable {
super::VtableClosure(_) |
super::VtableGenerator(_) |
super::VtableFnPointer(_) |
super::VtableObject(_) => {
debug!("assemble_candidates_from_impls: vtable={:?}",
vtable);

candidate_set.vec.push(ProjectionTyCandidate::Select);
true
}
super::VtableImpl(ref impl_data) => {
super::VtableImpl(impl_data) => {
// We have to be careful when projecting out of an
// impl because of specialization. If we are not in
// trans (i.e., projection mode is not "any"), and the
Expand Down Expand Up @@ -1062,27 +1079,25 @@ fn assemble_candidates_from_impls<'cx, 'gcx, 'tcx>(
node_item.item.defaultness.has_value()
} else {
node_item.item.defaultness.is_default() ||
selcx.tcx().impl_is_default(node_item.node.def_id())
selcx.tcx().impl_is_default(node_item.node.def_id())
};

// Only reveal a specializable default if we're past type-checking
// and the obligations is monomorphic, otherwise passes such as
// transmute checking and polymorphic MIR optimizations could
// get a result which isn't correct for all monomorphizations.
let new_candidate = if !is_default {
Some(ProjectionTyCandidate::Select)
if !is_default {
true
} else if obligation.param_env.reveal == Reveal::All {
assert!(!poly_trait_ref.needs_infer());
if !poly_trait_ref.needs_subst() {
Some(ProjectionTyCandidate::Select)
true
} else {
None
false
}
} else {
None
};

candidate_set.vec.extend(new_candidate);
false
}
}
super::VtableParam(..) => {
// This case tell us nothing about the value of an
Expand Down Expand Up @@ -1110,6 +1125,7 @@ fn assemble_candidates_from_impls<'cx, 'gcx, 'tcx>(
// in the compiler: a trait predicate (`T : SomeTrait`) and a
// projection. And the projection where clause is handled
// in `assemble_candidates_from_param_env`.
false
}
super::VtableAutoImpl(..) |
super::VtableBuiltin(..) => {
Expand All @@ -1119,10 +1135,18 @@ fn assemble_candidates_from_impls<'cx, 'gcx, 'tcx>(
"Cannot project an associated type from `{:?}`",
vtable);
}
}
};

Ok(())
})
if eligible {
if candidate_set.push_candidate(ProjectionTyCandidate::Select(vtable)) {
Ok(())
} else {
Err(())
}
} else {
Err(())
}
});
}

fn confirm_candidate<'cx, 'gcx, 'tcx>(
Expand All @@ -1142,30 +1166,19 @@ fn confirm_candidate<'cx, 'gcx, 'tcx>(
confirm_param_env_candidate(selcx, obligation, poly_projection)
}

ProjectionTyCandidate::Select => {
confirm_select_candidate(selcx, obligation, obligation_trait_ref)
ProjectionTyCandidate::Select(vtable) => {
confirm_select_candidate(selcx, obligation, obligation_trait_ref, vtable)
}
}
}

fn confirm_select_candidate<'cx, 'gcx, 'tcx>(
selcx: &mut SelectionContext<'cx, 'gcx, 'tcx>,
obligation: &ProjectionTyObligation<'tcx>,
obligation_trait_ref: &ty::TraitRef<'tcx>)
obligation_trait_ref: &ty::TraitRef<'tcx>,
vtable: Selection<'tcx>)
-> Progress<'tcx>
{
let poly_trait_ref = obligation_trait_ref.to_poly_trait_ref();
let trait_obligation = obligation.with(poly_trait_ref.to_poly_trait_predicate());
let vtable = match selcx.select(&trait_obligation) {
Ok(Some(vtable)) => vtable,
_ => {
span_bug!(
obligation.cause.span,
"Failed to select `{:?}`",
trait_obligation);
}
};

match vtable {
super::VtableImpl(data) =>
confirm_impl_candidate(selcx, obligation, data),
Expand Down

0 comments on commit 619ad71

Please sign in to comment.