Skip to content

Commit 4f7bb98

Browse files
committed
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. https://github.com/rust-lang/rust/blob/fdcab310b2a57a4e9cc0b2629abd27afda49cd80/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. https://github.com/rust-lang/rust/blob/fdcab310b2a57a4e9cc0b2629abd27afda49cd80/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. https://github.com/rust-lang/rust/blob/fdcab310b2a57a4e9cc0b2629abd27afda49cd80/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: https://github.com/rust-lang/rust/blob/d12c6e947ceacf3b22c154caf9532b390d8dc88a/compiler/rustc_trait_selection/src/traits/select/confirmation.rs#L915-L929 2. Then equate it with the target trait object: https://github.com/rust-lang/rust/blob/d12c6e947ceacf3b22c154caf9532b390d8dc88a/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: https://github.com/rust-lang/rust/blob/d12c6e947ceacf3b22c154caf9532b390d8dc88a/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
2 parents 34ccd04 + 7c942cc commit 4f7bb98

File tree

15 files changed

+383
-118
lines changed

15 files changed

+383
-118
lines changed

compiler/rustc_infer/src/infer/at.rs

+28
Original file line numberDiff line numberDiff line change
@@ -481,3 +481,31 @@ impl<'tcx> ToTrace<'tcx> for ty::FnSig<'tcx> {
481481
TypeTrace { cause: cause.clone(), values: Sigs(ExpectedFound::new(a_is_expected, a, b)) }
482482
}
483483
}
484+
485+
impl<'tcx> ToTrace<'tcx> for ty::PolyExistentialTraitRef<'tcx> {
486+
fn to_trace(
487+
cause: &ObligationCause<'tcx>,
488+
a_is_expected: bool,
489+
a: Self,
490+
b: Self,
491+
) -> TypeTrace<'tcx> {
492+
TypeTrace {
493+
cause: cause.clone(),
494+
values: ExistentialTraitRef(ExpectedFound::new(a_is_expected, a, b)),
495+
}
496+
}
497+
}
498+
499+
impl<'tcx> ToTrace<'tcx> for ty::PolyExistentialProjection<'tcx> {
500+
fn to_trace(
501+
cause: &ObligationCause<'tcx>,
502+
a_is_expected: bool,
503+
a: Self,
504+
b: Self,
505+
) -> TypeTrace<'tcx> {
506+
TypeTrace {
507+
cause: cause.clone(),
508+
values: ExistentialProjection(ExpectedFound::new(a_is_expected, a, b)),
509+
}
510+
}
511+
}

compiler/rustc_infer/src/infer/error_reporting/mod.rs

