Skip to content

Commit 7069a8c

Browse files
committed
Auto merge of #86264 - crlf0710:trait_upcasting_part1, r=nikomatsakis
Trait upcasting coercion (part1) This revives the first part of earlier PR #60900 . It's not very clear to me which parts of that pr was design decisions, so i decide to cut it into pieces and land them incrementally. This allows more eyes on the details. This is the first part, it adds feature gates, adds feature gates tests, and implemented the unsize conversion part. (I hope i have dealt with the `ExistentialTraitRef` values correctly...) The next part will be implementing the pointer casting.
2 parents 6b0b07d + a28ee25 commit 7069a8c

20 files changed

+504
-27
lines changed

compiler/rustc_feature/src/active.rs

+4
Original file line numberDiff line numberDiff line change
@@ -683,6 +683,10 @@ declare_features! (
683683
/// Allows the `?` operator in const contexts.
684684
(active, const_try, "1.56.0", Some(74935), None),
685685

686+
/// Allows upcasting trait objects via supertraits.
687+
/// Trait upcasting is casting, e.g., `dyn Foo -> dyn Bar` where `Foo: Bar`.
688+
(incomplete, trait_upcasting, "1.56.0", Some(65991), None),
689+
686690
// -------------------------------------------------------------------------
687691
// feature-group-end: actual feature gates
688692
// -------------------------------------------------------------------------

compiler/rustc_span/src/symbol.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1270,6 +1270,7 @@ symbols! {
12701270
trace_macros,
12711271
track_caller,
12721272
trait_alias,
1273+
trait_upcasting,
12731274
transmute,
12741275
transparent,
12751276
transparent_enums,

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

+6-16
Original file line numberDiff line numberDiff line change
@@ -693,22 +693,12 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
693693
let may_apply = 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-
// Upcasts permit two 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-
//
701-
// Note that neither of these changes requires any
702-
// change at runtime. Eventually this will be
703-
// generalized.
704-
//
705-
// We always upcast when we can because of reason
706-
// #2 (region bounds).
707-
data_a.principal_def_id() == data_b.principal_def_id()
708-
&& data_b
709-
.auto_traits()
710-
// All of a's auto traits need to be in b's auto traits.
711-
.all(|b| data_a.auto_traits().any(|a| a == b))
696+
// See `confirm_builtin_unsize_candidate` for more info.
697+
let auto_traits_compatible = data_b
698+
.auto_traits()
699+
// All of a's auto traits need to be in b's auto traits.
700+
.all(|b| data_a.auto_traits().any(|a| a == b));
701+
auto_traits_compatible
712702
}
713703

714704
// `T` -> `Trait`

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

+50-4
Original file line numberDiff line numberDiff line change
@@ -703,10 +703,56 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
703703
match (source.kind(), target.kind()) {
704704
// Trait+Kx+'a -> Trait+Ky+'b (upcasts).
705705
(&ty::Dynamic(ref data_a, r_a), &ty::Dynamic(ref data_b, r_b)) => {
706-
// See `assemble_candidates_for_unsizing` for more info.
707-
let iter = data_a
708-
.principal()
709-
.map(|b| b.map_bound(ty::ExistentialPredicate::Trait))
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+
718+
// We already checked the compatiblity of auto traits within `assemble_candidates_for_unsizing`.
719+
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+
};
754+
755+
let iter = existential_predicate
710756
.into_iter()
711757
.chain(
712758
data_a

compiler/rustc_typeck/src/check/coercion.rs

+20
Original file line numberDiff line numberDiff line change
@@ -576,6 +576,7 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> {
576576
)];
577577

578578
let mut has_unsized_tuple_coercion = false;
579+
let mut has_trait_upcasting_coercion = false;
579580

580581
// Keep resolving `CoerceUnsized` and `Unsize` predicates to avoid
581582
// emitting a coercion in cases like `Foo<$1>` -> `Foo<$2>`, where
@@ -590,7 +591,16 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> {
590591
if traits.contains(&trait_pred.def_id()) =>
591592
{
592593
if unsize_did == trait_pred.def_id() {
594+
let self_ty = trait_pred.self_ty();
593595
let unsize_ty = trait_pred.trait_ref.substs[1].expect_ty();
596+
if let (ty::Dynamic(ref data_a, ..), ty::Dynamic(ref data_b, ..)) =
597+
(self_ty.kind(), unsize_ty.kind())
598+
{
599+
if data_a.principal_def_id() != data_b.principal_def_id() {
600+
debug!("coerce_unsized: found trait upcasting coercion");
601+
has_trait_upcasting_coercion = true;
602+
}
603+
}
594604
if let ty::Tuple(..) = unsize_ty.kind() {
595605
debug!("coerce_unsized: found unsized tuple coercion");
596606
has_unsized_tuple_coercion = true;
@@ -666,6 +676,16 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> {
666676
.emit();
667677
}
668678

679+
if has_trait_upcasting_coercion && !self.tcx().features().trait_upcasting {
680+
feature_err(
681+
&self.tcx.sess.parse_sess,
682+
sym::trait_upcasting,
683+
self.cause.span,
684+
"trait upcasting coercion is experimental",
685+
)
686+
.emit();
687+
}
688+
669689
Ok(coercion)
670690
}
671691

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
trait Foo {}
2+
3+
trait Bar: Foo {}
4+
5+
impl Foo for () {}
6+
7+
impl Bar for () {}
8+
9+
fn main() {
10+
let bar: &dyn Bar = &();
11+
let foo: &dyn Foo = bar;
12+
//~^ ERROR trait upcasting coercion is experimental [E0658]
13+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
error[E0658]: trait upcasting coercion is experimental
2+
--> $DIR/feature-gate-trait_upcasting.rs:11:25
3+
|
4+
LL | let foo: &dyn Foo = bar;
5+
| ^^^
6+
|
7+
= note: see issue #65991 <https://github.com/rust-lang/rust/issues/65991> for more information
8+
= help: add `#![feature(trait_upcasting)]` to the crate attributes to enable
9+
10+
error: aborting due to previous error
11+
12+
For more information about this error, try `rustc --explain E0658`.

src/test/ui/issues/issue-11515.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
#![feature(box_syntax)]
22

33
struct Test {
4-
func: Box<dyn FnMut() + 'static>
4+
func: Box<dyn FnMut() + 'static>,
55
}
66

77
fn main() {
88
let closure: Box<dyn Fn() + 'static> = Box::new(|| ());
9-
let test = box Test { func: closure }; //~ ERROR mismatched types
9+
let test = box Test { func: closure }; //~ ERROR trait upcasting coercion is experimental [E0658]
1010
}

src/test/ui/issues/issue-11515.stderr

+5-5
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
error[E0308]: mismatched types
1+
error[E0658]: trait upcasting coercion is experimental
22
--> $DIR/issue-11515.rs:9:33
33
|
44
LL | let test = box Test { func: closure };
5-
| ^^^^^^^ expected trait `FnMut`, found trait `Fn`
5+
| ^^^^^^^
66
|
7-
= note: expected struct `Box<(dyn FnMut() + 'static)>`
8-
found struct `Box<(dyn Fn() + 'static)>`
7+
= note: see issue #65991 <https://github.com/rust-lang/rust/issues/65991> for more information
8+
= help: add `#![feature(trait_upcasting)]` to the crate attributes to enable
99

1010
error: aborting due to previous error
1111

12-
For more information about this error, try `rustc --explain E0308`.
12+
For more information about this error, try `rustc --explain E0658`.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// run-pass
2+
#![feature(box_syntax, trait_upcasting)]
3+
#![allow(incomplete_features)]
4+
5+
struct Test {
6+
func: Box<dyn FnMut() + 'static>,
7+
}
8+
9+
fn main() {
10+
let closure: Box<dyn Fn() + 'static> = Box::new(|| ());
11+
let mut test = box Test { func: closure };
12+
(test.func)();
13+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// check-fail
2+
#![feature(trait_upcasting)]
3+
#![allow(incomplete_features)]
4+
5+
trait Bar<T> {
6+
fn bar(&self, _: T) {}
7+
}
8+
9+
trait Foo : Bar<i32> + Bar<u32> {
10+
fn foo(&self, _: ()) {}
11+
}
12+
13+
struct S;
14+
15+
impl Bar<i32> for S {}
16+
impl Bar<u32> for S {}
17+
impl Foo for S {}
18+
19+
fn main() {
20+
let s: &dyn Foo = &S;
21+
let t: &dyn Bar<_> = s; //~ ERROR mismatched types
22+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
error[E0308]: mismatched types
2+
--> $DIR/multiple-occurence-ambiguousity.rs:21:26
3+
|
4+
LL | let t: &dyn Bar<_> = s;
5+
| ----------- ^ expected trait `Bar`, found trait `Foo`
6+
| |
7+
| expected due to this
8+
|
9+
= note: expected reference `&dyn Bar<_>`
10+
found reference `&dyn Foo`
11+
12+
error: aborting due to previous error
13+
14+
For more information about this error, try `rustc --explain E0308`.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
#![feature(trait_upcasting)]
2+
#![allow(incomplete_features)]
3+
4+
trait Foo: Bar<i32> + Bar<u32> {}
5+
trait Bar<T> {
6+
fn bar(&self) -> Option<T> {
7+
None
8+
}
9+
}
10+
11+
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
18+
}
19+
20+
fn test_unknown_version(x: &dyn Foo) {
21+
let _ = x as &dyn Bar<_>; // Ambiguous
22+
//~^ ERROR non-primitive cast
23+
//~^^ ERROR the trait bound `&dyn Foo: Bar<_>` is not satisfied
24+
}
25+
26+
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
30+
let _: Option<u32> = a.bar();
31+
}
32+
33+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
error[E0605]: non-primitive cast: `&dyn Foo` as `&dyn Bar<i32>`
2+
--> $DIR/type-checking-test-1.rs:12:13
3+
|
4+
LL | let _ = x as &dyn Bar<i32>; // FIXME: OK, eventually
5+
| ^^^^^^^^^^^^^^^^^^ invalid cast
6+
|
7+
help: consider borrowing the value
8+
|
9+
LL | let _ = &x as &dyn Bar<i32>; // FIXME: OK, eventually
10+
| ^
11+
12+
error[E0605]: non-primitive cast: `&dyn Foo` as `&dyn Bar<u32>`
13+
--> $DIR/type-checking-test-1.rs:15:13
14+
|
15+
LL | let _ = x as &dyn Bar<u32>; // FIXME: OK, eventually
16+
| ^^^^^^^^^^^^^^^^^^ invalid cast
17+
|
18+
help: consider borrowing the value
19+
|
20+
LL | let _ = &x as &dyn Bar<u32>; // FIXME: OK, eventually
21+
| ^
22+
23+
error[E0277]: the trait bound `&dyn Foo: Bar<i32>` is not satisfied
24+
--> $DIR/type-checking-test-1.rs:12:13
25+
|
26+
LL | let _ = x as &dyn Bar<i32>; // FIXME: OK, eventually
27+
| ^ the trait `Bar<i32>` is not implemented for `&dyn Foo`
28+
|
29+
= note: required for the cast to the object type `dyn Bar<i32>`
30+
31+
error[E0277]: the trait bound `&dyn Foo: Bar<u32>` is not satisfied
32+
--> $DIR/type-checking-test-1.rs:15:13
33+
|
34+
LL | let _ = x as &dyn Bar<u32>; // FIXME: OK, eventually
35+
| ^ the trait `Bar<u32>` is not implemented for `&dyn Foo`
36+
|
37+
= note: required for the cast to the object type `dyn Bar<u32>`
38+
39+
error[E0605]: non-primitive cast: `&dyn Foo` as `&dyn Bar<_>`
40+
--> $DIR/type-checking-test-1.rs:21:13
41+
|
42+
LL | let _ = x as &dyn Bar<_>; // Ambiguous
43+
| ^^^^^^^^^^^^^^^^ invalid cast
44+
|
45+
help: consider borrowing the value
46+
|
47+
LL | let _ = &x as &dyn Bar<_>; // Ambiguous
48+
| ^
49+
50+
error[E0277]: the trait bound `&dyn Foo: Bar<_>` is not satisfied
51+
--> $DIR/type-checking-test-1.rs:21:13
52+
|
53+
LL | let _ = x as &dyn Bar<_>; // Ambiguous
54+
| ^ the trait `Bar<_>` is not implemented for `&dyn Foo`
55+
|
56+
= note: required for the cast to the object type `dyn Bar<_>`
57+
58+
error[E0605]: non-primitive cast: `&dyn Foo` as `&dyn Bar<u32>`
59+
--> $DIR/type-checking-test-1.rs:27:13
60+
|
61+
LL | let a = x as &dyn Bar<_>; // FIXME: OK, eventually
62+
| ^^^^^^^^^^^^^^^^ invalid cast
63+
|
64+
help: consider borrowing the value
65+
|
66+
LL | let a = &x as &dyn Bar<_>; // FIXME: OK, eventually
67+
| ^
68+
69+
error[E0277]: the trait bound `&dyn Foo: Bar<u32>` is not satisfied
70+
--> $DIR/type-checking-test-1.rs:27:13
71+
|
72+
LL | let a = x as &dyn Bar<_>; // FIXME: OK, eventually
73+
| ^ the trait `Bar<u32>` is not implemented for `&dyn Foo`
74+
|
75+
= note: required for the cast to the object type `dyn Bar<u32>`
76+
77+
error: aborting due to 8 previous errors
78+
79+
Some errors have detailed explanations: E0277, E0605.
80+
For more information about an error, try `rustc --explain E0277`.

0 commit comments

Comments
 (0)