min
09/14/2026, 7:09 AMsealed class Either<out L, out R> private constructor() {
class Foo<out L>(val foo: L): Either<L, Nothing>()
class Bar<out R>(val bar: R): Either<Nothing, R>()
}
sealed class Data private constructor() {
class A(val int: Int): Data()
class B(val string: String): Data()
}
(Either.Bar(Data.B("")) as? Either.Bar<Data.A>)?.let {
println("Either.Bar(Data.B(\"\")) is Either.Bar<Data.A>")
}
Why is the println statement executed?CLOVIS
09/14/2026, 7:11 AMas Either.Bar<Data.A> is actually as Either.Bar . And that's true, it's an Either.Bar ! So the cast is successful.
You should be getting a warning in your code:CLOVIS
09/14/2026, 7:14 AMEither.Bar<A> by Either.Bar<*> , which makes it explicit that the type parameter is not verified
(Either.Bar(Data.B("")) as? Either.Bar<*>)?.let {
println("Either.Bar(Data.B(\"\")) is Either.Bar<*>")
}min
09/14/2026, 7:14 AMCLOVIS
09/14/2026, 7:14 AMmin
09/14/2026, 7:14 AMCLOVIS
09/14/2026, 7:15 AMmin
09/14/2026, 7:16 AMas? as opposed to as in this particular caseCLOVIS
09/14/2026, 7:18 AMas was not a good idea in the first place hereCLOVIS
09/14/2026, 7:18 AMsealed class Either<out L, out R> private constructor() {
class Foo<out L>(val foo: L): Either<L, Nothing>()
class Bar<out R>(val bar: R): Either<Nothing, R>()
}
sealed class Data private constructor() {
class A(val int: Int): Data()
class B(val string: String): Data()
}
fun main() {
val b = Either.Bar(Data.B(""))
if (b.bar is Data.A) {
println("Either.Bar(Data.B(\"\")) is Either.Bar<A>")
}
}
Not only is the code simpler, but the compiler actually refuses to compile because it sees that it's dead:
Check for instance is always 'false'.CLOVIS
09/14/2026, 7:19 AMmin
09/14/2026, 7:19 AMwhen (/* ... */) {
is Either.Bar<Data.B> -> /* ... */
// ...
}
This didn’t work. I was exploring alternativesCLOVIS
09/14/2026, 7:23 AMis Either.Bar<Data.B> is just is Either.BarCLOVIS
09/14/2026, 7:26 AMis Foo<Bar> is always just is Foo , that's a limitation of the execution environment (not possible to do a real test on the JVM nor on JS)min
09/14/2026, 7:48 AMfun main() {
for (either in listOf<Either<Boolean, Choice>>(
Either.Foo(true),
Either.Bar(Choice.Case0(42)),
Either.Bar(Choice.Case1("")),
)) {
either.fooAs<Boolean>()?.let { foo ->
println("Either.Foo(${ foo })")
}
either.barAs<Choice.Case0>()?.let { bar ->
println("Either.Bar(Choice.Case0(${ <http://bar.int|bar.int> }))")
}
either.barAs<Choice.Case1>()?.let { bar ->
println("Either.Bar(Choice.Case1(${ bar.str }))")
}
}
}
sealed class Either<out L, out R> {
class Foo<out L>(val foo: L): Either<L, Nothing>()
class Bar<out R>(val bar: R): Either<Nothing, R>()
inline fun <reified T> fooAs(): T? {
return (this as? Either.Foo<*>)?.foo as? T
}
inline fun <reified T> barAs(): T? {
return (this as? Either.Bar<*>)?.bar as? T
}
}
sealed class Choice private constructor() {
class Case0(val int: Int): Choice()
class Case1(val str: String): Choice()
}
This is typesafe but no longer exhaustive.CLOVIS
09/14/2026, 7:54 AMCLOVIS
09/14/2026, 7:59 AMfun main() {
for (either in listOf<Either<Boolean, Choice>>(
Either.Foo(true),
Either.Bar(Choice.Case0(42)),
Either.Bar(Choice.Case1("")),
)) {
when (either) {
is Either.Foo if either.foo is Boolean ->
println("Either.Foo(${ either.foo })")
is Either.Bar if either.bar is Choice.Case0 ->
println("Either.Bar(Choice.Case0(${ <http://bar.int|bar.int> }))")
is Either.Bar if either.bar is Choice.Case1 ->
println("Either.Bar(Choice.Case1(${ bar.str }))")
}
}
}
Simpler, no need for reified , and it is exhaustivemin
09/14/2026, 8:06 AMwhen predicate can include an if clause?
fun main() {
for (either in listOf<Either<Boolean, Choice>>(
Either.Foo(true),
Either.Bar(Choice.Case0(42)),
Either.Bar(Choice.Case1("")),
)) {
println(when (either) {
is Either.Foo -> "Either.Foo(${ either.foo })"
is Either.Bar if either.bar is Choice.Case0
-> "Either.Bar(Choice.Case0(${ either.bar.int }))"
is Either.Bar if either.bar is Choice.Case1
-> "Either.Bar(Choice.Caes1(${ either.bar.str }))"
})
}
}
This fails to compile; I get the following error.
'when' expression must be exhaustive. Add the 'is Bar<*>' branch or an 'else' branch.
What am I doing wrong?CLOVIS
09/14/2026, 9:37 AMeither ?CLOVIS
09/14/2026, 9:38 AMCLOVIS
09/14/2026, 9:39 AMeither.bar is a sealed class so all branches are coveredmin
09/14/2026, 9:44 AMCLOVIS
09/14/2026, 9:49 AMCLOVIS
09/14/2026, 9:52 AMfor (either in listOf<Either<Boolean, Choice>>(
Either.Foo(true),
Either.Bar(Choice.Case0(42)),
Either.Bar(Choice.Case1("")),
)) {
println(
when (either) {
is Either.Foo -> "Either.Foo(${either.foo})"
is Either.Bar -> when (either.bar) {
is Choice.Case0 -> "Either.Bar(Choice.Case0(${<http://either.bar.int|either.bar.int>}))"
is Choice.Case1 -> "Either.Bar(Choice.Caes1(${either.bar.str}))"
}
}
)
}
but I'm pretty sure the compiler should realize it's the same codeYoussef Shoaib [MOD]
09/14/2026, 10:16 AMGiven that guards may include any expression, branches including a guard should be ignored for the purposes of exhaustiveness. If you need to ensure that you have covered every case when nested properties are involved, we recommend using nested conditionals instead.
```// don't do this
when (status) {
is Status.Error if status.problem == Problem.CONNECTION -> ...
is Status.Error if status.problem == Problem.AUTHENTICATION -> ...
is Status.Error if status.problem == Problem.UNKNOWN -> ...
// the compiler requires an additional 'is Status.Error' case
}
// better do this
when (status) {
is Status.Error -> when (status.problem) {
Problem.CONNECTION -> ...
Problem.AUTHENTICATION -> ...
Problem.UNKNOWN -> ...
}
}```
Marat Akhin
09/14/2026, 10:17 AMmin
09/14/2026, 10:34 AMMarat Akhin
09/14/2026, 10:35 AM