Handle multiple applicable projection candidates

This commit is contained in:
Matthew Jasper 2020-07-23 21:59:20 +01:00
parent bc08b791bc
commit cfee49593d
6 changed files with 82 additions and 37 deletions

View file

@ -157,7 +157,7 @@ rustc_queries! {
} }
/// Returns the list of bounds that can be used for /// Returns the list of bounds that can be used for
/// `SelectionCandidate::ProjectionCandidate` and /// `SelectionCandidate::ProjectionCandidate(_)` and
/// `ProjectionTyCandidate::TraitDef`. /// `ProjectionTyCandidate::TraitDef`.
/// Specifically this is the bounds written on the trait's type /// Specifically this is the bounds written on the trait's type
/// definition, or those after the `impl` keyword /// definition, or those after the `impl` keyword

View file

@ -105,9 +105,10 @@ pub enum SelectionCandidate<'tcx> {
ImplCandidate(DefId), ImplCandidate(DefId),
AutoImplCandidate(DefId), AutoImplCandidate(DefId),
/// This is a trait matching with a projected type as `Self`, and /// This is a trait matching with a projected type as `Self`, and we found
/// we found an applicable bound in the trait definition. /// an applicable bound in the trait definition. The `usize` is an index
ProjectionCandidate, /// into the list returned by `tcx.item_bounds`.
ProjectionCandidate(usize),
/// Implementation of a `Fn`-family trait by one of the anonymous types /// Implementation of a `Fn`-family trait by one of the anonymous types
/// generated for a `||` expression. /// generated for a `||` expression.

View file

@ -323,12 +323,12 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
_ => return, _ => return,
} }
let result = self.infcx.probe(|_| { let result = self
self.match_projection_obligation_against_definition_bounds(obligation).is_some() .infcx
}); .probe(|_| self.match_projection_obligation_against_definition_bounds(obligation));
if result { for predicate_index in result {
candidates.vec.push(ProjectionCandidate); candidates.vec.push(ProjectionCandidate(predicate_index));
} }
} }

View file

@ -70,8 +70,8 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
Ok(ImplSource::AutoImpl(data)) Ok(ImplSource::AutoImpl(data))
} }
ProjectionCandidate => { ProjectionCandidate(idx) => {
let obligations = self.confirm_projection_candidate(obligation); let obligations = self.confirm_projection_candidate(obligation, idx);
Ok(ImplSource::Param(obligations)) Ok(ImplSource::Param(obligations))
} }
@ -121,11 +121,22 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
fn confirm_projection_candidate( fn confirm_projection_candidate(
&mut self, &mut self,
obligation: &TraitObligation<'tcx>, obligation: &TraitObligation<'tcx>,
idx: usize,
) -> Vec<PredicateObligation<'tcx>> { ) -> Vec<PredicateObligation<'tcx>> {
self.infcx.commit_unconditionally(|_| { self.infcx.commit_unconditionally(|_| {
let candidate = self let tcx = self.tcx();
.match_projection_obligation_against_definition_bounds(obligation)
.unwrap_or_else(|| bug!("Can't find selected projection candidate")); let bound_self_ty = self.infcx.shallow_resolve(obligation.self_ty());
let (def_id, substs) = match bound_self_ty.skip_binder().kind {
ty::Projection(proj) => (proj.item_def_id, proj.substs),
ty::Opaque(def_id, substs) => (def_id, substs),
_ => bug!("projection candidate for unexpected type: {:?}", bound_self_ty),
};
let candidate_predicate = tcx.item_bounds(def_id)[idx].subst(tcx, substs);
let candidate = candidate_predicate
.to_opt_poly_trait_ref()
.expect("projection candidate is not a trait predicate");
let mut obligations = self let mut obligations = self
.infcx .infcx
.at(&obligation.cause, obligation.param_env) .at(&obligation.cause, obligation.param_env)
@ -139,7 +150,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
); );
}); });
// Require that the projection is well-formed. // Require that the projection is well-formed.
let self_ty = self.infcx.replace_bound_vars_with_placeholders(&obligation.self_ty()); let self_ty = self.infcx.replace_bound_vars_with_placeholders(&bound_self_ty);
let self_ty = normalize_with_depth_to( let self_ty = normalize_with_depth_to(
self, self,
obligation.param_env, obligation.param_env,
@ -152,7 +163,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
obligation.cause.clone(), obligation.cause.clone(),
obligation.recursion_depth + 1, obligation.recursion_depth + 1,
obligation.param_env, obligation.param_env,
ty::PredicateKind::WellFormed(self_ty.into()).to_predicate(self.tcx()), ty::PredicateKind::WellFormed(self_ty.into()).to_predicate(tcx),
)); ));
obligations obligations
}) })

View file

