3

So in the following code I get compilation error that "when must be exhaustive add necessary else":

class Test {

}

fun eval(e: Test): Int =
        when(e) {
            is Test -> throw IllegalArgumentException()
        }

To clarify this is only code aimed to understand Kotlin (newbie here).
So Test can not be extended by any subclass by default behaviour of Kotlin.
So what are other cases that the when expects?
Using sealed also does not work in this case

Jim
  • 3,845
  • 3
  • 22
  • 47
  • 3
    Wild guesses: 1) The code that checks for exhaustivity isn't checking for conditions like this because there are better alternatives (`if`) and it wasn't worth the effort. Or 2) Just because `Test` isn't open now, doesn't mean it won't be in the future. Meaning, if `eval()` is compiled and delivered, and then somebody opens `Test` and subclasses it, `eval()` will fail. – Todd Jul 26 '19 at 12:41
  • @Todd: Concerning (2) why is `eval` failing bad? I would say this is a good as the change in behavior (making it open) would be caught at compile time. – Jim Jul 26 '19 at 13:30
  • @Jim I didn't get that issue with sealed classes, i.e. listing all possible values is considered to be exhaustive. That's with Kotlin 1.3.41, FYI – user2340612 Jul 26 '19 at 14:50
  • @user2340612: Did you copy paste the code as I have it and used the keyword `sealed`? – Jim Jul 26 '19 at 15:00
  • @user2340612: Also how do I check which version of Kotlin my IDE uses? – Jim Jul 26 '19 at 15:00
  • @Jim not really.. I created a `sealed class Response` with 1 child `class Success : Response()`. Then `when(response) { is Success -> TODO() } ` is considered to be exhaustive. To check Kotlin version (assuming you're using IntelliJ IDE) just build your project, and in `Messages` tab (at the bottom) you'll see the version of `kotlinc`: something like `kotlinc-jvm 1.3.41` – user2340612 Jul 26 '19 at 15:04
  • @user2340612: Yes but what you did is not what I have in my post. That would work because of `Success`. Try removing `Success` – Jim Jul 26 '19 at 17:36
  • @Jim yeah I understand what you mean, in that case it won't work indeed. I don't know what's the reason behind that design decision, you should ask Jetbrains :-) – user2340612 Jul 26 '19 at 18:13

4 Answers4

2

Adding sealed without other changes is pointless: it makes the class abstract and impossible to subclass, so you'll never have an instance. I believe this is a compiler bug, it's very similar to https://youtrack.jetbrains.com/issue/KT-28249 though with class instead of object.

It's also a low-impact one, because if is Test is the only branch, you can just replace the entire when with the branch, and if it isn't, you can replace is Test with else.

Alexey Romanov
  • 167,066
  • 35
  • 309
  • 487
0

class Test

Because when needs default value for return, if the elements (e) is not matched anything it should be return default value

class Test

fun eval(e: Test): Int =
when (e) {
    is Test -> throw IllegalArgumentException()
    else -> 7 //default value
}

other option is change return type as nullable in your function

fun eval(e: Test): Int? =
when (e) {
    is Test -> throw IllegalArgumentException()
    else -> null
        }

when

If when is used as an expression, else branch is mandatory, unless the compiler can prove that all possible cases are covered with branch conditions

.

for more details refer when-expression

if elements is single no point of use when. your function may be simplified as below

fun eval(e: Test): Int = throw IllegalArgumentException() 
sasikumar
  • 12,540
  • 3
  • 28
  • 48
  • But that is my question. Since `Test` is not possible to be sub classed what could fall in the `else` branch? – Jim Jul 26 '19 at 12:15
  • yes your are correct.. Thats case no needs to use 'when' use 'if' instead of that. – sasikumar Jul 26 '19 at 12:17
  • But my question is why `when` can not handle this? Is there some type that can be replaced that I am not thinking? – Jim Jul 26 '19 at 12:19
  • you want throw exception ? – sasikumar Jul 26 '19 at 12:21
  • No. My question is not how to fix the code. My question is why does `when` get confused here as there are no other sub types. Or are there? I am trying to understand `when` and classes in `Kotlin` – Jim Jul 26 '19 at 12:23
  • 1
    Please check @Todd's comments. It seems my question is not clear for everyone – Jim Jul 26 '19 at 14:03
0

The problem is that when have to be exhaustive when you want to use it in return/assign statement.

For example:

// let k be of Any? type:
when (k) {
    is Number -> { println("Ok, k is a Number") }
}

This is OK. You don't expect any result. If k is a number then something will be printed. Otherwise, nothing will happen.

So, the second example:

// k is the same
boolean isNumber = when (k) {
    is Number -> true;
}

But, what if boolean is not a number? Code will not compile, because your program would be in indefinite state.

So, what you can do? Your when have to be exhaustive. So, just add else. One of the pattern is, for example, to throw IllegalStateException() if case like your.

fun eval(e: Test): Int = when(e) {
    is Test -> 1
    else -> throw IllegalArgumentException("This should never happen!")
}

Edit:

Regarding to your comment. e actually can be something different than Test. It can be... Nothing.

I am completely aware about possibilities when it can happen and correctness of usage things like this. But, example below is completely OK and compiles without any problem. Maybe with reflection it would have some useful usage.

class Test

fun eval(e: Test): Int =
    when(e) {
        is Test -> throw IllegalArgumentException()
        else -> throw IllegalArgumentException()
    }

fun getNothing(): Nothing {
    throw IllegalStateException()
}

fun main() {
    eval(getNothing())
}
Cililing
  • 4,303
  • 1
  • 17
  • 35
  • 1
    Exhaustivity is understood. What isn't understood is why his `eval()` isn't exhaustive if `Test` isn't open for extension. In his example, `e` can't be anything other than `Test`. – Todd Jul 26 '19 at 12:55
  • @Todd: Exactly! – Jim Jul 26 '19 at 13:28
  • I've added a little example how (in theory) pass something different than `Test` there (I know that this code always will execute with runtime fail). – Cililing Aug 05 '19 at 08:25
0

So what are other cases that the when expects? - "else" because Test is not sealed class.

The error shows clearly that you need to add else condition, i tried to return 0 in else condition since the function return Integer and error gone.

class Test {

}

fun eval(e: Test): Int =
    when(e) {
        is Test -> throw IllegalArgumentException()
        else -> 0 
    }

or you need to assign to var or val and return Int value as below,

fun eval(e: Test): Int {
    val i: Int = when (e) {
        is Test -> throw IllegalArgumentException()
        else -> 0
    }
    return i
}
prostý člověk
  • 909
  • 11
  • 29