Auto merge of #114036 - compiler-errors:upcast-to-fewer-assocs, r=lcnr
Rework upcasting confirmation to support upcasting to fewer projections in target bounds This PR implements a modified trait upcasting algorithm that is resilient to changes in the number of associated types in the bounds of the source and target trait objects. It does this by equating each bound of the target trait ref individually against the bounds of the source trait ref, rather than doing them all together by constructing a new trait object. #### The new way we do trait upcasting confirmation 1. Equate the target trait object's principal trait ref with one of the supertraits of the source trait object's principal.fdcab310b2/compiler/rustc_trait_selection/src/traits/select/mod.rs (L2509-L2525)
2. Make sure that every auto trait in the *target* trait object is present in the source trait ref's bounds.fdcab310b2/compiler/rustc_trait_selection/src/traits/select/mod.rs (L2559-L2562)
3. For each projection in the *target* trait object, make sure there is exactly one projection that equates with it in the source trait ref's bound. If there is more than one, bail with ambiguity.fdcab310b2/compiler/rustc_trait_selection/src/traits/select/mod.rs (L2526-L2557)
* Since there may be more than one that applies, we probe first to check that there is exactly one, then we equate it outside of a probe once we know that it's unique. 4. Make sure the lifetime of the source trait object outlives the lifetime of the target. <details> <summary>Meanwhile, this is how we used to do upcasting:</summary> 1. For each supertrait of the source trait object, take that supertrait, append the source object's projection bounds, and the *target* trait object's auto trait bounds, and make this into a new object type:d12c6e947c/compiler/rustc_trait_selection/src/traits/select/confirmation.rs (L915-L929)
2. Then equate it with the target trait object:d12c6e947c/compiler/rustc_trait_selection/src/traits/select/confirmation.rs (L936)
This will be a type mismatch if the target trait object has fewer projection bounds, since we compare the bounds structurally in relate:d12c6e947c/compiler/rustc_middle/src/ty/relate.rs (L696-L698)
</details> Fixes #114035 Also fixes #114113, because I added a normalize call in the old solver. r? types
This commit is contained in:
commit
4f7bb9890c
@ -481,3 +481,31 @@ fn to_trace(
|
||||
TypeTrace { cause: cause.clone(), values: Sigs(ExpectedFound::new(a_is_expected, a, b)) }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> ToTrace<'tcx> for ty::PolyExistentialTraitRef<'tcx> {
|
||||
fn to_trace(
|
||||
cause: &ObligationCause<'tcx>,
|
||||
a_is_expected: bool,
|
||||
a: Self,
|
||||
b: Self,
|
||||
) -> TypeTrace<'tcx> {
|
||||
TypeTrace {
|
||||
cause: cause.clone(),
|
||||
values: ExistentialTraitRef(ExpectedFound::new(a_is_expected, a, b)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> ToTrace<'tcx> for ty::PolyExistentialProjection<'tcx> {
|
||||
fn to_trace(
|
||||
cause: &ObligationCause<'tcx>,
|
||||
a_is_expected: bool,
|
||||
a: Self,
|
||||
b: Self,
|
||||
) -> TypeTrace<'tcx> {
|
||||
TypeTrace {
|
||||
cause: cause.clone(),
|
||||
values: ExistentialProjection(ExpectedFound::new(a_is_expected, a, b)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1635,6 +1635,12 @@ enum Mismatch<'a> {
|
||||
(false, Mismatch::Fixed(self.tcx.def_descr(expected.def_id)))
|
||||
}
|
||||
ValuePairs::Regions(_) => (false, Mismatch::Fixed("lifetime")),
|
||||
ValuePairs::ExistentialTraitRef(_) => {
|
||||
(false, Mismatch::Fixed("existential trait ref"))
|
||||
}
|
||||
ValuePairs::ExistentialProjection(_) => {
|
||||
(false, Mismatch::Fixed("existential projection"))
|
||||
}
|
||||
};
|
||||
let Some(vals) = self.values_str(values) else {
|
||||
// Derived error. Cancel the emitter.
|
||||
@ -2139,6 +2145,8 @@ fn values_str(
|
||||
infer::Regions(exp_found) => self.expected_found_str(exp_found),
|
||||
infer::Terms(exp_found) => self.expected_found_str_term(exp_found),
|
||||
infer::Aliases(exp_found) => self.expected_found_str(exp_found),
|
||||
infer::ExistentialTraitRef(exp_found) => self.expected_found_str(exp_found),
|
||||
infer::ExistentialProjection(exp_found) => self.expected_found_str(exp_found),
|
||||
infer::TraitRefs(exp_found) => {
|
||||
let pretty_exp_found = ty::error::ExpectedFound {
|
||||
expected: exp_found.expected.print_only_trait_path(),
|
||||
|
@ -374,6 +374,8 @@ pub enum ValuePairs<'tcx> {
|
||||
TraitRefs(ExpectedFound<ty::TraitRef<'tcx>>),
|
||||
PolyTraitRefs(ExpectedFound<ty::PolyTraitRef<'tcx>>),
|
||||
Sigs(ExpectedFound<ty::FnSig<'tcx>>),
|
||||
ExistentialTraitRef(ExpectedFound<ty::PolyExistentialTraitRef<'tcx>>),
|
||||
ExistentialProjection(ExpectedFound<ty::PolyExistentialProjection<'tcx>>),
|
||||
}
|
||||
|
||||
impl<'tcx> ValuePairs<'tcx> {
|
||||
|
@ -73,12 +73,15 @@ pub struct GoalCandidate<'tcx> {
|
||||
pub enum CandidateKind<'tcx> {
|
||||
/// Probe entered when normalizing the self ty during candidate assembly
|
||||
NormalizedSelfTyAssembly,
|
||||
DynUpcastingAssembly,
|
||||
/// A normal candidate for proving a goal
|
||||
Candidate {
|
||||
name: String,
|
||||
result: QueryResult<'tcx>,
|
||||
},
|
||||
Candidate { name: String, result: QueryResult<'tcx> },
|
||||
/// Used in the probe that wraps normalizing the non-self type for the unsize
|
||||
/// trait, which is also structurally matched on.
|
||||
UnsizeAssembly,
|
||||
/// During upcasting from some source object to target object type, used to
|
||||
/// do a probe to find out what projection type(s) may be used to prove that
|
||||
/// the source type upholds all of the target type's object bounds.
|
||||
UpcastProbe,
|
||||
}
|
||||
impl Debug for GoalCandidate<'_> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
|
@ -100,8 +100,11 @@ pub(super) fn format_candidate(&mut self, candidate: &GoalCandidate<'_>) -> std:
|
||||
CandidateKind::NormalizedSelfTyAssembly => {
|
||||
writeln!(self.f, "NORMALIZING SELF TY FOR ASSEMBLY:")
|
||||
}
|
||||
CandidateKind::DynUpcastingAssembly => {
|
||||
writeln!(self.f, "ASSEMBLING CANDIDATES FOR DYN UPCASTING:")
|
||||
CandidateKind::UnsizeAssembly => {
|
||||
writeln!(self.f, "ASSEMBLING CANDIDATES FOR UNSIZING:")
|
||||
}
|
||||
CandidateKind::UpcastProbe => {
|
||||
writeln!(self.f, "PROBING FOR PROJECTION COMPATIBILITY FOR UPCASTING:")
|
||||
}
|
||||
CandidateKind::Candidate { name, result } => {
|
||||
writeln!(self.f, "CANDIDATE {name}: {result:?}")
|
||||
|
@ -2734,8 +2734,9 @@ pub struct PrintClosureAsImpl<'tcx> {
|
||||
// HACK(eddyb) these are exhaustive instead of generic,
|
||||
// because `for<'tcx>` isn't possible yet.
|
||||
ty::PolyExistentialPredicate<'tcx>,
|
||||
ty::PolyExistentialProjection<'tcx>,
|
||||
ty::PolyExistentialTraitRef<'tcx>,
|
||||
ty::Binder<'tcx, ty::TraitRef<'tcx>>,
|
||||
ty::Binder<'tcx, ty::ExistentialTraitRef<'tcx>>,
|
||||
ty::Binder<'tcx, TraitRefPrintOnlyTraitPath<'tcx>>,
|
||||
ty::Binder<'tcx, TraitRefPrintOnlyTraitName<'tcx>>,
|
||||
ty::Binder<'tcx, ty::FnSig<'tcx>>,
|
||||
|
@ -444,7 +444,7 @@ fn consider_builtin_unsize_candidates(
|
||||
Err(NoSolution) => vec![],
|
||||
};
|
||||
|
||||
ecx.probe(|_| CandidateKind::DynUpcastingAssembly).enter(|ecx| {
|
||||
ecx.probe(|_| CandidateKind::UnsizeAssembly).enter(|ecx| {
|
||||
let a_ty = goal.predicate.self_ty();
|
||||
// We need to normalize the b_ty since it's matched structurally
|
||||
// in the other functions below.
|
||||
@ -526,7 +526,7 @@ fn consider_builtin_dyn_upcast_candidates(
|
||||
b_region: ty::Region<'tcx>,
|
||||
) -> Vec<(CanonicalResponse<'tcx>, BuiltinImplSource)> {
|
||||
let tcx = self.tcx();
|
||||
let Goal { predicate: (a_ty, b_ty), .. } = goal;
|
||||
let Goal { predicate: (a_ty, _b_ty), .. } = goal;
|
||||
|
||||
// All of a's auto traits need to be in b's auto traits.
|
||||
let auto_traits_compatible =
|
||||
@ -535,51 +535,30 @@ fn consider_builtin_dyn_upcast_candidates(
|
||||
return vec![];
|
||||
}
|
||||
|
||||
// Try to match `a_ty` against `b_ty`, replacing `a_ty`'s principal trait ref with
|
||||
// the supertrait principal and subtyping the types.
|
||||
let unsize_dyn_to_principal =
|
||||
|ecx: &mut Self, principal: Option<ty::PolyExistentialTraitRef<'tcx>>| {
|
||||
ecx.probe_candidate("upcast dyn to principle").enter(
|
||||
|ecx| -> Result<_, NoSolution> {
|
||||
// Require that all of the trait predicates from A match B, except for
|
||||
// the auto traits. We do this by constructing a new A type with B's
|
||||
// auto traits, and equating these types.
|
||||
let new_a_data = principal
|
||||
.into_iter()
|
||||
.map(|trait_ref| trait_ref.map_bound(ty::ExistentialPredicate::Trait))
|
||||
.chain(a_data.iter().filter(|a| {
|
||||
matches!(a.skip_binder(), ty::ExistentialPredicate::Projection(_))
|
||||
}))
|
||||
.chain(
|
||||
b_data
|
||||
.auto_traits()
|
||||
.map(ty::ExistentialPredicate::AutoTrait)
|
||||
.map(ty::Binder::dummy),
|
||||
);
|
||||
let new_a_data = tcx.mk_poly_existential_predicates_from_iter(new_a_data);
|
||||
let new_a_ty = Ty::new_dynamic(tcx, new_a_data, b_region, ty::Dyn);
|
||||
|
||||
// We also require that A's lifetime outlives B's lifetime.
|
||||
ecx.eq(goal.param_env, new_a_ty, b_ty)?;
|
||||
ecx.add_goal(goal.with(tcx, ty::OutlivesPredicate(a_region, b_region)));
|
||||
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
|
||||
},
|
||||
)
|
||||
};
|
||||
|
||||
let mut responses = vec![];
|
||||
// If the principal def ids match (or are both none), then we're not doing
|
||||
// trait upcasting. We're just removing auto traits (or shortening the lifetime).
|
||||
if a_data.principal_def_id() == b_data.principal_def_id() {
|
||||
if let Ok(resp) = unsize_dyn_to_principal(self, a_data.principal()) {
|
||||
if let Ok(resp) = self.consider_builtin_upcast_to_principal(
|
||||
goal,
|
||||
a_data,
|
||||
a_region,
|
||||
b_data,
|
||||
b_region,
|
||||
a_data.principal(),
|
||||
) {
|
||||
responses.push((resp, BuiltinImplSource::Misc));
|
||||
}
|
||||
} else if let Some(a_principal) = a_data.principal() {
|
||||
self.walk_vtable(
|
||||
a_principal.with_self_ty(tcx, a_ty),
|
||||
|ecx, new_a_principal, _, vtable_vptr_slot| {
|
||||
if let Ok(resp) = unsize_dyn_to_principal(
|
||||
ecx,
|
||||
if let Ok(resp) = ecx.consider_builtin_upcast_to_principal(
|
||||
goal,
|
||||
a_data,
|
||||
a_region,
|
||||
b_data,
|
||||
b_region,
|
||||
Some(new_a_principal.map_bound(|trait_ref| {
|
||||
ty::ExistentialTraitRef::erase_self_ty(tcx, trait_ref)
|
||||
})),
|
||||
@ -631,6 +610,83 @@ fn consider_builtin_unsize_to_dyn(
|
||||
self.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
|
||||
}
|
||||
|
||||
fn consider_builtin_upcast_to_principal(
|
||||
&mut self,
|
||||
goal: Goal<'tcx, (Ty<'tcx>, Ty<'tcx>)>,
|
||||
a_data: &'tcx ty::List<ty::PolyExistentialPredicate<'tcx>>,
|
||||
a_region: ty::Region<'tcx>,
|
||||
b_data: &'tcx ty::List<ty::PolyExistentialPredicate<'tcx>>,
|
||||
b_region: ty::Region<'tcx>,
|
||||
upcast_principal: Option<ty::PolyExistentialTraitRef<'tcx>>,
|
||||
) -> QueryResult<'tcx> {
|
||||
let param_env = goal.param_env;
|
||||
|
||||
// More than one projection in a_ty's bounds may match the projection
|
||||
// in b_ty's bound. Use this to first determine *which* apply without
|
||||
// having any inference side-effects. We process obligations because
|
||||
// unification may initially succeed due to deferred projection equality.
|
||||
let projection_may_match =
|
||||
|ecx: &mut Self,
|
||||
source_projection: ty::PolyExistentialProjection<'tcx>,
|
||||
target_projection: ty::PolyExistentialProjection<'tcx>| {
|
||||
source_projection.item_def_id() == target_projection.item_def_id()
|
||||
&& ecx
|
||||
.probe(|_| CandidateKind::UpcastProbe)
|
||||
.enter(|ecx| -> Result<(), NoSolution> {
|
||||
ecx.eq(param_env, source_projection, target_projection)?;
|
||||
let _ = ecx.try_evaluate_added_goals()?;
|
||||
Ok(())
|
||||
})
|
||||
.is_ok()
|
||||
};
|
||||
|
||||
for bound in b_data {
|
||||
match bound.skip_binder() {
|
||||
// Check that a's supertrait (upcast_principal) is compatible
|
||||
// with the target (b_ty).
|
||||
ty::ExistentialPredicate::Trait(target_principal) => {
|
||||
self.eq(param_env, upcast_principal.unwrap(), bound.rebind(target_principal))?;
|
||||
}
|
||||
// Check that b_ty's projection is satisfied by exactly one of
|
||||
// a_ty's projections. First, we look through the list to see if
|
||||
// any match. If not, error. Then, if *more* than one matches, we
|
||||
// return ambiguity. Otherwise, if exactly one matches, equate
|
||||
// it with b_ty's projection.
|
||||
ty::ExistentialPredicate::Projection(target_projection) => {
|
||||
let target_projection = bound.rebind(target_projection);
|
||||
let mut matching_projections =
|
||||
a_data.projection_bounds().filter(|source_projection| {
|
||||
projection_may_match(self, *source_projection, target_projection)
|
||||
});
|
||||
let Some(source_projection) = matching_projections.next() else {
|
||||
return Err(NoSolution);
|
||||
};
|
||||
if matching_projections.next().is_some() {
|
||||
return self.evaluate_added_goals_and_make_canonical_response(
|
||||
Certainty::AMBIGUOUS,
|
||||
);
|
||||
}
|
||||
self.eq(param_env, source_projection, target_projection)?;
|
||||
}
|
||||
// Check that b_ty's auto traits are present in a_ty's bounds.
|
||||
ty::ExistentialPredicate::AutoTrait(def_id) => {
|
||||
if !a_data.auto_traits().any(|source_def_id| source_def_id == def_id) {
|
||||
return Err(NoSolution);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Also require that a_ty's lifetime outlives b_ty's lifetime.
|
||||
self.add_goal(Goal::new(
|
||||
self.tcx(),
|
||||
param_env,
|
||||
ty::Binder::dummy(ty::OutlivesPredicate(a_region, b_region)),
|
||||
));
|
||||
|
||||
self.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
|
||||
}
|
||||
|
||||
/// We have the following builtin impls for arrays:
|
||||
/// ```ignore (builtin impl example)
|
||||
/// impl<T: ?Sized, const N: usize> Unsize<[T]> for [T; N] {}
|
||||
|
@ -745,7 +745,10 @@ fn assemble_candidates_for_unsizing(
|
||||
|
||||
match (source.kind(), target.kind()) {
|
||||
// Trait+Kx+'a -> Trait+Ky+'b (upcasts).
|
||||
(&ty::Dynamic(ref data_a, _, ty::Dyn), &ty::Dynamic(ref data_b, _, ty::Dyn)) => {
|
||||
(
|
||||
&ty::Dynamic(ref a_data, a_region, ty::Dyn),
|
||||
&ty::Dynamic(ref b_data, b_region, ty::Dyn),
|
||||
) => {
|
||||
// Upcast coercions permit several things:
|
||||
//
|
||||
// 1. Dropping auto traits, e.g., `Foo + Send` to `Foo`
|
||||
@ -757,19 +760,19 @@ fn assemble_candidates_for_unsizing(
|
||||
//
|
||||
// We always perform upcasting coercions when we can because of reason
|
||||
// #2 (region bounds).
|
||||
let auto_traits_compatible = data_b
|
||||
let auto_traits_compatible = b_data
|
||||
.auto_traits()
|
||||
// All of a's auto traits need to be in b's auto traits.
|
||||
.all(|b| data_a.auto_traits().any(|a| a == b));
|
||||
.all(|b| a_data.auto_traits().any(|a| a == b));
|
||||
if auto_traits_compatible {
|
||||
let principal_def_id_a = data_a.principal_def_id();
|
||||
let principal_def_id_b = data_b.principal_def_id();
|
||||
let principal_def_id_a = a_data.principal_def_id();
|
||||
let principal_def_id_b = b_data.principal_def_id();
|
||||
if principal_def_id_a == principal_def_id_b {
|
||||
// no cyclic
|
||||
candidates.vec.push(BuiltinUnsizeCandidate);
|
||||
} else if principal_def_id_a.is_some() && principal_def_id_b.is_some() {
|
||||
// not casual unsizing, now check whether this is trait upcasting coercion.
|
||||
let principal_a = data_a.principal().unwrap();
|
||||
let principal_a = a_data.principal().unwrap();
|
||||
let target_trait_did = principal_def_id_b.unwrap();
|
||||
let source_trait_ref = principal_a.with_self_ty(self.tcx(), source);
|
||||
if let Some(deref_trait_ref) = self.need_migrate_deref_output_trait_object(
|
||||
@ -785,9 +788,23 @@ fn assemble_candidates_for_unsizing(
|
||||
for (idx, upcast_trait_ref) in
|
||||
util::supertraits(self.tcx(), source_trait_ref).enumerate()
|
||||
{
|
||||
if upcast_trait_ref.def_id() == target_trait_did {
|
||||
candidates.vec.push(TraitUpcastingUnsizeCandidate(idx));
|
||||
}
|
||||
self.infcx.probe(|_| {
|
||||
if upcast_trait_ref.def_id() == target_trait_did
|
||||
&& let Ok(nested) = self.match_upcast_principal(
|
||||
obligation,
|
||||
upcast_trait_ref,
|
||||
a_data,
|
||||
b_data,
|
||||
a_region,
|
||||
b_region,
|
||||
)
|
||||
{
|
||||
if nested.is_none() {
|
||||
candidates.ambiguous = true;
|
||||
}
|
||||
candidates.vec.push(TraitUpcastingUnsizeCandidate(idx));
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -879,68 +879,27 @@ fn confirm_trait_upcasting_unsize_candidate(
|
||||
|
||||
// `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.args.type_at(1);
|
||||
let target = self.infcx.shallow_resolve(target);
|
||||
let predicate = obligation.predicate.no_bound_vars().unwrap();
|
||||
let a_ty = self.infcx.shallow_resolve(predicate.self_ty());
|
||||
let b_ty = self.infcx.shallow_resolve(predicate.trait_ref.args.type_at(1));
|
||||
|
||||
debug!(?source, ?target, "confirm_trait_upcasting_unsize_candidate");
|
||||
let ty::Dynamic(a_data, a_region, ty::Dyn) = *a_ty.kind() else { bug!() };
|
||||
let ty::Dynamic(b_data, b_region, ty::Dyn) = *b_ty.kind() else { bug!() };
|
||||
|
||||
let mut nested = vec![];
|
||||
let source_trait_ref;
|
||||
let upcast_trait_ref;
|
||||
match (source.kind(), target.kind()) {
|
||||
// TraitA+Kx+'a -> TraitB+Ky+'b (trait upcasting coercion).
|
||||
(
|
||||
&ty::Dynamic(ref data_a, r_a, repr_a @ ty::Dyn),
|
||||
&ty::Dynamic(ref data_b, r_b, ty::Dyn),
|
||||
) => {
|
||||
// See `assemble_candidates_for_unsizing` for more info.
|
||||
// We already checked the compatibility of auto traits within `assemble_candidates_for_unsizing`.
|
||||
let principal_a = data_a.principal().unwrap();
|
||||
source_trait_ref = principal_a.with_self_ty(tcx, source);
|
||||
upcast_trait_ref = util::supertraits(tcx, source_trait_ref).nth(idx).unwrap();
|
||||
assert_eq!(data_b.principal_def_id(), Some(upcast_trait_ref.def_id()));
|
||||
let existential_predicate = upcast_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_from_iter(iter);
|
||||
let source_trait = Ty::new_dynamic(tcx, existential_predicates, r_b, repr_a);
|
||||
let source_principal = a_data.principal().unwrap().with_self_ty(tcx, a_ty);
|
||||
let unnormalized_upcast_principal =
|
||||
util::supertraits(tcx, source_principal).nth(idx).unwrap();
|
||||
|
||||
// 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(DefineOpaqueTypes::No, target, source_trait)
|
||||
.map_err(|_| Unimplemented)?;
|
||||
nested.extend(obligations);
|
||||
|
||||
let outlives = ty::OutlivesPredicate(r_a, r_b);
|
||||
nested.push(Obligation::with_depth(
|
||||
tcx,
|
||||
obligation.cause.clone(),
|
||||
obligation.recursion_depth + 1,
|
||||
obligation.param_env,
|
||||
obligation.predicate.rebind(outlives),
|
||||
));
|
||||
}
|
||||
_ => bug!(),
|
||||
};
|
||||
let nested = self
|
||||
.match_upcast_principal(
|
||||
obligation,
|
||||
unnormalized_upcast_principal,
|
||||
a_data,
|
||||
b_data,
|
||||
a_region,
|
||||
b_region,
|
||||
)?
|
||||
.expect("did not expect ambiguity during confirmation");
|
||||
|
||||
let vtable_segment_callback = {
|
||||
let mut vptr_offset = 0;
|
||||
@ -951,7 +910,7 @@ fn confirm_trait_upcasting_unsize_candidate(
|
||||
}
|
||||
VtblSegment::TraitOwnEntries { trait_ref, emit_vptr } => {
|
||||
vptr_offset += count_own_vtable_entries(tcx, trait_ref);
|
||||
if trait_ref == upcast_trait_ref {
|
||||
if trait_ref == unnormalized_upcast_principal {
|
||||
if emit_vptr {
|
||||
return ControlFlow::Break(Some(vptr_offset));
|
||||
} else {
|
||||
@ -969,7 +928,7 @@ fn confirm_trait_upcasting_unsize_candidate(
|
||||
};
|
||||
|
||||
let vtable_vptr_slot =
|
||||
prepare_vtable_segments(tcx, source_trait_ref, vtable_segment_callback).unwrap();
|
||||
prepare_vtable_segments(tcx, source_principal, vtable_segment_callback).unwrap();
|
||||
|
||||
Ok(ImplSource::Builtin(BuiltinImplSource::TraitUpcasting { vtable_vptr_slot }, nested))
|
||||
}
|
||||
|
@ -2477,6 +2477,98 @@ fn match_impl(
|
||||
Ok(Normalized { value: impl_args, obligations: nested_obligations })
|
||||
}
|
||||
|
||||
fn match_upcast_principal(
|
||||
&mut self,
|
||||
obligation: &PolyTraitObligation<'tcx>,
|
||||
unnormalized_upcast_principal: ty::PolyTraitRef<'tcx>,
|
||||
a_data: &'tcx ty::List<ty::PolyExistentialPredicate<'tcx>>,
|
||||
b_data: &'tcx ty::List<ty::PolyExistentialPredicate<'tcx>>,
|
||||
a_region: ty::Region<'tcx>,
|
||||
b_region: ty::Region<'tcx>,
|
||||
) -> SelectionResult<'tcx, Vec<PredicateObligation<'tcx>>> {
|
||||
let tcx = self.tcx();
|
||||
let mut nested = vec![];
|
||||
|
||||
let upcast_principal = normalize_with_depth_to(
|
||||
self,
|
||||
obligation.param_env,
|
||||
obligation.cause.clone(),
|
||||
obligation.recursion_depth + 1,
|
||||
unnormalized_upcast_principal,
|
||||
&mut nested,
|
||||
);
|
||||
|
||||
for bound in b_data {
|
||||
match bound.skip_binder() {
|
||||
// Check that a_ty's supertrait (upcast_principal) is compatible
|
||||
// with the target (b_ty).
|
||||
ty::ExistentialPredicate::Trait(target_principal) => {
|
||||
nested.extend(
|
||||
self.infcx
|
||||
.at(&obligation.cause, obligation.param_env)
|
||||
.sup(
|
||||
DefineOpaqueTypes::No,
|
||||
upcast_principal.map_bound(|trait_ref| {
|
||||
ty::ExistentialTraitRef::erase_self_ty(tcx, trait_ref)
|
||||
}),
|
||||
bound.rebind(target_principal),
|
||||
)
|
||||
.map_err(|_| SelectionError::Unimplemented)?
|
||||
.into_obligations(),
|
||||
);
|
||||
}
|
||||
// Check that b_ty's projection is satisfied by exactly one of
|
||||
// a_ty's projections. First, we look through the list to see if
|
||||
// any match. If not, error. Then, if *more* than one matches, we
|
||||
// return ambiguity. Otherwise, if exactly one matches, equate
|
||||
// it with b_ty's projection.
|
||||
ty::ExistentialPredicate::Projection(target_projection) => {
|
||||
let target_projection = bound.rebind(target_projection);
|
||||
let mut matching_projections =
|
||||
a_data.projection_bounds().filter(|source_projection| {
|
||||
// Eager normalization means that we can just use can_eq
|
||||
// here instead of equating and processing obligations.
|
||||
source_projection.item_def_id() == target_projection.item_def_id()
|
||||
&& self.infcx.can_eq(
|
||||
obligation.param_env,
|
||||
*source_projection,
|
||||
target_projection,
|
||||
)
|
||||
});
|
||||
let Some(source_projection) = matching_projections.next() else {
|
||||
return Err(SelectionError::Unimplemented);
|
||||
};
|
||||
if matching_projections.next().is_some() {
|
||||
return Ok(None);
|
||||
}
|
||||
nested.extend(
|
||||
self.infcx
|
||||
.at(&obligation.cause, obligation.param_env)
|
||||
.sup(DefineOpaqueTypes::No, source_projection, target_projection)
|
||||
.map_err(|_| SelectionError::Unimplemented)?
|
||||
.into_obligations(),
|
||||
);
|
||||
}
|
||||
// Check that b_ty's auto traits are present in a_ty's bounds.
|
||||
ty::ExistentialPredicate::AutoTrait(def_id) => {
|
||||
if !a_data.auto_traits().any(|source_def_id| source_def_id == def_id) {
|
||||
return Err(SelectionError::Unimplemented);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nested.push(Obligation::with_depth(
|
||||
tcx,
|
||||
obligation.cause.clone(),
|
||||
obligation.recursion_depth + 1,
|
||||
obligation.param_env,
|
||||
ty::Binder::dummy(ty::OutlivesPredicate(a_region, b_region)),
|
||||
));
|
||||
|
||||
Ok(Some(nested))
|
||||
}
|
||||
|
||||
/// Normalize `where_clause_trait_ref` and try to match it against
|
||||
/// `obligation`. If successful, return any predicates that
|
||||
/// result from the normalization.
|
||||
|
25
tests/ui/traits/trait-upcasting/fewer-associated.rs
Normal file
25
tests/ui/traits/trait-upcasting/fewer-associated.rs
Normal file
@ -0,0 +1,25 @@
|
||||
// check-pass
|
||||
// issue: 114035
|
||||
// revisions: current next
|
||||
//[next] compile-flags: -Ztrait-solver=next
|
||||
|
||||
#![feature(trait_upcasting)]
|
||||
|
||||
trait A: B {
|
||||
type Assoc;
|
||||
}
|
||||
|
||||
trait B {}
|
||||
|
||||
fn upcast(a: &dyn A<Assoc = i32>) -> &dyn B {
|
||||
a
|
||||
}
|
||||
|
||||
// Make sure that we can drop the existential projection `A::Assoc = i32`
|
||||
// when upcasting `dyn A<Assoc = i32>` to `dyn B`. Before, we used some
|
||||
// complicated algorithm which required rebuilding a new object type with
|
||||
// different bounds in order to test that an upcast was valid, but this
|
||||
// didn't allow upcasting to t that have fewer associated types
|
||||
// than the source type.
|
||||
|
||||
fn main() {}
|
@ -0,0 +1,14 @@
|
||||
error[E0308]: mismatched types
|
||||
--> $DIR/illegal-upcast-from-impl.rs:16:66
|
||||
|
|
||||
LL | fn illegal(x: &dyn Sub<Assoc = ()>) -> &dyn Super<Assoc = i32> { x }
|
||||
| ----------------------- ^ expected trait `Super`, found trait `Sub`
|
||||
| |
|
||||
| expected `&dyn Super<Assoc = i32>` because of return type
|
||||
|
|
||||
= note: expected reference `&dyn Super<Assoc = i32>`
|
||||
found reference `&dyn Sub<Assoc = ()>`
|
||||
|
||||
error: aborting due to previous error
|
||||
|
||||
For more information about this error, try `rustc --explain E0308`.
|
@ -0,0 +1,14 @@
|
||||
error[E0308]: mismatched types
|
||||
--> $DIR/illegal-upcast-from-impl.rs:16:66
|
||||
|
|
||||
LL | fn illegal(x: &dyn Sub<Assoc = ()>) -> &dyn Super<Assoc = i32> { x }
|
||||
| ----------------------- ^ expected trait `Super`, found trait `Sub`
|
||||
| |
|
||||
| expected `&dyn Super<Assoc = i32>` because of return type
|
||||
|
|
||||
= note: expected reference `&dyn Super<Assoc = i32>`
|
||||
found reference `&dyn Sub<Assoc = ()>`
|
||||
|
||||
error: aborting due to previous error
|
||||
|
||||
For more information about this error, try `rustc --explain E0308`.
|
23
tests/ui/traits/trait-upcasting/illegal-upcast-from-impl.rs
Normal file
23
tests/ui/traits/trait-upcasting/illegal-upcast-from-impl.rs
Normal file
@ -0,0 +1,23 @@
|
||||
// revisions: current next
|
||||
//[next] compile-flags: -Ztrait-solver=next
|
||||
|
||||
#![feature(trait_upcasting)]
|
||||
|
||||
trait Super {
|
||||
type Assoc;
|
||||
}
|
||||
|
||||
trait Sub: Super {}
|
||||
|
||||
impl<T: ?Sized> Super for T {
|
||||
type Assoc = i32;
|
||||
}
|
||||
|
||||
fn illegal(x: &dyn Sub<Assoc = ()>) -> &dyn Super<Assoc = i32> { x }
|
||||
//~^ ERROR mismatched types
|
||||
|
||||
// Want to make sure that we can't "upcast" to a supertrait that has a different
|
||||
// associated type that is instead provided by a blanket impl (and doesn't come
|
||||
// from the object bounds).
|
||||
|
||||
fn main() {}
|
20
tests/ui/traits/trait-upcasting/normalization.rs
Normal file
20
tests/ui/traits/trait-upcasting/normalization.rs
Normal file
@ -0,0 +1,20 @@
|
||||
// check-pass
|
||||
// issue: 114113
|
||||
// revisions: current next
|
||||
//[next] compile-flags: -Ztrait-solver=next
|
||||
|
||||
#![feature(trait_upcasting)]
|
||||
|
||||
trait Mirror {
|
||||
type Assoc;
|
||||
}
|
||||
impl<T> Mirror for T {
|
||||
type Assoc = T;
|
||||
}
|
||||
|
||||
trait Bar<T> {}
|
||||
trait Foo<T>: Bar<<T as Mirror>::Assoc> {}
|
||||
|
||||
fn upcast<T>(x: &dyn Foo<T>) -> &dyn Bar<T> { x }
|
||||
|
||||
fn main() {}
|
Loading…
Reference in New Issue
Block a user