Properly generate multiple candidates for trait upcasting coercion.

This commit is contained in:
Charles Lew
2021-08-18 02:41:29 +08:00
parent adf1688447
commit 1e605023ec
8 changed files with 155 additions and 159 deletions

View File

@@ -118,6 +118,11 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
let data = self.confirm_builtin_unsize_candidate(obligation)?;
Ok(ImplSource::Builtin(data))
}
TraitUpcastingUnsizeCandidate(idx) => {
let data = self.confirm_trait_upcasting_unsize_candidate(obligation, idx)?;
Ok(ImplSource::Builtin(data))
}
}
}
@@ -685,6 +690,81 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
.map_err(|e| OutputTypeParameterMismatch(expected_trait_ref, obligation_trait_ref, e))
}
fn confirm_trait_upcasting_unsize_candidate(
&mut self,
obligation: &TraitObligation<'tcx>,
idx: usize,
) -> Result<ImplSourceBuiltinData<PredicateObligation<'tcx>>, SelectionError<'tcx>> {
let tcx = self.tcx();
// `assemble_candidates_for_unsizing` should ensure there are no late-bound
// regions here. See the comment there for more details.
let source = self.infcx.shallow_resolve(obligation.self_ty().no_bound_vars().unwrap());
let target = obligation.predicate.skip_binder().trait_ref.substs.type_at(1);
let target = self.infcx.shallow_resolve(target);
debug!(?source, ?target, "confirm_trait_upcasting_unsize_candidate");
let mut nested = vec![];
match (source.kind(), target.kind()) {
// TraitA+Kx+'a -> TraitB+Ky+'b (trait upcasting coercion).
(&ty::Dynamic(ref data_a, r_a), &ty::Dynamic(ref data_b, r_b)) => {
// See `assemble_candidates_for_unsizing` for more info.
// We already checked the compatiblity of auto traits within `assemble_candidates_for_unsizing`.
let principal_a = data_a.principal().unwrap();
let source_trait_ref = principal_a.with_self_ty(tcx, source);
let target_trait_ref = util::supertraits(tcx, source_trait_ref).nth(idx).unwrap();
assert_eq!(data_b.principal_def_id(), Some(target_trait_ref.def_id()));
let existential_predicate = target_trait_ref.map_bound(|trait_ref| {
ty::ExistentialPredicate::Trait(ty::ExistentialTraitRef::erase_self_ty(
tcx, trait_ref,
))
});
let iter = Some(existential_predicate)
.into_iter()
.chain(
data_a
.projection_bounds()
.map(|b| b.map_bound(ty::ExistentialPredicate::Projection)),
)
.chain(
data_b
.auto_traits()
.map(ty::ExistentialPredicate::AutoTrait)
.map(ty::Binder::dummy),
);
let existential_predicates = tcx.mk_poly_existential_predicates(iter);
let source_trait = tcx.mk_dynamic(existential_predicates, r_b);
// Require that the traits involved in this upcast are **equal**;
// only the **lifetime bound** is changed.
let InferOk { obligations, .. } = self
.infcx
.at(&obligation.cause, obligation.param_env)
.sup(target, source_trait)
.map_err(|_| Unimplemented)?;
nested.extend(obligations);
// Register one obligation for 'a: 'b.
let cause = ObligationCause::new(
obligation.cause.span,
obligation.cause.body_id,
ObjectCastObligation(target),
);
let outlives = ty::OutlivesPredicate(r_a, r_b);
nested.push(Obligation::with_depth(
cause,
obligation.recursion_depth + 1,
obligation.param_env,
obligation.predicate.rebind(outlives).to_predicate(tcx),
));
}
_ => bug!(),
};
Ok(ImplSourceBuiltinData { nested })
}
fn confirm_builtin_unsize_candidate(
&mut self,
obligation: &TraitObligation<'tcx>,
@@ -701,58 +781,13 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
let mut nested = vec![];
match (source.kind(), target.kind()) {
// Trait+Kx+'a -> Trait+Ky+'b (upcasts).
// Trait+Kx+'a -> Trait+Ky+'b (auto traits and lifetime subtyping).
(&ty::Dynamic(ref data_a, r_a), &ty::Dynamic(ref data_b, r_b)) => {
// Upcast coercions permit several things:
//
// 1. Dropping auto traits, e.g., `Foo + Send` to `Foo`
// 2. Tightening the region bound, e.g., `Foo + 'a` to `Foo + 'b` if `'a: 'b`
// 3. Tightening trait to its super traits, eg. `Foo` to `Bar` if `Foo: Bar`
//
// Note that neither of the first two of these changes requires any
// change at runtime. The third needs to change pointer metadata at runtime.
//
// We always perform upcasting coercions when we can because of reason
// #2 (region bounds).
// See `assemble_candidates_for_unsizing` for more info.
// We already checked the compatiblity of auto traits within `assemble_candidates_for_unsizing`.
let principal_a = data_a.principal();
let principal_def_id_b = data_b.principal_def_id();
let existential_predicate = if let Some(principal_a) = principal_a {
let source_trait_ref = principal_a.with_self_ty(tcx, source);
let target_trait_did = principal_def_id_b.ok_or_else(|| Unimplemented)?;
let upcast_idx = util::supertraits(tcx, source_trait_ref)
.position(|upcast_trait_ref| upcast_trait_ref.def_id() == target_trait_did)
.ok_or_else(|| Unimplemented)?;
// FIXME(crlf0710): This is less than ideal, for example,
// if the trait is defined as `trait Foo: Bar<u32> + Bar<i32>`,
// the coercion from Box<Foo> to Box<dyn Bar<_>> is actually ambiguous.
// We currently make this coercion fail for now.
//
// see #65991 for more information.
if util::supertraits(tcx, source_trait_ref)
.skip(upcast_idx + 1)
.any(|upcast_trait_ref| upcast_trait_ref.def_id() == target_trait_did)
{
return Err(Unimplemented);
}
let target_trait_ref =
util::supertraits(tcx, source_trait_ref).nth(upcast_idx).unwrap();
let existential_predicate = target_trait_ref.map_bound(|trait_ref| {
ty::ExistentialPredicate::Trait(ty::ExistentialTraitRef::erase_self_ty(
tcx, trait_ref,
))
});
Some(existential_predicate)
} else if principal_def_id_b.is_none() {
None
} else {
return Err(Unimplemented);
};
let iter = existential_predicate
let iter = data_a
.principal()
.map(|b| b.map_bound(ty::ExistentialPredicate::Trait))
.into_iter()
.chain(
data_a