Skip to content

Commit 1e60502

Browse files
committed
Properly generate multiple candidates for trait upcasting coercion.
1 parent adf1688 commit 1e60502

File tree

8 files changed

+155
-159
lines changed

8 files changed

+155
-159
lines changed

compiler/rustc_middle/src/traits/select.rs

+5
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,11 @@ pub enum SelectionCandidate<'tcx> {
135135
/// `rustc_infer::traits::util::supertraits`.
136136
ObjectCandidate(usize),
137137

138+
/// Perform trait upcasting coercion of `dyn Trait` to a supertrait of `Trait`.
139+
/// The index is the position in the iterator returned by
140+
/// `rustc_infer::traits::util::supertraits`.
141+
TraitUpcastingUnsizeCandidate(usize),
142+
138143
BuiltinObjectCandidate,
139144

140145
BuiltinUnsizeCandidate,

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

+47-13
Original file line numberDiff line numberDiff line change
@@ -690,46 +690,80 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
690690

691691
debug!(?source, ?target, "assemble_candidates_for_unsizing");
692692

693-
let may_apply = match (source.kind(), target.kind()) {
693+
match (source.kind(), target.kind()) {
694694
// Trait+Kx+'a -> Trait+Ky+'b (upcasts).
695695
(&ty::Dynamic(ref data_a, ..), &ty::Dynamic(ref data_b, ..)) => {
696-
// See `confirm_builtin_unsize_candidate` for more info.
696+
// Upcast coercions permit several things:
697+
//
698+
// 1. Dropping auto traits, e.g., `Foo + Send` to `Foo`
699+
// 2. Tightening the region bound, e.g., `Foo + 'a` to `Foo + 'b` if `'a: 'b`
700+
// 3. Tightening trait to its super traits, eg. `Foo` to `Bar` if `Foo: Bar`
701+
//
702+
// Note that neither of the first two of these changes requires any
703+
// change at runtime. The third needs to change pointer metadata at runtime.
704+
//
705+
// We always perform upcasting coercions when we can because of reason
706+
// #2 (region bounds).
697707
let auto_traits_compatible = data_b
698708
.auto_traits()
699709
// All of a's auto traits need to be in b's auto traits.
700710
.all(|b| data_a.auto_traits().any(|a| a == b));
701-
auto_traits_compatible
711+
if auto_traits_compatible {
712+
let principal_def_id_a = data_a.principal_def_id();
713+
let principal_def_id_b = data_b.principal_def_id();
714+
if principal_def_id_a == principal_def_id_b {
715+
// no cyclic
716+
candidates.vec.push(BuiltinUnsizeCandidate);
717+
} else if principal_def_id_a.is_some() && principal_def_id_b.is_some() {
718+
// not casual unsizing, now check whether this is trait upcasting coercion.
719+
let principal_a = data_a.principal().unwrap();
720+
let target_trait_did = principal_def_id_b.unwrap();
721+
let source_trait_ref = principal_a.with_self_ty(self.tcx(), source);
722+
for (idx, upcast_trait_ref) in
723+
util::supertraits(self.tcx(), source_trait_ref).enumerate()
724+
{
725+
if upcast_trait_ref.def_id() == target_trait_did {
726+
candidates.vec.push(TraitUpcastingUnsizeCandidate(idx));
727+
}
728+
}
729+
}
730+
}
702731
}
703732

704733
// `T` -> `Trait`
705-
(_, &ty::Dynamic(..)) => true,
734+
(_, &ty::Dynamic(..)) => {
735+
candidates.vec.push(BuiltinUnsizeCandidate);
736+
}
706737

707738
// Ambiguous handling is below `T` -> `Trait`, because inference
708739
// variables can still implement `Unsize<Trait>` and nested
709740
// obligations will have the final say (likely deferred).
710741
(&ty::Infer(ty::TyVar(_)), _) | (_, &ty::Infer(ty::TyVar(_))) => {
711742
debug!("assemble_candidates_for_unsizing: ambiguous");
712743
candidates.ambiguous = true;
713-
false
714744
}
715745

716746
// `[T; n]` -> `[T]`
717-
(&ty::Array(..), &ty::Slice(_)) => true,
747+
(&ty::Array(..), &ty::Slice(_)) => {
748+
candidates.vec.push(BuiltinUnsizeCandidate);
749+
}
718750

719751
// `Struct<T>` -> `Struct<U>`
720752
(&ty::Adt(def_id_a, _), &ty::Adt(def_id_b, _)) if def_id_a.is_struct() => {
721-
def_id_a == def_id_b
753+
if def_id_a == def_id_b {
754+
candidates.vec.push(BuiltinUnsizeCandidate);
755+
}
722756
}
723757

724758
// `(.., T)` -> `(.., U)`
725-
(&ty::Tuple(tys_a), &ty::Tuple(tys_b)) => tys_a.len() == tys_b.len(),
759+
(&ty::Tuple(tys_a), &ty::Tuple(tys_b)) => {
760+
if tys_a.len() == tys_b.len() {
761+
candidates.vec.push(BuiltinUnsizeCandidate);
762+
}
763+
}
726764

727-
_ => false,
765+
_ => {}
728766
};
729-
730-
if may_apply {
731-
candidates.vec.push(BuiltinUnsizeCandidate);
732-
}
733767
}
734768

