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:
bors
2023-08-04 10:55:22 +00:00
15 changed files with 380 additions and 115 deletions

View File

@@ -879,68 +879,27 @@ impl<'cx, 'tcx> SelectionContext<'cx, '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.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 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
}
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 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
};
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))
}