Casts of generic classes in mutableMap - kotlin

In my application I want to do something like this:
interface Binder<in VB: ViewBinding, in T: Any> {
fun bind(binding: VB, item: T)
}
class TypeInfoMap {
val map = mutableMapOf<Class<out Any>, Binder<ViewBinding, Any>>()
inline fun <reified VB: ViewBinding, reified T: Any> put(binder: Binder<VB, T>) {
map[T::class.java] = binder as Binder<ViewBinding, Any> // warning unchecked cast
}
inline fun <reified VB: ViewBinding, reified T: Any> get(cls: Class<T>): Binder<VB, T> {
return map[cls] as? Binder<VB, T> ?: throw IllegalStateException() // no warning
}
}
I get the warning unchecked cast in the put function. Why is that? I declared upper bounds for the generic types, shouldn't the cast be fine here? Also the cast in the get function does not produce any warning, even when I don't inline the function. I would have thought I would get a warning here and I'm actually surprised that I don't get one.
Is there a way in Kotlin to write all of this without warnings? I thought that's what reified is for.

First of all, it is incorrect to cast from Binder<VB, T> to Binder<ViewBinding, Any>. If binder is defined as a Binder<VB, T>, you can call binder.bind() with a VB instance, but not with ViewBinding instances that are not VBs. So a Binder<VB, T> is not a Binder<ViewBinding, Any>.
Second, the unchecked cast warning is not about whether the cast is valid or not. It's about the fact that you won't get a ClassCastException at runtime if the type is not correct. This is why it's dangerous.
You probably don't get an unchecked cast warning in the get() method because the cast is always valid anyway: a Binder<ViewBinding, Any> is always a Binder<VB, T> given the variance of Binder and the declared parent types of VB and T.
Is there a way in Kotlin to write all of this without warnings? I thought that's what reified is for.
reified allows to access the generic types at runtime, but only those of the reified function. So for instance they allow you to get the KClass of some instance without explicitly passing it. However, the internal map you're using will still give no information at runtime about what you put in it in the past.
The best you can do is ignore the unchecked cast warning because you won't be able to know the generic types contained in the map at runtime. However, you have a more type-safe approach if you make the map private because you can control what you put in it:
class TypeInfoMap {
private val map = mutableMapOf<Key<*, *>, Binder<*, *>>()
class Key<VB : ViewBinding, T : Any>(
val bindingClass: KClass<VB>,
val valueClass: KClass<T>,
)
fun <VB : ViewBinding, T : Any> put(key: Key<VB, T>, binder: Binder<VB, T>) {
map[key] = binder
}
#Suppress("UNCHECKED_CAST") // types are guaranteed by put()
fun <VB : ViewBinding, T : Any> get(key: Key<VB, T>): Binder<VB, T> {
val binder = map[key] ?: error("No binding of type ${key.bindingClass} found for class ${key.valueClass}")
return binder as Binder<VB, T>
}
inline fun <reified VB: ViewBinding, reified T: Any> put(binder: Binder<VB, T>) {
put(Key(VB::class, T::class), binder)
}
inline fun <reified VB: ViewBinding, reified T: Any> get(): Binder<VB, T> = get(Key(VB::class, T::class))
}
You can then use it safely with nice reified types:
val infoMap = TypeInfoMap()
val someBinder: Binder<MyViewBinding, MyType> = createSomeBinderSomewhere()
infoMap.put(someBinder)
// guaranteed type here (or runtime error if no binding of those types is found)
val binder = infoMap.get<MyViewBinding, MyType>()

Related

How can I fix Kotlin Overload Resolution Ambiguity with extension functions and type bounds

I want to define an Option type, and then pseudo-polymorphically define a getOrNull operation.
sealed interface Option<out T>
data class Some<out T>(val value: T): Option<T>
object None: Option<Nothing>
fun <T> Option<T>.getOrNull(): T? = when (this) {
is Some<T> -> getOrNull()
is None -> getOrNull()
}
fun <T> Some<T>.getOrNull(): T = value
fun None.getOrNull(): Nothing? = null
This all works fine.
But if I tighten the generic type to disallow Option(null) by specifying T: Any
fun <T: Any> Option<T>.getOrNull(): T? = when (this) {
is Some<T> -> getOrNull()
is None -> getOrNull()
}
then I get
Kotlin: Overload resolution ambiguity:
public fun <T : Any> Option<TypeVariable(T)>.getOrNull(): TypeVariable(T)? defined in root package in file Delme.kt
public fun <T> Some<TypeVariable(T)>.getOrNull(): TypeVariable(T) defined in root package in file Delme.kt
for the line is Some<T> -> getOrNull()
How can I tell the compiler which of the two subclass extension functions it should be calling?
The thing is, when you constrain the T of Option<T>.getOrNull but NOT the T of Some<T>.getOrNull you take away the fact that one was more specific than the other (a subtype). Now, they are just "different", hence the resolution ambiguity (the compiler cannot "order" them by specificity anymore).
You can solve it by also constraining Some<T>.getOrNull with Any:
fun <T : Any> Some<T>.getOrNull(): T = value

