Why can't Kotlin smart cast between an interface and the generic type that is derived from it?

1.2k Views Asked by At

I have the following class:

abstract class PresenterActivity<S : ViewState, I : ViewIntent> : AppCompatActivity() { 
    open fun initViewIntent(): I {
        return object : ViewIntent{} // type mismatch on this line
    }
}

I receive a pre-compilation error stating:

Type mismatch
Required: I
Found: <S, I>

To fix this pre-compilation error I am casting the ViewIntent object to I:

abstract class PresenterActivity<S : ViewState, I : ViewIntent> : AppCompatActivity() { 
    open fun initViewIntent(): I {
        @Suppress("UNCHECKED_CAST")
        return object : ViewIntent{} as I
    }
}

But why can't Kotlin detect that I must be derived from ViewIntent and smart cast it?

3

There are 3 best solutions below

0
Raymond Arteaga On BEST ANSWER

It's just because "I" is NOT necessarily derived from ViewIntent, but exactly ViewIntent class.

You can fix it like this:

abstract class PresenterActivity<S : ViewState, I : ViewIntent> : AppCompatActivity() { 
    open fun initViewIntent(): ViewIntent {
        return object : ViewIntent{} 
    }
}

Doing it your way is really unsafe.

To understand why, I guess you should start reading this:

https://blog.kotlin-academy.com/kotlin-generics-variance-modifiers-36b82c7caa39

https://kotlinlang.org/docs/reference/generics.html

https://proandroiddev.com/understanding-generics-and-variance-in-kotlin-714c14564c47

0
Egor Neliuba On

That's because ViewIntent isn't I. See example:

class MyViewIntent : ViewIntent

class MyPresenterActivity : PresenterActivity<..., MyViewIntent>() {
    // inherited from PresenterActivity
    open fun initViewIntent(): ViewIntent {
        return object : ViewIntent{} as MyViewIntent // you see where this breaks
    }
}
0
Jacob Zimmerman On

Basically, the reason why what you're doing doesn't work is because whatever I is is a subclass of ViewIntent. Your object is also a subclass ViewIntent. It's a completely different subclass. The cast you're doing is like trying to cast StringBuilder into a String.

Now let's discuss what I think you "want" to do and why that doesn't work either. In order to really get the result you want, you need to create the I type directly, like this:

return object : I {}

And in we replaced that I with an actual class,

return object : SomeClass {}

this would certainly fail, too. SomeClass's constructor needs to be called, and you're not doing it. And there's no way to know what to pass into that constructor when using the generic type.