Unnecessary/broken check for abstract intermediary subclass of sealed class required in exhaustive when

202 Views Asked by At

I have a sealed class hierarchy like so:

sealed class Base

class A : Base()

abstract class B : Base()

class C : Base()
class D : Base()

Then I want to have an exhaustive when:

fun main() {
    val c: Base = C()
    
    when (c) {
        is A -> println("A")
        is C -> println("C")
        is D -> println("D")
    }.let {}
}

But this is not allowed because

'when' expression must be exhaustive, add necessary 'is B' branch or 'else' branch instead

But that's not true! I've covered all possibilities. There is no value that isn't matched by my when.

Furthermore, if I do add a is C clause:

when (c) {
    is A -> println("A")
    is B -> println("B")
    is C -> println("C")
    is D -> println("D")
}.let {}

It isn't actually called. C is printed.

This is actually a problem in the other direction, too. Say I want the same logic to handle all subclasses of B: C and D:

when (c) {
    is A -> println("A")
    is B -> println("some subclass of B")
}.let {}

That's also not allowed, even though it is exhaustive. What's going on here? Is this a bug in Kotlin? Am I missing something?

1

There are 1 best solutions below

0
On

Assuming you meant for C and D to be subclasses of B rather than Base, then this behavior is expected. The ability of the compiler to recognize an exhaustive when statement only extends to the direct children of the sealed class. C and D are not part of the sealed class contract. This works with your existing class setup if C and D extend B:

when (c) {
    is A -> println("A")
    is B -> println("B")
}.let {}

And if you make B a sealed class, just like A, your original code works and the compiler can recognize that it's exhaustive without including B in the when statement.