Kotlin sealed interface with generic type doesn't infer constraints for it's subtypes

Example:
sealed interface Foo<out T> {
val value: T
}
data class Bar<out K: List<Int>>(override val value: K): Foo<K>
fun <T> processFoo(foo: Foo<T>) {
when (foo) {
is Bar -> foo.value.forEach(::println)
}
}
Fails with:
Unresolved reference. None of the following candidates is applicable
because of receiver type mismatch:
public inline fun <T> Iterable<TypeVariable(T)>.forEach(action: (TypeVariable(T)) -> Unit): Unit defined in kotlin.collections
public inline fun <K, V> Map<out TypeVariable(K), TypeVariable(V)>.forEach(action: (Map.Entry<TypeVariable(K), TypeVariable(V)>) -> Unit): Unit defined in kotlin.collections
Why this fails? I expect that if foo is of type Bar then we know that T is a subtype of List<Int>. So we should be able to call forEach on it. Am I wrong?
This problem is simply caused by a typo in your code.
If you replace is Bar with is Bar<*>, the compiler is able to infer that T is a List<Int> in that context and the code compiles.
I expect that if foo is of type Bar then we know that T is a subtype of List. So we should be able to call forEach on it.
Yes, that is true. But T could also implement Map<K, V> at the same time as it implements List<Int> (I don't know of such a type, but it theoretically could exist), in which case you would also be able to call this extension function:
inline fun <K, V> Map<out K, V>.forEach(
action: (Entry<K, V>) -> Unit)
See all the different forEaches here.
To specifically call the forEach defined for Iterable, just do a cast:
// could also cast to List<Int> here - that's a completely safe unchecked cast
(foo.value as List<*>).forEach(::println)
An alternative is to use is Bar<*>, but a (very) slight drawback of this is that, as <*> projects the type of foo.value to be List<Int>, you lose the T. You won't be able to use foo.value in places where a T is expected.
A contrived example would be:
fun <T> processFoo(foo: Foo<T>): T {
return when (foo) {
// you can't return foo.value when you are expected to return T
is Bar<*> -> foo.value.also {
it.forEach(::println)
}
}
}

kotlin: list properties of any object - variance error

I'm trying to write a function that produces map of properties and values for any type
inline fun <reified T : Any> T.propertiesMap() {
for (property in this::class.memberProperties) {
property.get(this)
}
}
i get a compilation error in property.get(this) about
out-projected type [...] prohibits the use of 'public abstract fun get(receiver...
The issue is that this::class produces a KClass<out T> instead of KClass<T> which is what would be needed to use anything of type T in the property.get(...) call. So you can do an unchecked cast to do what you want:
fun <T : Any> T.propertiesMap() {
#Suppress("UNCHECKED_CAST")
for (property in (this::class as KClass<T>).memberProperties) {
property.get(this)
}
}
Which does not require the function to be inline nor reified type parameter. Otherwise you can change your function to use T::class instead of this::class to create a matching KClass<T>.
inline fun <reified T : Any> T.propertiesMap() {
for (property in T::class.memberProperties) {
property.get(this)
}
}
If you use the type you are reifying rather than an instance of that type the variance issue will go away. When you call T::class.memberProperties you get back a Collection<KProperty<T, *>> which is what I believe you want. On the other hand, if you call that on an instance (this) rather than a type, you get back a Collection<KProperty<out T, Any?>>, which is where your out-variance issue comes from.
inline fun <reified T : Any> T.propertiesMap() {
for (property in T::class.memberProperties) {
property.get(this)
}
}
Essentially, you need to do T::class rather than this::class in order to get the right kind of collection back. I've left your code as-is otherwise because I'm not clear on what you want this function to do, but I suspect you could drop the for loop in favor of a map call.

Why the type of expression objectOfTypeT::class is KClass<out T>?

Suppose we have generic function:
fun <T: Any> foo(o: T) {
o::class
}
The o::class's type is KClass<out T>. Why there is the out variance annotation, and why is it not KClass<out Any> (because T's erasure is Any)
This out variance annotation screwed my nice reflection code
EDIT:
After digging a while, I found kotlin rely on Object::getClass to get a Class to create a KClass, the actual creation code has a signature like fun <T: Any> create(jClass: Class<T>): KClass<T>. However this leads to another problem. The o::class should be of type KClass<Any> because jClass parameter of that create method should be of type Class<Object>, since the erasure of static type T is just Any (or Object, to which is mapped on JVM).
Why there is the out variance annotation?
This is expected behavior of the Bounded Class Reference in kotlin 1.1.
We know an instance of subclass can be assign to a supperclass, for example:
val long:Number = 1L;
val int:Number = 1;
We also know generic inheritance is not like class inheritance, for example:
val long:KClass<Long> = Long::class;
val number:KClass<Number> = long;
// ^
// can't be compiled, required KClass<Number> but found KClass<Number>
So we makes the code to be compiled by using Generic Type Projection as below:
val number:KClass<out Number> = long;
In Short, an variable of supperclass (Number) can be assign to an instances of any its subclasses (Long, Int, Double and .etc), but when get the KClass reference from the Number reference it should be return a KClass<out Number> rather than KClass<Number>, because KClass<Int> is not a subtype of the KClass<Number>.
The same rule applies in java, for example:
Number number = 1L;
Class<? extends Number> type = number.getClass();
Why is it not KClass (because T's erasure is Any)?
Because your method uses generic parameter T, but java.lang.Object#getClass don't uses any generic parameter at all and its return type is Class<? extends Object>.
However, the T#javaClass property takes a generic parameter T and you can see the code below that kotin cast a Class<?> to a Class<T>. so the Upper Bounded Wildcard of the o::class in the foo method is KClass<? extends T> rather than KClass<? extends Object> in java.
public inline val <T: Any> T.javaClass : Class<T>
#Suppress("UsePropertyAccessSyntax")
get() = (this as java.lang.Object).getClass() as Class<T>
// force to casting a Class<?> to a Class<T> ---^
A KClass<? extends T> is a subtype of KClass<?>, according to LISP principle you can't assign a superclass instance to a subclass type.
fun <T : Any> foo(value: T) {
val type: Class<out T> = (value as java.lang.Object).getClass();
// ^
// Compilation Error:
// you can't assign Class<? extends Object> to a Class<? extends T>
}
you can also see the method generic signature as below:
val noneGenericParametersMethod= Object::class.java.getDeclaredMethod("getClass")!!
val genericParametersMethod by lazy {
val it = object {
fun <T : Any> foo(): Class<out T> = TODO();
};
return#lazy it.javaClass.getDeclaredMethod("foo")!!;
}
println(genericParametersMethod.toGenericString())
// ^--- its return type is java.lang.Class<? extends T>
println(noneGenericParametersMethod.toGenericString())
// ^--- its return type is java.lang.Class<? extends Object>
Base on the above, the expression o::class actually returns a Raw Type KClass rather than a parameterized type KClass<out T>, and a Raw Type can be assign to any Parameterized Type, However, kotlin has no Raw Type, so kotlin compiler narrow the raw type KClass into the parameterized type KClass<out T>, just like as narrow an List<*> to an Iterable<*>. an example of Raw Type in java:
Box rawBox = new Box(); // rawBox is a raw type of Box<T>
Box<Integer> intBox = rawBox; // warning: unchecked conversion
Why java can't assign T.getClass() into a Class<? extends T>?
If you get deep into the documentation of the java.lang.Object#getClass method, you will found the result as below:
The actual result type is Class<? extends |X|> where |X| is the erasure of the static type of the expression on which getClass is called.
"The erasure of the static type": which means |X| is the bounded type rather than the actual generic argument type in runtime, for example:
// Object.getClass() using the bounded static type `Number`
// v
<T extends Number> Class<? extends Number> foo(T value){
return value.getClass();
}

How do I call a method in Kotlin with a different upper bound?

e.g. Given a Class<T> how do I call/invoke a method/constructor that requires Class<T> where T : Enum<T>?
fun <T : Any> handleAny(classOfT: Class<T>) {
if (classOfT.isEnum) {
handleEnum(classOfT)
}
}
fun <T : Enum<T>> handleEnum(classOfT: Class<T>) { /*...*/ }
Error: inferred type T is not a subtype of kotlin.Enum<T>
In Java I can do an unchecked call but I cannot seem to find a way to do anything similar in Kotlin.
As for now I found this quite hacky workaround for it:
private enum class DummyEnum
fun <T> handleAny(classOfT: Class<T>) {
if (classOfT.isEnum) {
handleEnum(classOfT as Class<DummyEnum>) //absolutely any enum goes
}
}
fun <T : Enum<T>> handleEnum(classOfT: Class<T>) {
println(classOfT.toString())
}
The idea is to make an unchecked cast to the type with any generic parameter satisfying the upper bound (let it be DummyEnum), which will then be erased at runtime anyway.
The limitation is that the solution doesn't work correctly with reified generics: if handleEnum had reified type parameter, it would be substituted for statically inferred type (DummyEnum).