refactor: use select inside of a probe
We ought not to be affecting inference state when assembling candidates, so invoke select inside of a probe.
This commit is contained in:
parent
4fdf2c4f97
commit
df5c62bed2
1 changed files with 294 additions and 201 deletions
|
|
@ -152,14 +152,8 @@ enum ProjectionTyCandidate<'tcx> {
|
|||
// from the definition of `Trait` when you have something like <<A as Trait>::B as Trait2>::C
|
||||
TraitDef(ty::PolyProjectionPredicate<'tcx>),
|
||||
|
||||
// defined in an impl
|
||||
Impl(VtableImplData<'tcx, PredicateObligation<'tcx>>),
|
||||
|
||||
// closure return type
|
||||
Closure(VtableClosureData<'tcx, PredicateObligation<'tcx>>),
|
||||
|
||||
// fn pointer return type
|
||||
FnPointer(VtableFnPointerData<'tcx, PredicateObligation<'tcx>>),
|
||||
// from a "impl" (or a "pseudo-impl" returned by select)
|
||||
Select,
|
||||
}
|
||||
|
||||
struct ProjectionTyCandidateSet<'tcx> {
|
||||
|
|
@ -599,10 +593,8 @@ fn project_type<'cx, 'gcx, 'tcx>(
|
|||
debug!("retaining param-env candidates only from {:?}", candidates.vec);
|
||||
candidates.vec.retain(|c| match *c {
|
||||
ProjectionTyCandidate::ParamEnv(..) => true,
|
||||
ProjectionTyCandidate::Impl(..) |
|
||||
ProjectionTyCandidate::Closure(..) |
|
||||
ProjectionTyCandidate::TraitDef(..) |
|
||||
ProjectionTyCandidate::FnPointer(..) => false,
|
||||
ProjectionTyCandidate::Select => false,
|
||||
});
|
||||
debug!("resulting candidate set: {:?}", candidates.vec);
|
||||
if candidates.vec.len() != 1 {
|
||||
|
|
@ -612,78 +604,12 @@ fn project_type<'cx, 'gcx, 'tcx>(
|
|||
|
||||
assert!(candidates.vec.len() <= 1);
|
||||
|
||||
let possible_candidate = candidates.vec.pop().and_then(|candidate| {
|
||||
// In Any (i.e. trans) mode, all projections succeed;
|
||||
// otherwise, we need to be sensitive to `default` and
|
||||
// specialization.
|
||||
if !selcx.projection_mode().is_any() {
|
||||
if let ProjectionTyCandidate::Impl(ref impl_data) = candidate {
|
||||
if let Some(node_item) = assoc_ty_def(selcx,
|
||||
impl_data.impl_def_id,
|
||||
obligation.predicate.item_name) {
|
||||
if node_item.node.is_from_trait() {
|
||||
if node_item.item.ty.is_some() {
|
||||
// If the associated type has a default from the
|
||||
// trait, that should be considered `default` and
|
||||
// hence not projected.
|
||||
//
|
||||
// Note, however, that we allow a projection from
|
||||
// the trait specifically in the case that the trait
|
||||
// does *not* give a default. This is purely to
|
||||
// avoid spurious errors: the situation can only
|
||||
// arise when *no* impl in the specialization chain
|
||||
// has provided a definition for the type. When we
|
||||
// confirm the candidate, we'll turn the projection
|
||||
// into a TyError, since the actual error will be
|
||||
// reported in `check_impl_items_against_trait`.
|
||||
return None;
|
||||
}
|
||||
} else if node_item.item.defaultness.is_default() {
|
||||
return None;
|
||||
}
|
||||
} else {
|
||||
// Normally this situation could only arise througha
|
||||
// compiler bug, but at coherence-checking time we only look
|
||||
// at the topmost impl (we don't even consider the trait
|
||||
// itself) for the definition -- so we can fail to find a
|
||||
// definition of the type even if it exists.
|
||||
|
||||
// For now, we just unconditionally ICE, because otherwise,
|
||||
// examples like the following will succeed:
|
||||
//
|
||||
// ```
|
||||
// trait Assoc {
|
||||
// type Output;
|
||||
// }
|
||||
//
|
||||
// impl<T> Assoc for T {
|
||||
// default type Output = bool;
|
||||
// }
|
||||
//
|
||||
// impl Assoc for u8 {}
|
||||
// impl Assoc for u16 {}
|
||||
//
|
||||
// trait Foo {}
|
||||
// impl Foo for <u8 as Assoc>::Output {}
|
||||
// impl Foo for <u16 as Assoc>::Output {}
|
||||
// return None;
|
||||
// }
|
||||
// ```
|
||||
//
|
||||
// The essential problem here is that the projection fails,
|
||||
// leaving two unnormalized types, which appear not to unify
|
||||
// -- so the overlap check succeeds, when it should fail.
|
||||
bug!("Tried to project an inherited associated type during \
|
||||
coherence checking, which is currently not supported.");
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(candidate)
|
||||
});
|
||||
|
||||
match possible_candidate {
|
||||
match candidates.vec.pop() {
|
||||
Some(candidate) => {
|
||||
let (ty, obligations) = confirm_candidate(selcx, obligation, candidate);
|
||||
let (ty, obligations) = confirm_candidate(selcx,
|
||||
obligation,
|
||||
&obligation_trait_ref,
|
||||
candidate);
|
||||
Ok(ProjectedTy::Progress(ty, obligations))
|
||||
}
|
||||
None => {
|
||||
|
|
@ -802,11 +728,256 @@ fn assemble_candidates_from_predicates<'cx, 'gcx, 'tcx, I>(
|
|||
}
|
||||
}
|
||||
|
||||
fn assemble_candidates_from_object_type<'cx, 'gcx, 'tcx>(
|
||||
fn assemble_candidates_from_impls<'cx, 'gcx, 'tcx>(
|
||||
selcx: &mut SelectionContext<'cx, 'gcx, 'tcx>,
|
||||
obligation: &ProjectionTyObligation<'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 vtable = match selcx.select(&trait_obligation) {
|
||||
Ok(Some(vtable)) => vtable,
|
||||
Ok(None) => {
|
||||
candidate_set.ambiguous = true;
|
||||
return Ok(());
|
||||
}
|
||||
Err(e) => {
|
||||
debug!("assemble_candidates_from_impls: selection error {:?}",
|
||||
e);
|
||||
return Err(e);
|
||||
}
|
||||
};
|
||||
|
||||
match vtable {
|
||||
super::VtableClosure(_) |
|
||||
super::VtableFnPointer(_) |
|
||||
super::VtableObject(_) => {
|
||||
debug!("assemble_candidates_from_impls: vtable={:?}",
|
||||
vtable);
|
||||
|
||||
candidate_set.vec.push(ProjectionTyCandidate::Select);
|
||||
}
|
||||
super::VtableImpl(ref impl_data) if !selcx.projection_mode().is_any() => {
|
||||
// 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
|
||||
// impl's type is declared as default, then we disable
|
||||
// projection (even if the trait ref is fully
|
||||
// monomorphic). In the case where trait ref is not
|
||||
// fully monomorphic (i.e., includes type parameters),
|
||||
// this is because those type parameters may
|
||||
// ultimately be bound to types from other crates that
|
||||
// may have specialized impls we can't see. In the
|
||||
// case where the trait ref IS fully monomorphic, this
|
||||
// is a policy decision that we made in the RFC in
|
||||
// order to preserve flexibility for the crate that
|
||||
// defined the specializable impl to specialize later
|
||||
// for existing types.
|
||||
//
|
||||
// In either case, we handle this by not adding a
|
||||
// candidate for an impl if it contains a `default`
|
||||
// type.
|
||||
let opt_node_item = assoc_ty_def(selcx,
|
||||
impl_data.impl_def_id,
|
||||
obligation.predicate.item_name);
|
||||
let new_candidate = if let Some(node_item) = opt_node_item {
|
||||
if node_item.node.is_from_trait() {
|
||||
if node_item.item.ty.is_some() {
|
||||
// The impl inherited a `type Foo =
|
||||
// Bar` given in the trait, which is
|
||||
// implicitly default. No candidate.
|
||||
None
|
||||
} else {
|
||||
// The impl did not specify `type` and neither
|
||||
// did the trait:
|
||||
//
|
||||
// ```rust
|
||||
// trait Foo { type T; }
|
||||
// impl Foo for Bar { }
|
||||
// ```
|
||||
//
|
||||
// This is an error, but it will be
|
||||
// reported in `check_impl_items_against_trait`.
|
||||
// We accept it here but will flag it as
|
||||
// an error when we confirm the candidate
|
||||
// (which will ultimately lead to `normalize_to_error`
|
||||
// being invoked).
|
||||
Some(ProjectionTyCandidate::Select)
|
||||
}
|
||||
} else if node_item.item.defaultness.is_default() {
|
||||
// The impl specified `default type Foo =
|
||||
// Bar`. No candidate.
|
||||
None
|
||||
} else {
|
||||
// The impl specified `type Foo = Bar`
|
||||
// with no default. Add a candidate.
|
||||
Some(ProjectionTyCandidate::Select)
|
||||
}
|
||||
} else {
|
||||
// This is saying that neither the trait nor
|
||||
// the impl contain a definition for this
|
||||
// associated type. Normally this situation
|
||||
// could only arise through a compiler bug --
|
||||
// if the user wrote a bad item name, it
|
||||
// should have failed in astconv. **However**,
|
||||
// at coherence-checking time, we only look at
|
||||
// the topmost impl (we don't even consider
|
||||
// the trait itself) for the definition -- and
|
||||
// so in that case it may be that the trait
|
||||
// *DOES* have a declaration, but we don't see
|
||||
// it, and we end up in this branch.
|
||||
//
|
||||
// This is kind of tricky to handle actually.
|
||||
// For now, we just unconditionally ICE,
|
||||
// because otherwise, examples like the
|
||||
// following will succeed:
|
||||
//
|
||||
// ```
|
||||
// trait Assoc {
|
||||
// type Output;
|
||||
// }
|
||||
//
|
||||
// impl<T> Assoc for T {
|
||||
// default type Output = bool;
|
||||
// }
|
||||
//
|
||||
// impl Assoc for u8 {}
|
||||
// impl Assoc for u16 {}
|
||||
//
|
||||
// trait Foo {}
|
||||
// impl Foo for <u8 as Assoc>::Output {}
|
||||
// impl Foo for <u16 as Assoc>::Output {}
|
||||
// return None;
|
||||
// }
|
||||
// ```
|
||||
//
|
||||
// The essential problem here is that the
|
||||
// projection fails, leaving two unnormalized
|
||||
// types, which appear not to unify -- so the
|
||||
// overlap check succeeds, when it should
|
||||
// fail.
|
||||
bug!("Tried to project an inherited associated type during \
|
||||
coherence checking, which is currently not supported.");
|
||||
};
|
||||
candidate_set.vec.extend(new_candidate);
|
||||
}
|
||||
super::VtableImpl(_) => {
|
||||
// In trans mode, we can just project out of impls, no prob.
|
||||
assert!(selcx.projection_mode().is_any());
|
||||
candidate_set.vec.push(ProjectionTyCandidate::Select);
|
||||
}
|
||||
super::VtableParam(..) => {
|
||||
// This case tell us nothing about the value of an
|
||||
// associated type. Consider:
|
||||
//
|
||||
// ```
|
||||
// trait SomeTrait { type Foo; }
|
||||
// fn foo<T:SomeTrait>(...) { }
|
||||
// ```
|
||||
//
|
||||
// If the user writes `<T as SomeTrait>::Foo`, then the `T
|
||||
// : SomeTrait` binding does not help us decide what the
|
||||
// type `Foo` is (at least, not more specifically than
|
||||
// what we already knew).
|
||||
//
|
||||
// But wait, you say! What about an example like this:
|
||||
//
|
||||
// ```
|
||||
// fn bar<T:SomeTrait<Foo=usize>>(...) { ... }
|
||||
// ```
|
||||
//
|
||||
// Doesn't the `T : Sometrait<Foo=usize>` predicate help
|
||||
// resolve `T::Foo`? And of course it does, but in fact
|
||||
// that single predicate is desugared into two predicates
|
||||
// in the compiler: a trait predicate (`T : SomeTrait`) and a
|
||||
// projection. And the projection where clause is handled
|
||||
// in `assemble_candidates_from_param_env`.
|
||||
}
|
||||
super::VtableDefaultImpl(..) |
|
||||
super::VtableBuiltin(..) => {
|
||||
// These traits have no associated types.
|
||||
span_bug!(
|
||||
obligation.cause.span,
|
||||
"Cannot project an associated type from `{:?}`",
|
||||
vtable);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
fn confirm_candidate<'cx, 'gcx, 'tcx>(
|
||||
selcx: &mut SelectionContext<'cx, 'gcx, 'tcx>,
|
||||
obligation: &ProjectionTyObligation<'tcx>,
|
||||
obligation_trait_ref: &ty::TraitRef<'tcx>,
|
||||
candidate: ProjectionTyCandidate<'tcx>)
|
||||
-> (Ty<'tcx>, Vec<PredicateObligation<'tcx>>)
|
||||
{
|
||||
debug!("confirm_candidate(candidate={:?}, obligation={:?})",
|
||||
candidate,
|
||||
obligation);
|
||||
|
||||
match candidate {
|
||||
ProjectionTyCandidate::ParamEnv(poly_projection) |
|
||||
ProjectionTyCandidate::TraitDef(poly_projection) => {
|
||||
confirm_param_env_candidate(selcx, obligation, poly_projection)
|
||||
}
|
||||
|
||||
ProjectionTyCandidate::Select => {
|
||||
confirm_select_candidate(selcx, obligation, obligation_trait_ref)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn confirm_select_candidate<'cx, 'gcx, 'tcx>(
|
||||
selcx: &mut SelectionContext<'cx, 'gcx, 'tcx>,
|
||||
obligation: &ProjectionTyObligation<'tcx>,
|
||||
obligation_trait_ref: &ty::TraitRef<'tcx>)
|
||||
-> (Ty<'tcx>, Vec<PredicateObligation<'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),
|
||||
super::VtableClosure(data) =>
|
||||
confirm_closure_candidate(selcx, obligation, data),
|
||||
super::VtableFnPointer(data) =>
|
||||
confirm_fn_pointer_candidate(selcx, obligation, data),
|
||||
super::VtableObject(_) =>
|
||||
confirm_object_candidate(selcx, obligation, obligation_trait_ref),
|
||||
super::VtableDefaultImpl(..) |
|
||||
super::VtableParam(..) |
|
||||
super::VtableBuiltin(..) =>
|
||||
// we don't create Select candidates with this kind of resolution
|
||||
span_bug!(
|
||||
obligation.cause.span,
|
||||
"Cannot project an associated type from `{:?}`",
|
||||
vtable),
|
||||
}
|
||||
}
|
||||
|
||||
fn confirm_object_candidate<'cx, 'gcx, 'tcx>(
|
||||
selcx: &mut SelectionContext<'cx, 'gcx, 'tcx>,
|
||||
obligation: &ProjectionTyObligation<'tcx>,
|
||||
obligation_trait_ref: &ty::TraitRef<'tcx>)
|
||||
-> (Ty<'tcx>, Vec<PredicateObligation<'tcx>>)
|
||||
{
|
||||
let self_ty = obligation_trait_ref.self_ty();
|
||||
let object_ty = selcx.infcx().shallow_resolve(self_ty);
|
||||
|
|
@ -825,127 +996,49 @@ fn assemble_candidates_from_object_type<'cx, 'gcx, 'tcx>(
|
|||
let env_predicates = projection_bounds.iter()
|
||||
.map(|p| p.to_predicate())
|
||||
.collect();
|
||||
let env_predicates = elaborate_predicates(selcx.tcx(), env_predicates);
|
||||
assemble_candidates_from_predicates(selcx,
|
||||
obligation,
|
||||
obligation_trait_ref,
|
||||
candidate_set,
|
||||
ProjectionTyCandidate::ParamEnv,
|
||||
env_predicates)
|
||||
}
|
||||
let env_predicate = {
|
||||
let env_predicates = elaborate_predicates(selcx.tcx(), env_predicates);
|
||||
|
||||
fn assemble_candidates_from_impls<'cx, 'gcx, 'tcx>(
|
||||
selcx: &mut SelectionContext<'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());
|
||||
let vtable = match selcx.select(&trait_obligation) {
|
||||
Ok(Some(vtable)) => vtable,
|
||||
Ok(None) => {
|
||||
candidate_set.ambiguous = true;
|
||||
return Ok(());
|
||||
}
|
||||
Err(e) => {
|
||||
debug!("assemble_candidates_from_impls: selection error {:?}",
|
||||
e);
|
||||
return Err(e);
|
||||
// select only those projections that are actually projecting an
|
||||
// item with the correct name
|
||||
let env_predicates = env_predicates.filter_map(|p| match p {
|
||||
ty::Predicate::Projection(data) =>
|
||||
if data.item_name() == obligation.predicate.item_name {
|
||||
Some(data)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
_ => None
|
||||
});
|
||||
|
||||
// select those with a relevant trait-ref
|
||||
let mut env_predicates = env_predicates.filter(|data| {
|
||||
let origin = TypeOrigin::RelateOutputImplTypes(obligation.cause.span);
|
||||
let data_poly_trait_ref = data.to_poly_trait_ref();
|
||||
let obligation_poly_trait_ref = obligation_trait_ref.to_poly_trait_ref();
|
||||
selcx.infcx().probe(|_| {
|
||||
selcx.infcx().sub_poly_trait_refs(false,
|
||||
origin,
|
||||
data_poly_trait_ref,
|
||||
obligation_poly_trait_ref).is_ok()
|
||||
})
|
||||
});
|
||||
|
||||
// select the first matching one; there really ought to be one or
|
||||
// else the object type is not WF, since an object type should
|
||||
// include all of its projections explicitly
|
||||
match env_predicates.next() {
|
||||
Some(env_predicate) => env_predicate,
|
||||
None => {
|
||||
debug!("confirm_object_candidate: no env-predicate \
|
||||
found in object type `{:?}`; ill-formed",
|
||||
object_ty);
|
||||
return (selcx.tcx().types.err, vec!());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
match vtable {
|
||||
super::VtableImpl(data) => {
|
||||
debug!("assemble_candidates_from_impls: impl candidate {:?}",
|
||||
data);
|
||||
|
||||
candidate_set.vec.push(
|
||||
ProjectionTyCandidate::Impl(data));
|
||||
}
|
||||
super::VtableObject(_) => {
|
||||
assemble_candidates_from_object_type(
|
||||
selcx, obligation, obligation_trait_ref, candidate_set);
|
||||
}
|
||||
super::VtableClosure(data) => {
|
||||
candidate_set.vec.push(
|
||||
ProjectionTyCandidate::Closure(data));
|
||||
}
|
||||
super::VtableFnPointer(data) => {
|
||||
candidate_set.vec.push(
|
||||
ProjectionTyCandidate::FnPointer(data));
|
||||
}
|
||||
super::VtableParam(..) => {
|
||||
// This case tell us nothing about the value of an
|
||||
// associated type. Consider:
|
||||
//
|
||||
// ```
|
||||
// trait SomeTrait { type Foo; }
|
||||
// fn foo<T:SomeTrait>(...) { }
|
||||
// ```
|
||||
//
|
||||
// If the user writes `<T as SomeTrait>::Foo`, then the `T
|
||||
// : SomeTrait` binding does not help us decide what the
|
||||
// type `Foo` is (at least, not more specifically than
|
||||
// what we already knew).
|
||||
//
|
||||
// But wait, you say! What about an example like this:
|
||||
//
|
||||
// ```
|
||||
// fn bar<T:SomeTrait<Foo=usize>>(...) { ... }
|
||||
// ```
|
||||
//
|
||||
// Doesn't the `T : Sometrait<Foo=usize>` predicate help
|
||||
// resolve `T::Foo`? And of course it does, but in fact
|
||||
// that single predicate is desugared into two predicates
|
||||
// in the compiler: a trait predicate (`T : SomeTrait`) and a
|
||||
// projection. And the projection where clause is handled
|
||||
// in `assemble_candidates_from_param_env`.
|
||||
}
|
||||
super::VtableDefaultImpl(..) |
|
||||
super::VtableBuiltin(..) => {
|
||||
// These traits have no associated types.
|
||||
span_bug!(
|
||||
obligation.cause.span,
|
||||
"Cannot project an associated type from `{:?}`",
|
||||
vtable);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn confirm_candidate<'cx, 'gcx, 'tcx>(
|
||||
selcx: &mut SelectionContext<'cx, 'gcx, 'tcx>,
|
||||
obligation: &ProjectionTyObligation<'tcx>,
|
||||
candidate: ProjectionTyCandidate<'tcx>)
|
||||
-> (Ty<'tcx>, Vec<PredicateObligation<'tcx>>)
|
||||
{
|
||||
debug!("confirm_candidate(candidate={:?}, obligation={:?})",
|
||||
candidate,
|
||||
obligation);
|
||||
|
||||
match candidate {
|
||||
ProjectionTyCandidate::ParamEnv(poly_projection) |
|
||||
ProjectionTyCandidate::TraitDef(poly_projection) => {
|
||||
confirm_param_env_candidate(selcx, obligation, poly_projection)
|
||||
}
|
||||
|
||||
ProjectionTyCandidate::Impl(impl_vtable) => {
|
||||
confirm_impl_candidate(selcx, obligation, impl_vtable)
|
||||
}
|
||||
|
||||
ProjectionTyCandidate::Closure(closure_vtable) => {
|
||||
confirm_closure_candidate(selcx, obligation, closure_vtable)
|
||||
}
|
||||
|
||||
ProjectionTyCandidate::FnPointer(fn_pointer_vtable) => {
|
||||
confirm_fn_pointer_candidate(selcx, obligation, fn_pointer_vtable)
|
||||
}
|
||||
}
|
||||
confirm_param_env_candidate(selcx, obligation, env_predicate)
|
||||
}
|
||||
|
||||
fn confirm_fn_pointer_candidate<'cx, 'gcx, 'tcx>(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue