Implement trait upcasting coercion type-checking.

This commit is contained in:
Charles Lew 2021-07-25 18:43:48 +08:00
parent ac354cf5ce
commit fb4e0a0972
10 changed files with 130 additions and 25 deletions

View file

@ -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`

View file

@ -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

View file

@ -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
@ -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;
@ -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)
}

View file

@ -9,5 +9,5 @@ impl Bar for () {}
fn main() {
let bar: &dyn Bar = &();
let foo: &dyn Foo = bar;
//~^ ERROR trait upcasting is experimental [E0658]
//~^ ERROR trait upcasting coercion is experimental [E0658]
}

View file

@ -1,4 +1,4 @@
error[E0658]: trait upcasting is experimental
error[E0658]: trait upcasting coercion is experimental
--> $DIR/feature-gate-trait_upcasting.rs:11:25
|
LL | let foo: &dyn Foo = bar;

View file

@ -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 trait upcasting is experimental [E0658]
let test = box Test { func: closure }; //~ ERROR trait upcasting coercion is experimental [E0658]
}

View file

@ -1,4 +1,4 @@
error[E0658]: trait upcasting is experimental
error[E0658]: trait upcasting coercion is experimental
--> $DIR/issue-11515.rs:9:33
|
LL | let test = box Test { func: closure };

View file

@ -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)();
}

View file

@ -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
}

View file

@ -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`.