Force compilation error with sealed classes

Solution 1:

The way to enforce exhaustive when is to make it an expression by using its value:

sealed class SealedClass {
    class First : SealedClass()
    class Second : SealedClass()
    class Third : SealedClass()
}

fun test(sealedClass: SealedClass) {
    val x = when (sealedClass) {
        is SealedClass.First -> doSomething()
        is SealedClass.Second -> doSomethingElse()
    }  // ERROR here

    // or

    when (sealedClass) {
        is SealedClass.First -> doSomething()
        is SealedClass.Second -> doSomethingElse()
    }.let {}  // ERROR here
}

Solution 2:

In inspiration by Voddan's answer, you can build a property called safe you can use:

val Any?.safe get() = Unit

To use:

when (sealedClass) {
    is SealedClass.First -> doSomething()
    is SealedClass.Second -> doSomethingElse()
}.safe

I think it provides a clearer message than just appending .let{} or assigning the result to a value.


There is an open issue on the Kotlin tracker which considers to support 'sealed whens'.

Solution 3:

Our approach avoids to have the function everywhere when auto-completing. With this solution you also have the when return type in compile time so you can continue using functions of the when return type.

Do exhaustive when (sealedClass) {
  is SealedClass.First -> doSomething()
  is SealedClass.Second -> doSomethingElse()
}

You can define this object like so:

object Do {
    inline infix fun<reified T> exhaustive(any: T?) = any
}

Solution 4:

We can create an extension property on type T with a name that helps explain the purpose

val <T> T.exhaustive: T
    get() = this

and then use it anywhere like

when (sealedClass) {
        is SealedClass.First -> doSomething()
        is SealedClass.Second -> doSomethingElse()
    }.exhaustive

It is readable, shows exactly what it does an will show an error if all cases are not covered. Read more here