Skip to content

Commit 1bb6ae5

Browse files
Rework upcasting
1 parent fcf3006 commit 1bb6ae5

File tree

12 files changed

+297
-104
lines changed

12 files changed

+297
-104
lines changed

compiler/rustc_infer/src/infer/at.rs

Lines changed: 28 additions & 0 deletions
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

Lines changed: 8 additions & 0 deletions
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

Lines changed: 2 additions & 0 deletions
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

Lines changed: 8 additions & 5 deletions
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

Lines changed: 5 additions & 2 deletions
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

Lines changed: 2 additions & 1 deletion
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

Lines changed: 88 additions & 37 deletions
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,78 @@ 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 = |ecx: &mut Self, source_projection, target_projection| {
629+
ecx.probe(|_| CandidateKind::UpcastProbe)
630+
.enter(|ecx| -> Result<(), NoSolution> {
631+
ecx.eq(param_env, source_projection, target_projection)?;
632+
let _ = ecx.try_evaluate_added_goals()?;
633+
Ok(())
634+
})
635+
.is_ok()
636+
};
637+
638+
for bound in b_data {
639+
match bound.skip_binder() {
640+
// Check that a's supertrait (upcast_principal) is compatible
641+
// with the target (b_ty).
642+
ty::ExistentialPredicate::Trait(target_principal) => {
643+
self.eq(param_env, upcast_principal.unwrap(), bound.rebind(target_principal))?;
644+
}
645+
// Check that b_ty's projection is satisfied by exactly one of
646+
// a_ty's projections. First, we look through the list to see if
647+
// any match. If not, error. Then, if *more* than one matches, we
648+
// return ambiguity. Otherwise, if exactly one matches, equate
649+
// it with b_ty's projection.
650+
ty::ExistentialPredicate::Projection(target_projection) => {
651+
let target_projection = bound.rebind(target_projection);
652+
let mut matching_projections =
653+
a_data.projection_bounds().filter(|source_projection| {
654+
projection_may_match(self, *source_projection, target_projection)
655+
});
656+
let Some(source_projection) = matching_projections.next() else {
657+
return Err(NoSolution);
658+
};
659+
if matching_projections.next().is_some() {
660+
return self.evaluate_added_goals_and_make_canonical_response(
661+
Certainty::AMBIGUOUS,
662+
);
663+
}
664+
self.eq(param_env, source_projection, target_projection)?;
665+
}
666+
// Check that b_ty's auto traits are present in a_ty's bounds.
667+
ty::ExistentialPredicate::AutoTrait(def_id) => {
668+
if !a_data.auto_traits().any(|source_def_id| source_def_id == def_id) {
669+
return Err(NoSolution);
670+
}
671+
}
672+
}
673+
}
674+
675+
// Also require that a_ty's lifetime outlives b_ty's lifetime.
676+
self.add_goal(Goal::new(
677+
self.tcx(),
678+
param_env,
679+
ty::Binder::dummy(ty::OutlivesPredicate(a_region, b_region)),
680+
));
681+
682+
self.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
683+
}
684+
634685
/// We have the following builtin impls for arrays:
635686
/// ```ignore (builtin impl example)
636687
/// impl<T: ?Sized, const N: usize> Unsize<[T]> for [T; N] {}

0 commit comments

Comments
 (0)