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

Kotlin @Binds error with covariant type parameter #1143

Closed
fondesa opened this issue Apr 15, 2018 · 8 comments
Closed

Kotlin @Binds error with covariant type parameter #1143

fondesa opened this issue Apr 15, 2018 · 8 comments

Comments

@fondesa
Copy link

fondesa commented Apr 15, 2018

Given the interface:

interface Example<T>

The implementation:

class ExampleImpl: Example<List<String>>

The module:

@Module
interface ExampleModule {
    @Binds
    fun provideExample(example: ExampleImpl): Example<List<String>>
}

The error @Binds methods must have only one parameter whose type is assignable to the return type is thrown at compile-time.

Important notes

  1. it happens only with @Binds and not with @Provides
    So, this works:
@Module
class ExampleModule {
    @Provides
    fun provideExample(example: ExampleImpl): Example<List<String>> = example
}
  1. it happens only when T is used as a covariant type parameter in the implementation of Example.
    So, this works:
class ExampleImpl: Example<MutableList<String>>

@Module
interface ExampleModule {
    @Binds
    fun provideExample(example: ExampleImpl): Example<MutableList<String>>
}
@ronshapiro
Copy link

This seems relevant for #900. Probably has something to do with Kotlin's declaration site variance. The quick fix is probably to use @JvmSuppressWildcards but let's keep this open until we have a better solution.

@fondesa
Copy link
Author

fondesa commented Apr 16, 2018

I tried with @JvmSuppressWildcards but it doesn't help in this case.
It's still needed using the @Provides annotation.

Example:

@Module
class ExampleModule {
    @Provides
    fun provideExample(example: ExampleImpl): Example<List<String>> = example
}

class Consumer @Inject constructor(private val example: @JvmSuppressWildcards Example<List<String>>)

@JakeWharton
Copy link

You might also need it on the bind. The bytecode window will show the signature of both places.

@fondesa
Copy link
Author

fondesa commented Apr 16, 2018

I tried to add it also on the bind with no success.
Decompiling the bytecode, I obtain:

Consumer
signature Lcom/package/Example<Ljava/util/List<Ljava/lang/String;>;>;
declaration: com.package.Example<java.util.List<java.lang.String>>

ExampleImpl
signature Ljava/lang/Object;Lcom/package/Example<Ljava/util/List<+Ljava/lang/String;>;>;
declaration: com/package/ExampleImpl implements com.package.Example<java.util.List<? extends java.lang.String>>

Module
signature (Lcom/package/ExampleImpl;)Lcom/package/Example<Ljava/util/List<Ljava/lang/String;>;>;
declaration: com.package.Example<java.util.List<java.lang.String>> provideExample(com.package.ExampleImpl)

The only way I avoided the error @Binds methods must have only one parameter whose type is assignable to the return type is declaring the implementation as:

class ExampleImpl : Example<List<@JvmSuppressWildcards String>>

But I can't inject it anymore.

After this last change, I obtain:

Consumer
signature Lcom/package/Example<Ljava/util/List<Ljava/lang/String;>;>;
declaration: com.package.Example<java.util.List<java.lang.String>>

ExampleImpl
signature Ljava/lang/Object;Lcom/package/Example<Ljava/util/List<Ljava/lang/String;>;>;
declaration: com/package/ExampleImpl implements com.package.Example<java.util.List<java.lang.String>>

Module
signature (Lcom/package/ExampleImpl;)Lcom/package/Example<Ljava/util/List<Ljava/lang/String;>;>;
declaration: com.package.Example<java.util.List<java.lang.String>> provideExample(com.package.ExampleImpl)

The types seems to conform after the last change, but it doesn't work yet.

@JakeWharton
Copy link

Ok. I'd like to try it out and see what the bytecode says, hopefully tonight. In theory nothing should prevent you from making it work, we just have to figure out what is being generated and how to normalize it all to the right type declarations.

@fondesa
Copy link
Author

fondesa commented Apr 16, 2018

I updated my comment with further information.

@fondesa
Copy link
Author

fondesa commented Apr 16, 2018

My last try was the right one, simply I forgot the @Inject annotation on the constructor of ExampleImpl.
The following summary is for people who encounter the same problem.
Thanks for your help, considering that now it works, I close the issue, feel free to re-open it if needed.

Short summary

Interface

interface Example<T>

Implementation

class ExampleImpl @Inject constructor() : Example<List<@JvmSuppressWildcards String>>

Module

@Module
interface ExampleModule {
    @Binds
    fun provideExample(example: ExampleImpl): Example<List<String>>
}

Consumer

class Consumer @Inject constructor(private val example: @JvmSuppressWildcards Example<List<String>>)

@bcorso
Copy link

bcorso commented Jul 8, 2020

Closing this since it looks like #1143 (comment) has the solution.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants