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

fix trait objects with a Self-containing projection values #56863

Merged
merged 2 commits into from
Dec 18, 2018
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
21 changes: 20 additions & 1 deletion src/librustc/traits/object_safety.rs
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,26 @@ impl<'a, 'tcx> TyCtxt<'a, 'tcx, 'tcx> {
// In the case of a trait predicate, we can skip the "self" type.
data.skip_binder().input_types().skip(1).any(|t| t.has_self_ty())
}
ty::Predicate::Projection(..) |
ty::Predicate::Projection(ref data) => {
// And similarly for projections. This should be redundant with
// the previous check because any projection should have a
// matching `Trait` predicate with the same inputs, but we do
// the check to be safe.
//
// Note that we *do* allow projection *outputs* to contain
// `self` (i.e., `trait Foo: Bar<Output=Self::Result> { type Result; }`),
// we just require the user to specify *both* outputs
// in the object type (i.e., `dyn Foo<Output=(), Result=()>`).
//
// This is ALT2 in issue #56288, see that for discussion of the
// possible alternatives.
data.skip_binder()
.projection_ty
.trait_ref(self)
.input_types()
.skip(1)
.any(|t| t.has_self_ty())
}
ty::Predicate::WellFormed(..) |
ty::Predicate::ObjectSafe(..) |
ty::Predicate::TypeOutlives(..) |
Expand Down
28 changes: 26 additions & 2 deletions src/librustc_typeck/astconv.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1013,15 +1013,39 @@ impl<'o, 'gcx: 'tcx, 'tcx> dyn AstConv<'gcx, 'tcx> + 'o {
let mut associated_types = BTreeSet::default();

for tr in traits::elaborate_trait_ref(tcx, principal) {
debug!("conv_object_ty_poly_trait_ref: observing object predicate `{:?}`", tr);
match tr {
ty::Predicate::Trait(pred) => {
associated_types.extend(tcx.associated_items(pred.def_id())
.filter(|item| item.kind == ty::AssociatedKind::Type)
.map(|item| item.def_id));
}
ty::Predicate::Projection(pred) => {
// Include projections defined on supertraits.
projection_bounds.push((pred, DUMMY_SP))
// A `Self` within the original bound will be substituted with a
// `TRAIT_OBJECT_DUMMY_SELF`, so check for that.
let references_self =
pred.skip_binder().ty.walk().any(|t| t == dummy_self);

// If the projection output contains `Self`, force the user to
// elaborate it explicitly to avoid a bunch of complexity.
//
// The "classicaly useful" case is the following:
// ```
// trait MyTrait: FnMut() -> <Self as MyTrait>::MyOutput {
// type MyOutput;
// }
// ```
//
// Here, the user could theoretically write `dyn MyTrait<Output=X>`,
// but actually supporting that would "expand" to an infinitely-long type
// `fix $ τ → dyn MyTrait<MyOutput=X, Output=<τ as MyTrait>::MyOutput`.
//
// Instead, we force the user to write `dyn MyTrait<MyOutput=X, Output=X>`,
// which is uglier but works. See the discussion in #56288 for alternatives.
if !references_self {
// Include projections defined on supertraits,
projection_bounds.push((pred, DUMMY_SP))
}
}
_ => ()
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Regression test for #56288. Checks that if a supertrait defines an associated type
// projection that references `Self`, then that associated type must still be explicitly
// specified in the `dyn Trait` variant, since we don't know what `Self` is anymore.

trait Base {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's add a comment to this test:


Regression test for #56288. Checks that if a supertrait defines an associated type projection that references Self, then that associated type must still be explicitly specified in the dyn Trait variant, since we don't know what Self is anymore.

type Output;
}

trait Helper: Base<Output=<Self as Helper>::Target> {
type Target;
}

impl Base for u32
{
type Output = i32;
}

impl Helper for u32
{
type Target = i32;
}

trait ConstI32 {
type Out;
}

impl<T: ?Sized> ConstI32 for T {
type Out = i32;
}

// Test that you still need to manually give a projection type if the Output type
// is normalizable.
trait NormalizableHelper:
Base<Output=<Self as ConstI32>::Out>
{
type Target;
}

impl NormalizableHelper for u32
{
type Target = i32;
}

fn main() {
let _x: Box<dyn Helper<Target=i32>> = Box::new(2u32);
//~^ ERROR the value of the associated type `Output` (from the trait `Base`) must be specified

let _y: Box<dyn NormalizableHelper<Target=i32>> = Box::new(2u32);
//~^ ERROR the value of the associated type `Output` (from the trait `Base`) must be specified
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
error[E0191]: the value of the associated type `Output` (from the trait `Base`) must be specified
--> $DIR/trait-object-with-self-in-projection-output-bad.rs:45:17
|
LL | type Output;
| ------------ `Output` defined here
...
LL | let _x: Box<dyn Helper<Target=i32>> = Box::new(2u32);
| ^^^^^^^^^^^^^^^^^^^^^^ associated type `Output` must be specified

error[E0191]: the value of the associated type `Output` (from the trait `Base`) must be specified
--> $DIR/trait-object-with-self-in-projection-output-bad.rs:48:17
|
LL | type Output;
| ------------ `Output` defined here
...
LL | let _y: Box<dyn NormalizableHelper<Target=i32>> = Box::new(2u32);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ associated type `Output` must be specified

error: aborting due to 2 previous errors

For more information about this error, try `rustc --explain E0191`.
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// compile-pass
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similarly a comment here would be good:


Regression test related to #56288. Check that a supertrait projection (of Output) that references Self can be ok if it is referencing a projection (of Self::Target, in this case). Note that we still require the user to manually specify both Target and Output for now.


// Regression test related to #56288. Check that a supertrait projection (of
// `Output`) that references `Self` can be ok if it is referencing a projection (of
// `Self::Target`, in this case). Note that we still require the user to manually
// specify both `Target` and `Output` for now.

trait Base {
type Output;
}

trait Helper: Base<Output=<Self as Helper>::Target> {
type Target;
}

impl Base for u32
{
type Output = i32;
}

impl Helper for u32
{
type Target = i32;
}

fn main() {
let _x: Box<dyn Helper<Target=i32, Output=i32>> = Box::new(2u32);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// compile-pass

// Regression test related to #56288. Check that a supertrait projection (of
// `Output`) that references `Self` is ok if there is another occurence of
// the same supertrait that specifies the projection explicitly, even if
// the projection's associated type is not explicitly specified in the object type.
//
// Note that in order for this to compile, we need the `Self`-referencing projection
// to normalize fairly directly to a concrete type, otherwise the trait resolver
// will hate us.
//
// There is a test in `trait-object-with-self-in-projection-output-bad.rs` that
// having a normalizing, but `Self`-containing projection does not *by itself*
// allow you to avoid writing the projected type (`Output`, in this example)
// explicitly.

trait ConstI32 {
type Out;
}

impl<T: ?Sized> ConstI32 for T {
type Out = i32;
}

trait Base {
type Output;
}

trait NormalizingHelper: Base<Output=<Self as ConstI32>::Out> + Base<Output=i32> {
type Target;
}

impl Base for u32
{
type Output = i32;
}

impl NormalizingHelper for u32
{
type Target = i32;
}

fn main() {
// Make sure this works both with and without the associated type
// being specified.
let _x: Box<dyn NormalizingHelper<Target=i32>> = Box::new(2u32);
let _y: Box<dyn NormalizingHelper<Target=i32, Output=i32>> = Box::new(2u32);
}