@ -1163,11 +1163,12 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
/// ///
/// Given an obligation like `<T as Foo>::Bar: Baz` where the self type is /// Given an obligation like `<T as Foo>::Bar: Baz` where the self type is
/// a projection, look at the bounds of `T::Bar`, see if we can find a /// a projection, look at the bounds of `T::Bar`, see if we can find a
/// `Baz` bound and it there is one it returns it. /// `Baz` bound. We return indexes into the list returned by
/// `tcx.item_bounds` for any applicable bounds.
fn match_projection_obligation_against_definition_bounds( fn match_projection_obligation_against_definition_bounds(
&mut self, &mut self,
obligation: &TraitObligation<'tcx>, obligation: &TraitObligation<'tcx>,
) -> Option<ty::PolyTraitRef<'tcx>> { ) -> smallvec::SmallVec<[usize; 2]> {
let poly_trait_predicate = self.infcx().resolve_vars_if_possible(&obligation.predicate); let poly_trait_predicate = self.infcx().resolve_vars_if_possible(&obligation.predicate);
let placeholder_trait_predicate = let placeholder_trait_predicate =
self.infcx().replace_bound_vars_with_placeholders(&poly_trait_predicate); self.infcx().replace_bound_vars_with_placeholders(&poly_trait_predicate);
@ -1192,25 +1193,33 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
}; };
let bounds = tcx.item_bounds(def_id).subst(tcx, substs); let bounds = tcx.item_bounds(def_id).subst(tcx, substs);
let matching_bound = bounds.iter().find_map(|bound| { let matching_bounds = bounds
if let ty::PredicateAtom::Trait(pred, _) = bound.skip_binders() { .iter()
let bound = ty::Binder::bind(pred.trait_ref); .enumerate()
if self.infcx.probe(|_| { .filter_map(|(idx, bound)| {
self.match_projection(obligation, bound, placeholder_trait_predicate.trait_ref) if let ty::PredicateAtom::Trait(pred, _) = bound.skip_binders() {
let bound = ty::Binder::bind(pred.trait_ref);
if self.infcx.probe(|_| {
self.match_projection(
obligation,
bound,
placeholder_trait_predicate.trait_ref,
)
.is_ok() .is_ok()
}) { }) {
return Some(bound); return Some(idx);
}
} }
} None
None })
}); .collect();
debug!( debug!(
"match_projection_obligation_against_definition_bounds: \ "match_projection_obligation_against_definition_bounds: \
matching_bound={:?}", matching_bounds={:?}",
matching_bound matching_bounds
); );
matching_bound matching_bounds
} }
fn match_projection( fn match_projection(
@ -1299,14 +1308,14 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
// clause so don't go around looking for impls. // clause so don't go around looking for impls.
!is_global(cand) !is_global(cand)
} }
ObjectCandidate | ProjectionCandidate => { ObjectCandidate | ProjectionCandidate(_) => {
// Arbitrarily give param candidates priority // Arbitrarily give param candidates priority
// over projection and object candidates. // over projection and object candidates.
!is_global(cand) !is_global(cand)
} }
ParamCandidate(..) => false, ParamCandidate(..) => false,
}, },
ObjectCandidate | ProjectionCandidate => match victim.candidate { ObjectCandidate | ProjectionCandidate(_) => match victim.candidate {
AutoImplCandidate(..) => { AutoImplCandidate(..) => {
bug!( bug!(
"default implementations shouldn't be recorded \ "default implementations shouldn't be recorded \
@ -1323,10 +1332,11 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
| BuiltinUnsizeCandidate | BuiltinUnsizeCandidate
| BuiltinCandidate { .. } | BuiltinCandidate { .. }
| TraitAliasCandidate(..) => true, | TraitAliasCandidate(..) => true,
ObjectCandidate | ProjectionCandidate => { ObjectCandidate | ProjectionCandidate(_) => {
// Arbitrarily give param candidates priority // Shouldn't have both an object and projection candidate,
// over projection and object candidates. // nor multiple object candidates. Multiple projection
true // candidates are ambiguous.
false
} }
ParamCandidate(ref cand) => is_global(cand), ParamCandidate(ref cand) => is_global(cand),
}, },

View file

@ -0,0 +1,23 @@
// Make sure that if there are multiple applicable bounds on a projection, we
// consider them ambiguous. In this test we are initially trying to solve
// `Self::Repr: From<_>`, which is ambiguous until we later infer `_` to
// `{integer}`.
// check-pass
trait PrimeField: Sized {
type Repr: From<u64> + From<Self>;
type Repr2: From<Self> + From<u64>;
fn method() {
Self::Repr::from(10);
Self::Repr2::from(10);
}
}
fn function<T: PrimeField>() {
T::Repr::from(10);
T::Repr2::from(10);
}
fn main() {}