735769
fn assemble_candidates_for_trait_alias(

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

+85-50
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,11 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
118118
let data = self.confirm_builtin_unsize_candidate(obligation)?;
119119
Ok(ImplSource::Builtin(data))
120120
}
121+
122+
TraitUpcastingUnsizeCandidate(idx) => {
123+
let data = self.confirm_trait_upcasting_unsize_candidate(obligation, idx)?;
124+
Ok(ImplSource::Builtin(data))
125+
}
121126
}
122127
}
123128

@@ -685,9 +690,10 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
685690
.map_err(|e| OutputTypeParameterMismatch(expected_trait_ref, obligation_trait_ref, e))
686691
}
687692

688-
fn confirm_builtin_unsize_candidate(
693+
fn confirm_trait_upcasting_unsize_candidate(
689694
&mut self,
690695
obligation: &TraitObligation<'tcx>,
696+
idx: usize,
691697
) -> Result<ImplSourceBuiltinData<PredicateObligation<'tcx>>, SelectionError<'tcx>> {
692698
let tcx = self.tcx();
693699

@@ -697,62 +703,91 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
697703
let target = obligation.predicate.skip_binder().trait_ref.substs.type_at(1);
698704
let target = self.infcx.shallow_resolve(target);
699705

700-
debug!(?source, ?target, "confirm_builtin_unsize_candidate");
706+
debug!(?source, ?target, "confirm_trait_upcasting_unsize_candidate");
701707

702708
let mut nested = vec![];
703709
match (source.kind(), target.kind()) {
704-
// Trait+Kx+'a -> Trait+Ky+'b (upcasts).
710+
// TraitA+Kx+'a -> TraitB+Ky+'b (trait upcasting coercion).
705711
(&ty::Dynamic(ref data_a, r_a), &ty::Dynamic(ref data_b, r_b)) => {
706-
// Upcast coercions permit several things:
707-
//
708-
// 1. Dropping auto traits, e.g., `Foo + Send` to `Foo`
709-
// 2. Tightening the region bound, e.g., `Foo + 'a` to `Foo + 'b` if `'a: 'b`
710-
// 3. Tightening trait to its super traits, eg. `Foo` to `Bar` if `Foo: Bar`
711-
//
712-
// Note that neither of the first two of these changes requires any
713-
// change at runtime. The third needs to change pointer metadata at runtime.
714-
//
715-
// We always perform upcasting coercions when we can because of reason
716-
// #2 (region bounds).
717-
712+
// See `assemble_candidates_for_unsizing` for more info.
718713
// We already checked the compatiblity of auto traits within `assemble_candidates_for_unsizing`.
714+
let principal_a = data_a.principal().unwrap();
715+
let source_trait_ref = principal_a.with_self_ty(tcx, source);
716+
let target_trait_ref = util::supertraits(tcx, source_trait_ref).nth(idx).unwrap();
717+
assert_eq!(data_b.principal_def_id(), Some(target_trait_ref.def_id()));
718+
let existential_predicate = target_trait_ref.map_bound(|trait_ref| {
719+
ty::ExistentialPredicate::Trait(ty::ExistentialTraitRef::erase_self_ty(
720+
tcx, trait_ref,
721+
))
722+
});
723+
let iter = Some(existential_predicate)
724+
.into_iter()
725+
.chain(
726+
data_a
727+
.projection_bounds()
728+
.map(|b| b.map_bound(ty::ExistentialPredicate::Projection)),
729+
)
730+
.chain(
731+
data_b
732+
.auto_traits()
733+
.map(ty::ExistentialPredicate::AutoTrait)
734+
.map(ty::Binder::dummy),
735+
);
736+
let existential_predicates = tcx.mk_poly_existential_predicates(iter);
737+
let source_trait = tcx.mk_dynamic(existential_predicates, r_b);
719738

720-
let principal_a = data_a.principal();
721-
let principal_def_id_b = data_b.principal_def_id();
722-
723-
let existential_predicate = if let Some(principal_a) = principal_a {
724-
let source_trait_ref = principal_a.with_self_ty(tcx, source);
725-
let target_trait_did = principal_def_id_b.ok_or_else(|| Unimplemented)?;
726-
let upcast_idx = util::supertraits(tcx, source_trait_ref)
727-
.position(|upcast_trait_ref| upcast_trait_ref.def_id() == target_trait_did)
728-
.ok_or_else(|| Unimplemented)?;
729-
// FIXME(crlf0710): This is less than ideal, for example,
730-
// if the trait is defined as `trait Foo: Bar<u32> + Bar<i32>`,
731-
// the coercion from Box<Foo> to Box<dyn Bar<_>> is actually ambiguous.
732-
// We currently make this coercion fail for now.
733-
//
734-
// see #65991 for more information.
735-
if util::supertraits(tcx, source_trait_ref)
736-
.skip(upcast_idx + 1)
737-
.any(|upcast_trait_ref| upcast_trait_ref.def_id() == target_trait_did)
738-
{
739-
return Err(Unimplemented);
740-
}
741-
let target_trait_ref =
742-
util::supertraits(tcx, source_trait_ref).nth(upcast_idx).unwrap();
743-
let existential_predicate = target_trait_ref.map_bound(|trait_ref| {
744-
ty::ExistentialPredicate::Trait(ty::ExistentialTraitRef::erase_self_ty(
745-
tcx, trait_ref,
746-
))
747-
});
748-
Some(existential_predicate)
749-
} else if principal_def_id_b.is_none() {
750-
None
751-
} else {
752-
return Err(Unimplemented);
753-
};
739+
// Require that the traits involved in this upcast are **equal**;
740+
// only the **lifetime bound** is changed.
741+
let InferOk { obligations, .. } = self
742+
.infcx
743+
.at(&obligation.cause, obligation.param_env)
744+
.sup(target, source_trait)
745+
.map_err(|_| Unimplemented)?;
746+
nested.extend(obligations);
747+
748+
// Register one obligation for 'a: 'b.
749+
let cause = ObligationCause::new(
750+
obligation.cause.span,
751+
obligation.cause.body_id,
752+
ObjectCastObligation(target),
753+
);
754+
let outlives = ty::OutlivesPredicate(r_a, r_b);
755+
nested.push(Obligation::with_depth(
756+
cause,
757+
obligation.recursion_depth + 1,
758+
obligation.param_env,
759+
obligation.predicate.rebind(outlives).to_predicate(tcx),
760+
));
761+
}
762+
_ => bug!(),
763+
};
764+
765+
Ok(ImplSourceBuiltinData { nested })
766+
}
754767

755-
let iter = existential_predicate
768+
fn confirm_builtin_unsize_candidate(
769+
&mut self,
770+
obligation: &TraitObligation<'tcx>,
771+
) -> Result<ImplSourceBuiltinData<PredicateObligation<'tcx>>, SelectionError<'tcx>> {
772+
let tcx = self.tcx();
773+
774+
// `assemble_candidates_for_unsizing` should ensure there are no late-bound
775+
// regions here. See the comment there for more details.
776+
let source = self.infcx.shallow_resolve(obligation.self_ty().no_bound_vars().unwrap());
777+
let target = obligation.predicate.skip_binder().trait_ref.substs.type_at(1);
778+
let target = self.infcx.shallow_resolve(target);
779+
780+
debug!(?source, ?target, "confirm_builtin_unsize_candidate");
781+
782+
let mut nested = vec![];
783+
match (source.kind(), target.kind()) {
784+
// Trait+Kx+'a -> Trait+Ky+'b (auto traits and lifetime subtyping).
785+
(&ty::Dynamic(ref data_a, r_a), &ty::Dynamic(ref data_b, r_b)) => {
786+
// See `assemble_candidates_for_unsizing` for more info.
787+
// We already checked the compatiblity of auto traits within `assemble_candidates_for_unsizing`.
788+
let iter = data_a
789+
.principal()
790+
.map(|b| b.map_bound(ty::ExistentialPredicate::Trait))
756791
.into_iter()
757792
.chain(
758793
data_a

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

+6
Original file line numberDiff line numberDiff line change
@@ -1500,6 +1500,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
15001500
| FnPointerCandidate
15011501
| BuiltinObjectCandidate
15021502
| BuiltinUnsizeCandidate
1503+
| TraitUpcastingUnsizeCandidate(_)
15031504
| BuiltinCandidate { .. }
15041505
| TraitAliasCandidate(..)
15051506
| ObjectCandidate(_)
@@ -1517,6 +1518,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
15171518
| FnPointerCandidate
15181519
| BuiltinObjectCandidate
15191520
| BuiltinUnsizeCandidate
1521+
| TraitUpcastingUnsizeCandidate(_)
15201522
| BuiltinCandidate { has_nested: true }
15211523
| TraitAliasCandidate(..),
15221524
ParamCandidate(ref cand),
@@ -1546,6 +1548,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
15461548
| FnPointerCandidate
15471549
| BuiltinObjectCandidate
15481550
| BuiltinUnsizeCandidate
1551+
| TraitUpcastingUnsizeCandidate(_)
15491552
| BuiltinCandidate { .. }
15501553
| TraitAliasCandidate(..),
15511554
) => true,
@@ -1557,6 +1560,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
15571560
| FnPointerCandidate
15581561
| BuiltinObjectCandidate
15591562
| BuiltinUnsizeCandidate
1563+
| TraitUpcastingUnsizeCandidate(_)
15601564
| BuiltinCandidate { .. }
15611565
| TraitAliasCandidate(..),
15621566
ObjectCandidate(_) | ProjectionCandidate(_),
@@ -1630,6 +1634,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
16301634
| FnPointerCandidate
16311635
| BuiltinObjectCandidate
16321636
| BuiltinUnsizeCandidate
1637+
| TraitUpcastingUnsizeCandidate(_)
16331638
| BuiltinCandidate { has_nested: true }
16341639
| TraitAliasCandidate(..),
16351640
ImplCandidate(_)
@@ -1638,6 +1643,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
16381643
| FnPointerCandidate
16391644
| BuiltinObjectCandidate
16401645
| BuiltinUnsizeCandidate
1646+
| TraitUpcastingUnsizeCandidate(_)
16411647
| BuiltinCandidate { has_nested: true }
16421648
| TraitAliasCandidate(..),
16431649
) => false,

src/test/ui/traits/trait-upcasting/type-checking-test-1.rs

+3-9
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,8 @@ trait Bar<T> {
99
}
1010

1111
fn test_specific(x: &dyn Foo) {
12-
let _ = x as &dyn Bar<i32>; // FIXME: OK, eventually
13-
//~^ ERROR non-primitive cast
14-
//~^^ ERROR the trait bound `&dyn Foo: Bar<i32>` is not satisfied
15-
let _ = x as &dyn Bar<u32>; // FIXME: OK, eventually
16-
//~^ ERROR non-primitive cast
17-
//~^^ ERROR the trait bound `&dyn Foo: Bar<u32>` is not satisfied
12+
let _ = x as &dyn Bar<i32>; // OK
13+
let _ = x as &dyn Bar<u32>; // OK
1814
}
1915

2016
fn test_unknown_version(x: &dyn Foo) {
@@ -24,9 +20,7 @@ fn test_unknown_version(x: &dyn Foo) {
2420
}
2521

2622
fn test_infer_version(x: &dyn Foo) {
27-
let a = x as &dyn Bar<_>; // FIXME: OK, eventually
28-
//~^ ERROR non-primitive cast
29-
//~^^ ERROR the trait bound `&dyn Foo: Bar<u32>` is not satisfied
23+
let a = x as &dyn Bar<_>; // OK
3024
let _: Option<u32> = a.bar();
3125
}
3226

0 commit comments

Comments
 (0)