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

Using associated types in async fn type break typing #60414

Closed
Ekleog opened this issue Apr 30, 2019 · 10 comments
Closed

Using associated types in async fn type break typing #60414

Ekleog opened this issue Apr 30, 2019 · 10 comments

Comments

@Ekleog
Copy link

@Ekleog Ekleog commented Apr 30, 2019

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.

@Ekleog
Copy link
Author

@Ekleog Ekleog commented Apr 30, 2019

cc #50547

@cramertj
Copy link
Member

@cramertj cramertj commented Apr 30, 2019

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

@oli-obk
Copy link
Contributor

@oli-obk oli-obk commented May 2, 2019

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.

@nikomatsakis
Copy link
Contributor

@nikomatsakis nikomatsakis commented Jun 11, 2019

It seems to be a missing normalization, most likely.

@jonas-schievink
Copy link
Member

@jonas-schievink jonas-schievink commented Jun 26, 2019

I have a local fix for @cramertj's reduction above, but #61577 is making this rather difficult as it makes debug! output not work properly.

@jonas-schievink jonas-schievink self-assigned this Jun 26, 2019
@jonas-schievink
Copy link
Member

@jonas-schievink jonas-schievink commented Jun 27, 2019

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

@nikomatsakis
Copy link
Contributor

@nikomatsakis nikomatsakis commented Jul 2, 2019

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 =)

@nikomatsakis
Copy link
Contributor

@nikomatsakis nikomatsakis commented Jul 3, 2019

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

let bounds = predicates_of.instantiate(tcx, opaque_defn.substs);

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.

@jonas-schievink
Copy link
Member

@jonas-schievink jonas-schievink commented Jul 3, 2019

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

let bounds = predicates_of.instantiate(tcx, substs);

...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!

@nikomatsakis
Copy link
Contributor

@nikomatsakis nikomatsakis commented Jul 5, 2019

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

bors added a commit that referenced this issue Jul 9, 2019
…tsakis

Normalize projections appearing in `impl Trait`

Fixes #60414

This does not try to do the same for `existential type`s (which have the same bug), since that always seems to lead to cycle errors.
@bors bors closed this in #62221 Jul 9, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

6 participants