Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Kotlin class literals with empty left hand side are not yet supported?

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.

like image 242
zjuhasz Avatar asked Sep 21 '16 22:09

zjuhasz


2 Answers

I guess it wouldn't be clear if Kotlin used the is operator differently between a KClass and another KClass as it does between an instance and a type which is why what I was trying to do doesn't work. Anyway I made this little infix function to imitate the functionality. However it only works with JVM target of course since it's using Java reflection. This is going off of the answer given in this SO post.

infix fun <T : Any, C : Any> KClass<T>.can(comparate: KClass<C>) =
   comparate.java.isAssignableFrom(this.java)

This will allow you to do exactly what I was trying to do but with the can function instead of the is operator like so:

if(String::class can Any::class)
like image 137
zjuhasz Avatar answered Jan 03 '23 02:01

zjuhasz


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.")
    }
}
like image 41
Jayson Minard Avatar answered Jan 03 '23 02:01

Jayson Minard