min
09/12/2026, 9:18 AMwhen (val op = MyAnnotationOperand.from(declaration.symbol, context.session)) {
is Either.Error<MyAnnotationOperand.FactoryError.NotAnnotatedClass> -> TODO()
is Either.Error<MyAnnotationOperand.FactoryError.MissingArgument> -> TODO()
is Either.Error<MyAnnotationOperand.FactoryError.InvalidArgument> -> TODO()
is Either.Happy<MyAnnotationOperand> -> TODO()
}
How can I make this work?min
09/12/2026, 9:18 AMsealed class Either<out Y, out N> {
class Happy<out Y>(val happy: Y): Either<Y, Nothing>()
class Error<out N>(val error: N): Either<Nothing, N>()
}
This is the Either type in use.min
09/12/2026, 9:20 AMinline fun <T: Any> T?.coalesce(fallback: () -> T): T {
return this ?: fallback()
}
private class MyAnnotationOperand private constructor(
val annotatedClass: FirClassSymbol<*>,
val annotationArgument: ClassId,
) {
sealed class FactoryError {
object NotAnnotatedClass: FactoryError()
object MissingArgument: FactoryError()
object InvalidArgument: FactoryError()
}
companion object {
fun from(
maybeAnnotatedClass: FirClassSymbol<*>,
session: FirSession,
): Either<MyAnnotationOperand, FactoryError> {
return Either.Happy(MyAnnotationOperand(
fencedClass = maybeAnnotatedClass,
fenceArgument = maybeAnnotatedClass
.getAnnotationByClassId(
classId = ANNOTATION_CLASS,
session = session,
)
.coalesce { return Either.Error(FactoryError.NotAnnotatedClass) }
.getKClassArgument(ANNOTATION_ARG)
.coalesce { return Either.Error(FactoryError.MissingArgument) }
.classId
.coalesce { return Either.Error(FactoryError.InvalidArgument) }
))
}
}
}
And this is MyAnnotationOperand.fromYoussef Shoaib [MOD]
09/12/2026, 10:23 AMwhen (val op = MyAnnotationOperand.from(declaration.symbol, context.session)) {
is Either.Error -> when (op.error) {
MyAnnotationOperand.FactoryError.NotAnnotatedClass -> TODO()
MyAnnotationOperand.FactoryError.MissingArgument -> TODO()
MyAnnotationOperand.FactoryError.InvalidArgument -> TODO()
}
is Either.Happy -> TODO()
}
This would get better if we have Rich Errorsmin
09/13/2026, 10:05 AMwhen so the nesting is unnecessary?min
09/13/2026, 10:07 AMYoussef Shoaib [MOD]
09/13/2026, 6:13 PMis Foo<...> cannot be done in general.
Your case specifically allows for that, sure, but it's still unclear how the compiler would know to look at error or happy to check the type.
Some improved when could do the trick, sure, but that hasn't been designed yet.
If when had some kind of pattern matching capability, then you could do:
// imaginary syntax
when (val op = MyAnnotationOperand.from(declaration.symbol, context.session)) {
matches Either.Error(error is MyAnnotationOperand.FactoryError.NotAnnotatedClass) -> TODO()
matches Either.Error(error is MyAnnotationOperand.FactoryError.MissingArgument) -> TODO()
matches Either.Error(error is MyAnnotationOperand.FactoryError.InvalidArgument) -> TODO()
is Either.Happy -> TODO()
}
but again, a fleshed out design for pattern matching hasn't happened yet, although gradual improvements are happening (e.g. when guards)
FWIW, there is an alternative solution using contracts that I forgot to mention:
import kotlin.contracts.*
// `N: Any` to avoid `Either.Error(null)` being confused with `Either.Happy`
fun <Y, N: Any> Either<Y, N>.errorOrNull(): N? {
contract {
returns(null) implies (this@errorOrNull is Either.Happy)
returnsNotNull() implies (this@errorOrNull is Either.Error)
}
return when (this) {
is Either.Happy -> null
is Either.Error -> error
}
}
fun main() {
val op = MyAnnotationOperand.from()
when (op.errorOrNull()) {
is MyAnnotationOperand.FactoryError.NotAnnotatedClass -> TODO()
is MyAnnotationOperand.FactoryError.MissingArgument -> TODO()
is MyAnnotationOperand.FactoryError.InvalidArgument -> TODO()
null -> op.happy // smart cast to Either.Happy
}
}
you still get the exhaustivity for errors, and the happy path gets a smart cast too.min
09/14/2026, 8:28 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 Choice private constructor() {
class Case0(val int: Int): Choice()
class Case1(val str: String): Choice()
}
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 }))"
})
}
}
But I get the following compiler error:
'when' expression must be exhaustive. Add the 'is Bar<*>' branch or an 'else' branch.
Are you aware of a way around this?Youssef Shoaib [MOD]
09/14/2026, 10:15 AMwhen, or the errorOrNull pattern with contracts mentioned above