Why type can't be inferred for base class or interface? Kotlin 1.4

fun main() {
    val typeB = convert(C())
}

fun convert(type: A): B? {
    return if (type is C || type is D) {
        type // ->
    } else {
        null
    }
}

interface A
open class B: A // can be interface
class C: B()
class D: B()

Why am I getting this error: Type mismatch: inferred type is A but B? was expected
Or try it here: https://pl.kotl.in/d5eVBKkDU

1 Like

Looks like a simplest reproducer. Do you have any real-world use cases? I can’t imagine one and I’m not surprised it’s not handled by Kotlin type inference.

If you expect to see type “B” in a branch, why not write an explicit check for type “B”? Besides being more explicit, it is also more readable and won’t cause any type inference problems.

Also note, Kotlin cannot make any guarantees on the class hierarchy unless base classes explicitly marked as sealed.

It seems Kotlin compiler does not support smart casts for conditions combined with or.
It works when you have a single condition or when conditions are and-combined:

fun main() {
    println(convert(C()))
}

fun convert(type: Any): B? {
    if (type is C && type is D) {
        return type
    } else if (type is C && type is Int) {
        return type
    } else if (type is C) {
        return type
    } else if (type is D) {
        return type
    } else {
        return null
    }
}

interface A
open class B: A // can be `interface B: A`
class C: B()
class D: B()

This issue was already reported some time ago: https://youtrack.jetbrains.com/issue/KT-1982
However it doesn’t seem to be popular or have a lot of use cases, so I wouldn’t expect this anytime soon.
Still, if you need it, I’d recommend to upvote the issue and describe your use case in the comment.

3 Likes

Yes it is from real example. Can’t just use type B because there are other cases of B class/interface inheritance:

return when (type) {
    is C, is D -> type // doesn't work
    is E -> update(type)
    is F -> type.copy(...)
    else -> null 
}

Of course, there is a workaround:

return when {
    type is B && (type is C || type is D) -> type // or is C, is D -> type as B
    type is E -> update(type)
    type is F -> type.copy(...)
    else -> null 
}

but it doesn’t look concise