Rust: Using associated types in async fn type break typing

Created on 30 Apr 2019  路  10Comments  路  Source: rust-lang/rust

With the following code:

trait Trait {
    type Assoc;
}

async fn foo<T: Trait<Assoc = ()>>() -> T::Assoc {
    ()
}

comes the following error:

error[E0271]: type mismatch resolving `<impl std::future::Future as std::future::Future>::Output == <T as Trait>::Assoc`
 --> src/lib.rs:7:41
  |
7 | async fn foo<T: Trait<Assoc = ()>>() -> T::Assoc {
  |                                         ^^^^^^^^ expected (), found associated type
  |
  = note: expected type `()`
             found type `<T as Trait>::Assoc`
  = note: the return type of a function must have a statically known size

error: aborting due to previous error

(playground link)

However, this function compiles correctly without the async keyword.

A-async-await A-impl-trait AsyncAwait-Focus C-bug T-compiler

Most helpful comment

This also happens for existential type definitions:

#![feature(existential_type)]

trait Implemented {
    type Assoc;
}
impl<T> Implemented for T {
    type Assoc = u8;
}

trait Trait {
    type Out;
}

impl Trait for () {
    type Out = u8;
}

existential type Ex: Trait<Out = <() as Implemented>::Assoc>;

fn define() -> Ex {
    ()
}
error[E0271]: type mismatch resolving `<() as Trait>::Out == <() as Implemented>::Assoc`
  --> src/lib.rs:18:1
   |
18 | existential type Ex: Trait<Out = <() as Implemented>::Assoc>;
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected u8, found associated type
   |
   = note: expected type `u8`
              found type `<() as Implemented>::Assoc`
   = note: the return type of a function must have a statically known size

My fix only seems to turn this into a cycle error for some reason.

EDIT: Nevermind I just broke existential types in general lol

All 10 comments

You can trigger this without async/await using impl Trait:

struct Foo<T>(T);

trait FooLike { type Output; }
impl<T> FooLike for Foo<T> {
    type Output = T;
}


trait Trait {
    type Assoc;
}

fn foo<T: Trait<Assoc = ()>>() -> impl FooLike<Output = T::Assoc> {
    Foo(())
}
error[E0271]: type mismatch resolving `<Foo<()> as FooLike>::Output == <T as Trait>::Assoc`
  --> src/main.rs:13:35
   |
13 | fn foo<T: Trait<Assoc = ()>>() -> impl FooLike<Output = T::Assoc> {
   |                                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected (), found associated type
   |
   = note: expected type `()`
              found type `<T as Trait>::Assoc`
   = note: the return type of a function must have a statically known size

cc @oli-obk @nikomatsakis

Some more reduction in genericity and clearing up a few things:

#![feature(impl_trait_in_bindings)]

struct Foo;

trait FooLike { type Output; }
impl FooLike for Foo {
    type Output = u32;
}

trait Trait {
    type Assoc;
}

fn foo<T: Trait<Assoc = i32>>() {
    let _: impl FooLike<Output = T::Assoc> = Foo;
}
error[E0271]: type mismatch resolving `<Foo as FooLike>::Output == <T as Trait>::Assoc`
  --> src/main.rs:15:12
   |
15 |     let _: impl FooLike<Output = T::Assoc> = Foo;
   |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected u32, found associated type
   |
   = note: expected type `u32`
              found type `<T as Trait>::Assoc`
   = note: the return type of a function must have a statically known size

The problem is that T::Assoc = i32 is not know in impl trait items. It's also not related to the return position, but refers to any impl trait in the function. Outside of impl trait T::Assoc = i32 can be resolved without a problem, so it must be something related to the way impl trait reuses the generics of its parent.

It seems to be a missing normalization, most likely.

I have a local fix for @cramertj's reduction above, but https://github.com/rust-lang/rust/issues/61577 is making this rather difficult as it makes debug! output not work properly.

This also happens for existential type definitions:

#![feature(existential_type)]

trait Implemented {
    type Assoc;
}
impl<T> Implemented for T {
    type Assoc = u8;
}

trait Trait {
    type Out;
}

impl Trait for () {
    type Out = u8;
}

existential type Ex: Trait<Out = <() as Implemented>::Assoc>;

fn define() -> Ex {
    ()
}
error[E0271]: type mismatch resolving `<() as Trait>::Out == <() as Implemented>::Assoc`
  --> src/lib.rs:18:1
   |
18 | existential type Ex: Trait<Out = <() as Implemented>::Assoc>;
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected u8, found associated type
   |
   = note: expected type `u8`
              found type `<() as Implemented>::Assoc`
   = note: the return type of a function must have a statically known size

My fix only seems to turn this into a cycle error for some reason.

EDIT: Nevermind I just broke existential types in general lol

Marking as blocking, although I think there's minimal future compat risk. If push came to shove I personally would be ok with removing this from the blocking list. But then it looks like @jonas-schievink already fixed it =)

@jonas-schievink I think the problem is on this line of code:

https://github.com/rust-lang/rust/blob/8301de16dafc81a3b5d94aa0707ad83bdb56a599/src/librustc/infer/opaque_types/mod.rs#L344

in particular, we "instantiate" the predicates -- meaning, substitute in the values for their type parameters -- but we never normalize them. We should be able to invoke the normalize methods. Note that in this code we do have an infcx available, as well, in self. I think we should be able to invoke:

self.infcx.partially_normalize_associated_types_in(span, body-id, param_env, &predicates)

this returns an InferOk value, which contains obligations that must be pushed into self.obligations.

@nikomatsakis Hmm, that code is only invoked if opaque_defn.has_required_region_bounds is true, which I think wouldn't be the case here since there are no region bounds at all (right?).

I also noticed that self.obligations does not exist in this context (self is an &InferCtxt), and there's no FulfillmentContext nearby either.

However, another place where predicates_of.instantiate(..) is called without normalizing the result is here, further down in the same file:

https://github.com/rust-lang/rust/blob/088b987307b91612ab164026e1dcdd0129fdb62b/src/librustc/infer/opaque_types/mod.rs#L1039

...and there self.obligations does exist (as does self.infcx). Quick testing shows that normalizing there fixes this issue as well, and as a bonus also fixes the issue with existential type without ICEs!

@jonas-schievink good catch -- actually I think the lines you found are the ones I meant to direct you to in the first place. I see you have some other problems on the PR, looking now.

Was this page helpful?
0 / 5 - 0 ratings