I'm trying to understand a complex variance situat...
# getting-started
c
I'm trying to understand a complex variance situation;
Copy code
typealias NullableValue<T> = TypedValue<T, KClass<out T & Any>?>
typealias NonNullValue<T> = TypedValue<T & Any, KClass<out T & Any>>
typealias NullValue = TypedValue<Nothing?, Nothing?>

class TypedValue<T : Any?, out K : KClass<out T & Any>?>(
    val value: T,
    val type: K,
)

// Compiles, and I understand why:
val n1: NullableValue<Int> = TODO()
val n3: NonNullValue<Int> = TODO()
val n5: NullValue = TODO()

// Does not compile, and I understand why:
val n4: NonNullValue<Int?> = TODO()
However, there's this one, that I don't understand:
Copy code
val n2: NullableValue<Int?> = TODO()
Type argument is not within its bounds.
Expected:
Any
Found:
Int?
Why does it expect
Any
? From what I understand: •
NullableValue<Int?>
• is expanded to
TypedValue<Int?, KClass<out Int>?>
• To me, that seems to satisfy all type bounds. So, why is it forbidden?
p
Seems like compiler bug (smth with typealias) . Such code compiles:
Copy code
abstract class TypedValue<T : Any?, out K : KClass<out T & Any>?>

class TypedValue2<T> : TypedValue<T, KClass<out T & Any>?>()

val n1: TypedValue2<Int?>  = TODO()
d
Every time I use a language with Generics, I eventually end up wishing it had templates (ala C++) instead.
Using interfaces rather than typealiases, all of the lines compile:
Copy code
interface TypedValue<T : Any?, out K : KClass<out T & Any>?>
interface NullableValue<T> : TypedValue<T, KClass<out T & Any>?>
interface NonNullValue<T> : TypedValue<T & Any, KClass<out T & Any>>
interface NullValue : TypedValue<Nothing?, Nothing?>

val n1: NullableValue<Int> = TODO()
val n2: NullableValue<Int?> = TODO()
val n3: NonNullValue<Int> = TODO()
val n4: NonNullValue<Int?> = TODO()
val n5: NullValue = TODO()
Tangentially related, have you seen how jackson handles TypeReferences? (I think Spring has something similar). It allows you to create a reified token.