Why can't sealed class be exhausted with upper bound generics?


#1

See the code below:

sealed class A
class B : A()
class C : A()

fun <T : A> f(a: T): T {
  return when (a) {//Error: 'when' expression must be exhaustive, add necessary 'else' branch
    is B -> a
    is C -> a
  }
}

a is of type T which is upper bounded by A, so a’s type should be subset of B and ‘C’, why the compiler still complains about exhaustiveness?


#2

probably because of the same reason the following code doesn’t work:

class A
fun <T : A> f(foo : T){
    val i = when(foo){
        is A -> 1
    }
}

thx @fatjoe79 for pointing out that my speculation below is not true.

My speculation:
Generics only exist at compile-time and not at runtime (at runtime they are replaced with Any?).
Probably the check if the when is exhaustive is done after the generics are replaced with Any??


#3

But in this case the generic parameter is not erased to Any? Because of the upper bound it will be erased to A, won’t it?

In any way, we are talking about a compile-time error. It should be possible to evaluate this error BEFORE generics are erased.

All in all I believe it should be possible to fix this theoretically. But I don’t know how complicated it is to do.


#5

Even stranger: if you add a third ‘is A -> a’ branch to the when, then the compiler recognises that that condition is always true, but still complains that it’s not exhaustive!


#6

Casting to A works as a workaround (if you really need this):

sealed class A
class B : A()
class C : A()

fun <T : A> f(a: T): T {
  return when (a as A) {
    is B -> a
    is C -> a
  }
}

I think the compiler does not treat a as a A when you use upper bound (btw, you can have several upperbound so its more complex).

This is a minor compiler bug to me (and it looks like likned to type erasure).