+8
Original file line numberDiff line numberDiff line change
@@ -1635,6 +1635,12 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
16351635
(false, Mismatch::Fixed(self.tcx.def_descr(expected.def_id)))
16361636
}
16371637
ValuePairs::Regions(_) => (false, Mismatch::Fixed("lifetime")),
1638+
ValuePairs::ExistentialTraitRef(_) => {
1639+
(false, Mismatch::Fixed("existential trait ref"))
1640+
}
1641+
ValuePairs::ExistentialProjection(_) => {
1642+
(false, Mismatch::Fixed("existential projection"))
1643+
}
16381644
};
16391645
let Some(vals) = self.values_str(values) else {
16401646
// Derived error. Cancel the emitter.
@@ -2139,6 +2145,8 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
21392145
infer::Regions(exp_found) => self.expected_found_str(exp_found),
21402146
infer::Terms(exp_found) => self.expected_found_str_term(exp_found),
21412147
infer::Aliases(exp_found) => self.expected_found_str(exp_found),
2148+
infer::ExistentialTraitRef(exp_found) => self.expected_found_str(exp_found),
2149+
infer::ExistentialProjection(exp_found) => self.expected_found_str(exp_found),
21422150
infer::TraitRefs(exp_found) => {
21432151
let pretty_exp_found = ty::error::ExpectedFound {
21442152
expected: exp_found.expected.print_only_trait_path(),

compiler/rustc_infer/src/infer/mod.rs

+2
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,8 @@ pub enum ValuePairs<'tcx> {
374374
TraitRefs(ExpectedFound<ty::TraitRef<'tcx>>),
375375
PolyTraitRefs(ExpectedFound<ty::PolyTraitRef<'tcx>>),
376376
Sigs(ExpectedFound<ty::FnSig<'tcx>>),
377+
ExistentialTraitRef(ExpectedFound<ty::PolyExistentialTraitRef<'tcx>>),
378+
ExistentialProjection(ExpectedFound<ty::PolyExistentialProjection<'tcx>>),
377379
}
378380

379381
impl<'tcx> ValuePairs<'tcx> {

compiler/rustc_middle/src/traits/solve/inspect.rs

+8-5
Original file line numberDiff line numberDiff line change
@@ -73,12 +73,15 @@ pub struct GoalCandidate<'tcx> {
7373
pub enum CandidateKind<'tcx> {
7474
/// Probe entered when normalizing the self ty during candidate assembly
7575
NormalizedSelfTyAssembly,
76-
DynUpcastingAssembly,
7776
/// A normal candidate for proving a goal
78-
Candidate {
79-
name: String,
80-
result: QueryResult<'tcx>,
81-
},
77+
Candidate { name: String, result: QueryResult<'tcx> },
78+
/// Used in the probe that wraps normalizing the non-self type for the unsize
79+
/// trait, which is also structurally matched on.
80+
UnsizeAssembly,
81+
/// During upcasting from some source object to target object type, used to
82+
/// do a probe to find out what projection type(s) may be used to prove that
83+
/// the source type upholds all of the target type's object bounds.
84+
UpcastProbe,
8285
}
8386
impl Debug for GoalCandidate<'_> {
8487
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {

compiler/rustc_middle/src/traits/solve/inspect/format.rs

+5-2
Original file line numberDiff line numberDiff line change
@@ -100,8 +100,11 @@ impl<'a, 'b> ProofTreeFormatter<'a, 'b> {
100100
CandidateKind::NormalizedSelfTyAssembly => {
101101
writeln!(self.f, "NORMALIZING SELF TY FOR ASSEMBLY:")
102102
}
103-
CandidateKind::DynUpcastingAssembly => {
104-
writeln!(self.f, "ASSEMBLING CANDIDATES FOR DYN UPCASTING:")
103+
CandidateKind::UnsizeAssembly => {
104+
writeln!(self.f, "ASSEMBLING CANDIDATES FOR UNSIZING:")
105+
}
106+
CandidateKind::UpcastProbe => {
107+
writeln!(self.f, "PROBING FOR PROJECTION COMPATIBILITY FOR UPCASTING:")
105108
}
106109
CandidateKind::Candidate { name, result } => {
107110
writeln!(self.f, "CANDIDATE {name}: {result:?}")

compiler/rustc_middle/src/ty/print/pretty.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -2734,8 +2734,9 @@ forward_display_to_print! {
27342734
// HACK(eddyb) these are exhaustive instead of generic,
27352735
// because `for<'tcx>` isn't possible yet.
27362736
ty::PolyExistentialPredicate<'tcx>,
2737+
ty::PolyExistentialProjection<'tcx>,
2738+
ty::PolyExistentialTraitRef<'tcx>,
27372739
ty::Binder<'tcx, ty::TraitRef<'tcx>>,
2738-
ty::Binder<'tcx, ty::ExistentialTraitRef<'tcx>>,
27392740
ty::Binder<'tcx, TraitRefPrintOnlyTraitPath<'tcx>>,
27402741
ty::Binder<'tcx, TraitRefPrintOnlyTraitName<'tcx>>,
27412742
ty::Binder<'tcx, ty::FnSig<'tcx>>,

compiler/rustc_trait_selection/src/solve/trait_goals.rs

+93-37
Original file line numberDiff line numberDiff line change
@@ -444,7 +444,7 @@ impl<'tcx> assembly::GoalKind<'tcx> for TraitPredicate<'tcx> {
444444
Err(NoSolution) => vec![],
445445
};
446446

447-
ecx.probe(|_| CandidateKind::DynUpcastingAssembly).enter(|ecx| {
447+
ecx.probe(|_| CandidateKind::UnsizeAssembly).enter(|ecx| {
448448
let a_ty = goal.predicate.self_ty();
449449
// We need to normalize the b_ty since it's matched structurally
450450
// in the other functions below.
@@ -526,7 +526,7 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
526526
b_region: ty::Region<'tcx>,
527527
) -> Vec<(CanonicalResponse<'tcx>, BuiltinImplSource)> {
528528
let tcx = self.tcx();
529-
let Goal { predicate: (a_ty, b_ty), .. } = goal;
529+
let Goal { predicate: (a_ty, _b_ty), .. } = goal;
530530

531531
// All of a's auto traits need to be in b's auto traits.
532532
let auto_traits_compatible =
@@ -535,51 +535,30 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
535535
return vec![];
536536
}
537537

538-
// Try to match `a_ty` against `b_ty`, replacing `a_ty`'s principal trait ref with
539-
// the supertrait principal and subtyping the types.
540-
let unsize_dyn_to_principal =
541-
|ecx: &mut Self, principal: Option<ty::PolyExistentialTraitRef<'tcx>>| {
542-
ecx.probe_candidate("upcast dyn to principle").enter(
543-
|ecx| -> Result<_, NoSolution> {
544-
// Require that all of the trait predicates from A match B, except for
545-
// the auto traits. We do this by constructing a new A type with B's
546-
// auto traits, and equating these types.
547-
let new_a_data = principal
548-
.into_iter()
549-
.map(|trait_ref| trait_ref.map_bound(ty::ExistentialPredicate::Trait))
550-
.chain(a_data.iter().filter(|a| {
551-
matches!(a.skip_binder(), ty::ExistentialPredicate::Projection(_))
552-
}))
553-
.chain(
554-
b_data
555-
.auto_traits()
556-
.map(ty::ExistentialPredicate::AutoTrait)
557-
.map(ty::Binder::dummy),
558-
);
559-
let new_a_data = tcx.mk_poly_existential_predicates_from_iter(new_a_data);
560-
let new_a_ty = Ty::new_dynamic(tcx, new_a_data, b_region, ty::Dyn);
561-
562-
// We also require that A's lifetime outlives B's lifetime.
563-
ecx.eq(goal.param_env, new_a_ty, b_ty)?;
564-
ecx.add_goal(goal.with(tcx, ty::OutlivesPredicate(a_region, b_region)));
565-
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
566-
},
567-
)
568-
};
569-
570538
let mut responses = vec![];
571539
// If the principal def ids match (or are both none), then we're not doing
572540
// trait upcasting. We're just removing auto traits (or shortening the lifetime).
573541
if a_data.principal_def_id() == b_data.principal_def_id() {
574-
if let Ok(resp) = unsize_dyn_to_principal(self, a_data.principal()) {
542+
if let Ok(resp) = self.consider_builtin_upcast_to_principal(
543+
goal,
544+
a_data,
545+
a_region,
546+
b_data,
547+
b_region,
548+
a_data.principal(),
549+
) {
575550
responses.push((resp, BuiltinImplSource::Misc));
576551
}
577552
} else if let Some(a_principal) = a_data.principal() {
578553
self.walk_vtable(
579554
a_principal.with_self_ty(tcx, a_ty),
580555
|ecx, new_a_principal, _, vtable_vptr_slot| {
581-
if let Ok(resp) = unsize_dyn_to_principal(
582-
ecx,
556+
if let Ok(resp) = ecx.consider_builtin_upcast_to_principal(
557+
goal,
558+
a_data,
559+
a_region,
560+
b_data,
561+
b_region,
583562
Some(new_a_principal.map_bound(|trait_ref| {
584563
ty::ExistentialTraitRef::erase_self_ty(tcx, trait_ref)
585564
})),
@@ -631,6 +610,83 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
631610
self.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
632611
}
633612

613+
fn consider_builtin_upcast_to_principal(
614+
&mut self,
615+
goal: Goal<'tcx, (Ty<'tcx>, Ty<'tcx>)>,
616+
a_data: &'tcx ty::List<ty::PolyExistentialPredicate<'tcx>>,
617+
a_region: ty::Region<'tcx>,
618+
b_data: &'tcx ty::List<ty::PolyExistentialPredicate<'tcx>>,
619+
b_region: ty::Region<'tcx>,
620+
upcast_principal: Option<ty::PolyExistentialTraitRef<'tcx>>,
621+
) -> QueryResult<'tcx> {
622+
let param_env = goal.param_env;
623+
624+
// More than one projection in a_ty's bounds may match the projection
625+
// in b_ty's bound. Use this to first determine *which* apply without
626+
// having any inference side-effects. We process obligations because
627+
// unification may initially succeed due to deferred projection equality.
628+
let projection_may_match =
629+
|ecx: &mut Self,
630+
source_projection: ty::PolyExistentialProjection<'tcx>,
631+
target_projection: ty::PolyExistentialProjection<'tcx>| {
632+
source_projection.item_def_id() == target_projection.item_def_id()
633+
&& ecx
634+
.probe(|_| CandidateKind::UpcastProbe)
635+
.enter(|ecx| -> Result<(), NoSolution> {
636+
ecx.eq(param_env, source_projection, target_projection)?;
637+
let _ = ecx.try_evaluate_added_goals()?;
638+
Ok(())
639+
})
640+
.is_ok()
641+
};
642+
643+
for bound in b_data {
644+
match bound.skip_binder() {
645+
// Check that a's supertrait (upcast_principal) is compatible
646+
// with the target (b_ty).
647+
ty::ExistentialPredicate::Trait(target_principal) => {
648+
self.eq(param_env, upcast_principal.unwrap(), bound.rebind(target_principal))?;
649+
}
650+
// Check that b_ty's projection is satisfied by exactly one of
651+
// a_ty's projections. First, we look through the list to see if
652+
// any match. If not, error. Then, if *more* than one matches, we
653+
// return ambiguity. Otherwise, if exactly one matches, equate
654+
// it with b_ty's projection.
655+
ty::ExistentialPredicate::Projection(target_projection) => {
656+
let target_projection = bound.rebind(target_projection);
657+
let mut matching_projections =
658+
a_data.projection_bounds().filter(|source_projection| {
659+
projection_may_match(self, *source_projection, target_projection)
660+
});
661+
let Some(source_projection) = matching_projections.next() else {
662+
return Err(NoSolution);
663+
};
664+
if matching_projections.next().is_some() {
665+
return self.evaluate_added_goals_and_make_canonical_response(
666+
Certainty::AMBIGUOUS,
667+
);
668+
}
669+
self.eq(param_env, source_projection, target_projection)?;
670+
}
671+
// Check that b_ty's auto traits are present in a_ty's bounds.
672+
ty::ExistentialPredicate::AutoTrait(def_id) => {
673+
if !a_data.auto_traits().any(|source_def_id| source_def_id == def_id) {
674+
return Err(NoSolution);
675+
}
676+
}
677+
}
678+
}
679+
680+
// Also require that a_ty's lifetime outlives b_ty's lifetime.
681+
self.add_goal(Goal::new(
682+
self.tcx(),
683+
param_env,
684+
ty::Binder::dummy(ty::OutlivesPredicate(a_region, b_region)),
685+
));
686+
687+
self.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
688+
}
689+
634690
/// We have the following builtin impls for arrays:
635691
/// ```ignore (builtin impl example)
636692
/// impl<T: ?Sized, const N: usize> Unsize<[T]> for [T; N] {}

compiler/rustc_trait_selection/src/traits/select/candidate_assembly.rs

+26-9
Original file line numberDiff line numberDiff line change
@@ -745,7 +745,10 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
745745

746746
match (source.kind(), target.kind()) {
747747
// Trait+Kx+'a -> Trait+Ky+'b (upcasts).
748-
(&ty::Dynamic(ref data_a, _, ty::Dyn), &ty::Dynamic(ref data_b, _, ty::Dyn)) => {
748+
(
749+
&ty::Dynamic(ref a_data, a_region, ty::Dyn),
750+
&ty::Dynamic(ref b_data, b_region, ty::Dyn),
751+
) => {
749752
// Upcast coercions permit several things:
750753
//
751754
// 1. Dropping auto traits, e.g., `Foo + Send` to `Foo`
@@ -757,19 +760,19 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
757760
//
758761
// We always perform upcasting coercions when we can because of reason
759762
// #2 (region bounds).
760-
let auto_traits_compatible = data_b
763+
let auto_traits_compatible = b_data
761764
.auto_traits()
762765
// All of a's auto traits need to be in b's auto traits.
763-
.all(|b| data_a.auto_traits().any(|a| a == b));
766+
.all(|b| a_data.auto_traits().any(|a| a == b));
764767
if auto_traits_compatible {
765-
let principal_def_id_a = data_a.principal_def_id();
766-
let principal_def_id_b = data_b.principal_def_id();
768+
let principal_def_id_a = a_data.principal_def_id();
769+
let principal_def_id_b = b_data.principal_def_id();
767770
if principal_def_id_a == principal_def_id_b {
768771
// no cyclic
769772
candidates.vec.push(BuiltinUnsizeCandidate);
770773
} else if principal_def_id_a.is_some() && principal_def_id_b.is_some() {
771774
// not casual unsizing, now check whether this is trait upcasting coercion.
772-
let principal_a = data_a.principal().unwrap();
775+
let principal_a = a_data.principal().unwrap();
773776
let target_trait_did = principal_def_id_b.unwrap();
774777
let source_trait_ref = principal_a.with_self_ty(self.tcx(), source);
775778
if let Some(deref_trait_ref) = self.need_migrate_deref_output_trait_object(
@@ -785,9 +788,23 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
785788
for (idx, upcast_trait_ref) in
786789
util::supertraits(self.tcx(), source_trait_ref).enumerate()
787790
{
788-
if upcast_trait_ref.def_id() == target_trait_did {
789-
candidates.vec.push(TraitUpcastingUnsizeCandidate(idx));
790-
}
791+
self.infcx.probe(|_| {
792+
if upcast_trait_ref.def_id() == target_trait_did
793+
&& let Ok(nested) = self.match_upcast_principal(
794+
obligation,
795+
upcast_trait_ref,
796+
a_data,
797+
b_data,
798+
a_region,
799+
b_region,
800+
)
801+
{
802+
if nested.is_none() {
803+
candidates.ambiguous = true;
804+
}
805+
candidates.vec.push(TraitUpcastingUnsizeCandidate(idx));
806+
}
807+
})
791808
}
792809
}
793810
}

0 commit comments

Comments
 (0)