Smart casting and generics with multiple inheritance

interface Runner
interface Jumper
interface Swimmer
interface Diver

fun <T> testAllAthleticism(t: T) where T : Runner, T : Jumper, T : Swimmer, T : Diver {}
fun <T> testGroundAthleticism(t: T) where T : Runner, T : Jumper {}
fun <T> testWaterAthleticism(t: T) where T : Swimmer, T : Diver {}
fun <T> testSwimming(t: T) where T : Swimmer {}

fun <T> runTest(t: T) {
    when {
        t is Runner && t is Jumper && t is Swimmer && t is Diver -> testAllAthleticism(t) // compiler error
        t is Runner && t is Jumper -> testGroundAthleticism(t) // compiler error
        t is Swimmer && t is Diver -> testWaterAthleticism(t) // compiler error
        t is Swimmer -> testSwimming(t) // works

I can’t figure out how to get this kind of code to work, if I understand correctly this might just be a short coming of the capabilities of the compiler in regards to smart casting, which is understandable.

Is there anyway I can unsafely cast this? I couldn’t find out how to do multiple as casts to make this work.

Are you running in Kotlin/JVM? As far as I know, the JVM erases types of genertics, so you would need to make your function inline for this to be testable, something like:

inline fun <reified T> runTest(t: T)

I’m not a Kotlin expert though so maybe that’s not your problem.

I gave that a try just in case, but it didn’t work.

IIRC, type erasure is just about runtime, types are erased to improve performance or something like that.

Reification of generic types just allows you to access that data at runtime instead of erasing it, I think.

Edit: and yes, I’m on the JVM.

I can’t think of a way this might be done if reified types don’t work. Maybe try the new type inference system (compiler argument XXLanguage:+NewInference to enable it).
If it still does not work (even with reified types) you should probably create an issue

Until Java 8, this was also impossible in Java. Java 8 introduced syntax to cast to multiple interfaces:

testWaterAthleticism((Swimmer & Diver) t);

Unfortunately, Kotlin does not have any similar feature to my knowledge.

I did find a rather hacky workaround, however:

fun <T> runTest(t: T) {
    when {
        t is Runner && t is Jumper && t is Swimmer && t is Diver -> {
            fun <S> foo() where S: Runner, S: Jumper, S: Swimmer, S: Diver {
                testAllAthleticism(t as S)

Note that foo does not necessarily have to be local. It does, however, have to be generic and S must not be reified, as we rely on the cast to S be unchecked.

1 Like

What you want is generic type specialization with appropriate overloading resolution. I’m not aware of the JVM supporting this.

However, one idea would be to pull all interface methods from Runner, Jumper and Swimmer to A, from Runner and Jumper to B, and least from Swimmer and Driver to C and change the signatures of your methods as follows:

fun <T> testAllAthleticism(t: A)
fun <T> testGroundAthleticism(t: B)
fun <T> testWaterAthleticism(t: C)
fun <T> testSwimming(t: Swimmer)

Note, that A,B,C must be implemented by the appropriate interfaces.
However, in my eyes, this solution is a very ugly one.