Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Trait upcasting coercion (part1) #86264

Merged
merged 4 commits into from
Jul 31, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions compiler/rustc_feature/src/active.rs
Original file line number Diff line number Diff line change
Expand Up @@ -683,6 +683,10 @@ declare_features! (
/// Allows the `?` operator in const contexts.
(active, const_try, "1.56.0", Some(74935), None),

/// Allows upcasting trait objects via supertraits.
/// Trait upcasting is casting, e.g., `dyn Foo -> dyn Bar` where `Foo: Bar`.
(incomplete, trait_upcasting, "1.56.0", Some(65991), None),

// -------------------------------------------------------------------------
// feature-group-end: actual feature gates
// -------------------------------------------------------------------------
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_span/src/symbol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1270,6 +1270,7 @@ symbols! {
trace_macros,
track_caller,
trait_alias,
trait_upcasting,
transmute,
transparent,
transparent_enums,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -693,22 +693,12 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
let may_apply = match (source.kind(), target.kind()) {
// Trait+Kx+'a -> Trait+Ky+'b (upcasts).
(&ty::Dynamic(ref data_a, ..), &ty::Dynamic(ref data_b, ..)) => {
// Upcasts permit two things:
//
// 1. Dropping auto traits, e.g., `Foo + Send` to `Foo`
// 2. Tightening the region bound, e.g., `Foo + 'a` to `Foo + 'b` if `'a: 'b`
//
// Note that neither of these changes requires any
// change at runtime. Eventually this will be
// generalized.
//
// We always upcast when we can because of reason
// #2 (region bounds).
data_a.principal_def_id() == data_b.principal_def_id()
&& data_b
.auto_traits()
// All of a's auto traits need to be in b's auto traits.
.all(|b| data_a.auto_traits().any(|a| a == b))
// See `confirm_builtin_unsize_candidate` for more info.
let auto_traits_compatible = data_b
.auto_traits()
// All of a's auto traits need to be in b's auto traits.
.all(|b| data_a.auto_traits().any(|a| a == b));
auto_traits_compatible
}

// `T` -> `Trait`
Expand Down
54 changes: 50 additions & 4 deletions compiler/rustc_trait_selection/src/traits/select/confirmation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -703,10 +703,56 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
match (source.kind(), target.kind()) {
// Trait+Kx+'a -> Trait+Ky+'b (upcasts).
(&ty::Dynamic(ref data_a, r_a), &ty::Dynamic(ref data_b, r_b)) => {
// See `assemble_candidates_for_unsizing` for more info.
let iter = data_a
.principal()
.map(|b| b.map_bound(ty::ExistentialPredicate::Trait))
// Upcast coercions permit several things:
//
// 1. Dropping auto traits, e.g., `Foo + Send` to `Foo`
// 2. Tightening the region bound, e.g., `Foo + 'a` to `Foo + 'b` if `'a: 'b`
// 3. Tightening trait to its super traits, eg. `Foo` to `Bar` if `Foo: Bar`
//
// Note that neither of the first two of these changes requires any
// change at runtime. The third needs to change pointer metadata at runtime.
//
// We always perform upcasting coercions when we can because of reason
// #2 (region bounds).

// We already checked the compatiblity of auto traits within `assemble_candidates_for_unsizing`.

let principal_a = data_a.principal();
let principal_def_id_b = data_b.principal_def_id();

let existential_predicate = if let Some(principal_a) = principal_a {
let source_trait_ref = principal_a.with_self_ty(tcx, source);
let target_trait_did = principal_def_id_b.ok_or_else(|| Unimplemented)?;
let upcast_idx = util::supertraits(tcx, source_trait_ref)
.position(|upcast_trait_ref| upcast_trait_ref.def_id() == target_trait_did)
.ok_or_else(|| Unimplemented)?;
// FIXME(crlf0710): This is less than ideal, for example,
// if the trait is defined as `trait Foo: Bar<u32> + Bar<i32>`,
// the coercion from Box<Foo> to Box<dyn Bar<_>> is actually ambiguous.
// We currently make this coercion fail for now.
//
// see #65991 for more information.
if util::supertraits(tcx, source_trait_ref)
.skip(upcast_idx + 1)
.any(|upcast_trait_ref| upcast_trait_ref.def_id() == target_trait_did)
{
return Err(Unimplemented);
}
let target_trait_ref =
util::supertraits(tcx, source_trait_ref).nth(upcast_idx).unwrap();
let existential_predicate = target_trait_ref.map_bound(|trait_ref| {
ty::ExistentialPredicate::Trait(ty::ExistentialTraitRef::erase_self_ty(
tcx, trait_ref,
))
});
Some(existential_predicate)
} else if principal_def_id_b.is_none() {
None
} else {
return Err(Unimplemented);
};

let iter = existential_predicate
.into_iter()
.chain(
data_a
Expand Down
20 changes: 20 additions & 0 deletions compiler/rustc_typeck/src/check/coercion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -576,6 +576,7 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> {
)];

let mut has_unsized_tuple_coercion = false;
let mut has_trait_upcasting_coercion = false;

// Keep resolving `CoerceUnsized` and `Unsize` predicates to avoid
// emitting a coercion in cases like `Foo<$1>` -> `Foo<$2>`, where
Expand All @@ -590,7 +591,16 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> {
if traits.contains(&trait_pred.def_id()) =>
{
if unsize_did == trait_pred.def_id() {
let self_ty = trait_pred.self_ty();
let unsize_ty = trait_pred.trait_ref.substs[1].expect_ty();
if let (ty::Dynamic(ref data_a, ..), ty::Dynamic(ref data_b, ..)) =
(self_ty.kind(), unsize_ty.kind())
{
if data_a.principal_def_id() != data_b.principal_def_id() {
debug!("coerce_unsized: found trait upcasting coercion");
has_trait_upcasting_coercion = true;
}
}
if let ty::Tuple(..) = unsize_ty.kind() {
debug!("coerce_unsized: found unsized tuple coercion");
has_unsized_tuple_coercion = true;
Expand Down Expand Up @@ -666,6 +676,16 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> {
.emit();
}

if has_trait_upcasting_coercion && !self.tcx().features().trait_upcasting {
feature_err(
&self.tcx.sess.parse_sess,
sym::trait_upcasting,
self.cause.span,
"trait upcasting coercion is experimental",
)
.emit();
}

Ok(coercion)
}

Expand Down
13 changes: 13 additions & 0 deletions src/test/ui/feature-gates/feature-gate-trait_upcasting.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
trait Foo {}

trait Bar: Foo {}

impl Foo for () {}

impl Bar for () {}

fn main() {
let bar: &dyn Bar = &();
let foo: &dyn Foo = bar;
//~^ ERROR trait upcasting coercion is experimental [E0658]
}
12 changes: 12 additions & 0 deletions src/test/ui/feature-gates/feature-gate-trait_upcasting.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
error[E0658]: trait upcasting coercion is experimental
--> $DIR/feature-gate-trait_upcasting.rs:11:25
|
LL | let foo: &dyn Foo = bar;
| ^^^
|
= note: see issue #65991 <https://github.com/rust-lang/rust/issues/65991> for more information
= help: add `#![feature(trait_upcasting)]` to the crate attributes to enable

error: aborting due to previous error

For more information about this error, try `rustc --explain E0658`.
4 changes: 2 additions & 2 deletions src/test/ui/issues/issue-11515.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
#![feature(box_syntax)]

struct Test {
func: Box<dyn FnMut() + 'static>
func: Box<dyn FnMut() + 'static>,
}

fn main() {
let closure: Box<dyn Fn() + 'static> = Box::new(|| ());
let test = box Test { func: closure }; //~ ERROR mismatched types
let test = box Test { func: closure }; //~ ERROR trait upcasting coercion is experimental [E0658]
}
10 changes: 5 additions & 5 deletions src/test/ui/issues/issue-11515.stderr
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
error[E0308]: mismatched types
error[E0658]: trait upcasting coercion is experimental
--> $DIR/issue-11515.rs:9:33
|
LL | let test = box Test { func: closure };
| ^^^^^^^ expected trait `FnMut`, found trait `Fn`
| ^^^^^^^
|
= note: expected struct `Box<(dyn FnMut() + 'static)>`
found struct `Box<(dyn Fn() + 'static)>`
= note: see issue #65991 <https://github.com/rust-lang/rust/issues/65991> for more information
= help: add `#![feature(trait_upcasting)]` to the crate attributes to enable

error: aborting due to previous error

For more information about this error, try `rustc --explain E0308`.
For more information about this error, try `rustc --explain E0658`.
13 changes: 13 additions & 0 deletions src/test/ui/traits/trait-upcasting/issue-11515-upcast-fn_mut-fn.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// run-pass
#![feature(box_syntax, trait_upcasting)]
#![allow(incomplete_features)]

struct Test {
func: Box<dyn FnMut() + 'static>,
}

fn main() {
let closure: Box<dyn Fn() + 'static> = Box::new(|| ());
let mut test = box Test { func: closure };
(test.func)();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// check-fail
#![feature(trait_upcasting)]
#![allow(incomplete_features)]

trait Bar<T> {
fn bar(&self, _: T) {}
}

trait Foo : Bar<i32> + Bar<u32> {
fn foo(&self, _: ()) {}
}

struct S;

impl Bar<i32> for S {}
impl Bar<u32> for S {}
impl Foo for S {}

fn main() {
let s: &dyn Foo = &S;
let t: &dyn Bar<_> = s; //~ ERROR mismatched types
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
error[E0308]: mismatched types
--> $DIR/multiple-occurence-ambiguousity.rs:21:26
|
LL | let t: &dyn Bar<_> = s;
| ----------- ^ expected trait `Bar`, found trait `Foo`
| |
| expected due to this
|
= note: expected reference `&dyn Bar<_>`
found reference `&dyn Foo`

error: aborting due to previous error

For more information about this error, try `rustc --explain E0308`.
33 changes: 33 additions & 0 deletions src/test/ui/traits/trait-upcasting/type-checking-test-1.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#![feature(trait_upcasting)]
#![allow(incomplete_features)]

trait Foo: Bar<i32> + Bar<u32> {}
trait Bar<T> {
fn bar(&self) -> Option<T> {
None
}
}

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

fn test_unknown_version(x: &dyn Foo) {
let _ = x as &dyn Bar<_>; // Ambiguous
//~^ ERROR non-primitive cast
//~^^ ERROR the trait bound `&dyn Foo: Bar<_>` is not satisfied
}

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

fn main() {}
80 changes: 80 additions & 0 deletions src/test/ui/traits/trait-upcasting/type-checking-test-1.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
error[E0605]: non-primitive cast: `&dyn Foo` as `&dyn Bar<i32>`
--> $DIR/type-checking-test-1.rs:12:13
|
LL | let _ = x as &dyn Bar<i32>; // FIXME: OK, eventually
| ^^^^^^^^^^^^^^^^^^ invalid cast
|
help: consider borrowing the value
|
LL | let _ = &x as &dyn Bar<i32>; // FIXME: OK, eventually
| ^

error[E0605]: non-primitive cast: `&dyn Foo` as `&dyn Bar<u32>`
--> $DIR/type-checking-test-1.rs:15:13
|
LL | let _ = x as &dyn Bar<u32>; // FIXME: OK, eventually
| ^^^^^^^^^^^^^^^^^^ invalid cast
|
help: consider borrowing the value
|
LL | let _ = &x as &dyn Bar<u32>; // FIXME: OK, eventually
| ^

error[E0277]: the trait bound `&dyn Foo: Bar<i32>` is not satisfied
--> $DIR/type-checking-test-1.rs:12:13
|
LL | let _ = x as &dyn Bar<i32>; // FIXME: OK, eventually
| ^ the trait `Bar<i32>` is not implemented for `&dyn Foo`
|
= note: required for the cast to the object type `dyn Bar<i32>`

error[E0277]: the trait bound `&dyn Foo: Bar<u32>` is not satisfied
--> $DIR/type-checking-test-1.rs:15:13
|
LL | let _ = x as &dyn Bar<u32>; // FIXME: OK, eventually
| ^ the trait `Bar<u32>` is not implemented for `&dyn Foo`
|
= note: required for the cast to the object type `dyn Bar<u32>`

error[E0605]: non-primitive cast: `&dyn Foo` as `&dyn Bar<_>`
--> $DIR/type-checking-test-1.rs:21:13
|
LL | let _ = x as &dyn Bar<_>; // Ambiguous
| ^^^^^^^^^^^^^^^^ invalid cast
|
help: consider borrowing the value
|
LL | let _ = &x as &dyn Bar<_>; // Ambiguous
| ^

error[E0277]: the trait bound `&dyn Foo: Bar<_>` is not satisfied
--> $DIR/type-checking-test-1.rs:21:13
|
LL | let _ = x as &dyn Bar<_>; // Ambiguous
| ^ the trait `Bar<_>` is not implemented for `&dyn Foo`
|
= note: required for the cast to the object type `dyn Bar<_>`

error[E0605]: non-primitive cast: `&dyn Foo` as `&dyn Bar<u32>`
--> $DIR/type-checking-test-1.rs:27:13
|
LL | let a = x as &dyn Bar<_>; // FIXME: OK, eventually
| ^^^^^^^^^^^^^^^^ invalid cast
|
help: consider borrowing the value
|
LL | let a = &x as &dyn Bar<_>; // FIXME: OK, eventually
| ^

error[E0277]: the trait bound `&dyn Foo: Bar<u32>` is not satisfied
--> $DIR/type-checking-test-1.rs:27:13
|
LL | let a = x as &dyn Bar<_>; // FIXME: OK, eventually
| ^ the trait `Bar<u32>` is not implemented for `&dyn Foo`
|
= note: required for the cast to the object type `dyn Bar<u32>`

error: aborting due to 8 previous errors

Some errors have detailed explanations: E0277, E0605.
For more information about an error, try `rustc --explain E0277`.
Loading