Multiple generic constraints not resolved with star-projection


#1

When declaring multiple generic constraints on a type with where and then using the type with star-projection, I expect the upper bound to be a combination of all the types in the constraint.
However, it turns out, that only the first declared bound is recognized there.
If you switch where T : A, T : B to where T : B, T : A, the compile error also moves to the a-usage :slight_smile:
See my example…

class GenericConstraintsTest {

    interface A {
        val a: String
    }

    open class B(val b: String)

    class C(override val a: String, b: String) : B(b), A

    open class D<T>(val d: T)
        where T : A, T : B

    fun foo(x: D<*>) {
        x.d.a
        x.d.b // compile error 

        // the below works!
        val y = D(C("a", "b"))
        y.d.a
        y.d.b
    }
}

Is this a bug?


#2

Well, I created https://youtrack.jetbrains.com/issue/KT-25007


#3

I think the problem has something to do with this:

[…] Only one upper bound can be specified inside the angle brackets. If the same type parameter needs more than one upper bound, we need a separate where-clause

See: https://kotlinlang.org/docs/reference/generics.html#upper-bounds

Seeing that explanation I can deduct that the JVM can only store one upper bound, so when you specify more than one using a where clause, only the first one is stored. This can be tested by changing the order of the upper bounds in the where clause:

First T:A then T:B

class GenericConstraintsTest {
    interface A {
        val a: String
    }

    open class B(val b: String)

    class C(override val a: String, b: String) : B(b), A

    //sampleStart
    open class D<T>(val d: T)
            where T : A, T : B // Only 'T:A' is taken into account

    fun foo(x: D<*>) {
        x.d.a
        x.d.b // compile error
        //sampleEnd

        // the below works!
        val y = D(C("a", "b"))
        y.d.a
        y.d.b
    }
}

First T:B then T:A

class GenericConstraintsTest {
    interface A {
        val a: String
    }

    open class B(val b: String)

    class C(override val a: String, b: String) : B(b), A
    
    //sampleStart
    open class D<T>(val d: T)
            where T : B, T : A // Only 'T:B' is taken into account

    fun foo(x: D<*>) {
        x.d.a // compile error
        x.d.b
        //sampleEnd
        
        // the below works!
        val y = D(C("a", "b"))
        y.d.a
        y.d.b
    }
}

Note: Click the play button to see the compilation error in the kotlin playground.

Disclaimer: I have no technical knowledge about the JVM or how it works, this is all based on some test I have done. Maybe I am completely wrong, or maybe not. :sweat_smile: