Why Kotlin doesn't allow use-site variance with the interface


#1

I want to create a source like this:

interface IA{}
interface IB{}
class A{}
abstract class C extends A implements IA {}
abstract class D extends C implements IB {}
class E extends D implements IA, IB {}

class B <T extends A & IA> {}

class Test {
	B<? extends IB> t = new B<E>();
}

but I can’t write this code.

var t: B<out IB> = B<E>()

I want to know why to this is not allowed.


#2

First, I’m going to convert your question into Kotlin:

interface IA
interface IB
open class A
abstract class C : A(), IA
abstract class D : C(), IB

open class E : D(), IA, IB

class B<T> where T : A, T : IA

class Test {
    fun foo(): B<out IB> = B<E>()
    //     ERROR ^^^^^^
}

Now, let’s examine the error message on out IB:

Type argument is not within bounds.
Expected: A
Found: IB
Type argument is not within bounds.
Expected: IA
Found: IB

The problem is that this parameter is required to implement both A and IA, which is what you specified in the where clause (intersection type). But IB implements neither. If you say out E it’s fine and does not require the explicit cast to E:

    val t: B<out E> = B()

This was an interesting brain teaser that got me to learn about the where keyword - I didn’t even know that Kotlin had intersection types!


#3

I want to add variance. because Test.t will set B with a type that doesn’t extend both C and D, extends another subtype of A, and implements IA and IB.
Now, In my code, we write subtype of B and add variance with where. But I think it is not good to read. So I want to know why to couldn’t add variance like Java or how to pass this problem.


#4

It doesn’t (yet). Your example of where T : IA, T : A1 is an intersection type, isn’t it?


Improve online Kotlin documentation for the `where` clause
#5

I think so. where T : IA, T : A1 is an intersection type.

this mean make an intersection type with where T : IA, T : A1, T : IB


#6

@gidds and @anatawa12 - you’re both right - thank you! I’ve suggested that the documentation be expanded. I’ll edit my post so I don’t confuse someone else.


#7

I think you can simplify your whole example to:

    interface IA{}
    interface IB{}

    class B <T extends IA> {}

    class Test {
        B<? extends IB> t = new B<>();
    }

I’m still missing something between these two lines:

class B <T extends IA> {}
B<? extends IB>

The first line says that the generic type parameter to class B must extend/implement/be an IA. The second line arbitrarily redefines what types a B class can take says that B’s type parameter will be from a totally unrelated interface IB.

This looks like a contradiction to me. I understand that you can “make it work out” with an E class implementing IA and IB, but that doesn’t remove the contradiction. I think due to type erasure that if it compiles, it will run fine. It still seems to me you may have found a bug in Java generics.