I am trying to check if a type conforms to a another type with an if expression like so:
if (String::class is Any::class)
This gives me the error class literals with empty left hand side are not yet supported. Can anyone elaborate on that error and/or tell me how I should be doing this check?
edit (clarification): I can't do an equality check because I need to know if the class on the left either matches the class on the right or is a subclass of it. So if an instance of the class on the left can be safely cast to the class on the right.
Basically I need the equivalent of:
if ("A string" is Any)
But without having a String instance, String just being used an example here.
Your error message is that the is
check expects a class name and not a reference to a KClass on the right side. The message itself might be a little unclear. But the same applies in Java, you would not use instanceOf
operator but instead would call isAssignableFrom
.
For help on solving the problem, you have examples that can be found in Github...
In the Klutter library are examples of a lot of combinations of instanceOf style checking between Class
, KClass
, Type
and KType
as well as primitives. You can copy ideas from there. There are many combinations that you might want to have covered in the long run.
Here is a sampling of a big mix of extensions for checking if one type is assignable from the other. A few examples are:
fun <T : Any, O : Any> KClass<T>.isAssignableFrom(other: KClass<O>): Boolean {
if (this.java == other.java) return true
return this.java.isAssignableFrom(other.java)
}
fun <T : Any> KClass<T>.isAssignableFrom(other: Class<*>): Boolean {
if (this.java == other) return true
return this.java.isAssignableFrom(other)
}
fun KClass<*>.isAssignableFromOrSamePrimitive(other: KType): Boolean {
return (this.java as Type).isAssignableFromOrSamePrimitive(other.javaType)
}
fun KClass<*>.isAssignableFromOrSamePrimitive(other: Type): Boolean {
return (this.java as Type).isAssignableFromOrSamePrimitive(other)
}
fun Type.isAssignableFromOrSamePrimitive(other: Type): Boolean {
if (this == other) return true
if (this is Class<*>) {
if (other is Class<*>) {
return this == other.kotlin.javaObjectType || this == other.kotlin.javaPrimitiveType ||
this.isAssignableFrom(other)
}
return this.isAssignableFrom(other.erasedType())
}
return this.erasedType().isAssignableFrom(other.erasedType())
}
// ... and so on for every permutation of types
See the linked source for all permutations.
And you will need this erasedType()
extension used by the above samples -- which goes from a Type
back to a Class
(after type erasure):
@Suppress("UNCHECKED_CAST") fun Type.erasedType(): Class<Any> {
return when (this) {
is Class<*> -> this as Class<Any>
is ParameterizedType -> this.getRawType().erasedType()
is GenericArrayType -> {
// getting the array type is a bit trickier
val elementType = this.getGenericComponentType().erasedType()
val testArray = java.lang.reflect.Array.newInstance(elementType, 0)
testArray.javaClass
}
is TypeVariable<*> -> {
// not sure yet
throw IllegalStateException("Not sure what to do here yet")
}
is WildcardType -> {
this.getUpperBounds()[0].erasedType()
}
else -> throw IllegalStateException("Should not get here.")
}
}