I was trying to do some testing on an API and was wanting to just check if a property was declared with the type it should be. The Kotlin reflection has the KType for precisely representing the type, but what it does not have is a way to get a KType to compare it to. In particular what I wanted was a way to get a KType from a type using something like ::type the way we have ::class as in:
inline fun <reified T> KCallable<*>.isOfType(): Boolean
= T::type == returnType
so I could say
val isOfRightType = MyClass::myProperty.isOfType<List<Foo?>>()
Or perhaps you could just implement such a method on KType
Sadly, this cannot be implemented as a method on KType because of type erasure on JVM. We may be able to introduce a language feature in the future to support this, however.
Currently, as a workaround, you can declare a function/property with that type somewhere in your code and compare the two types with ==:
fun f(): List<Foo?> = TODO()
...
val isOfRightType = MyClass::myProperty.returnType == ::f.returnType
I think having a full non-erased type system would be great. Especially if you added support for getting it from non-kotlin fields and classes.
fun Field.getFullType(declaredClassType: FullType = fullGenericType(this.getDeclaringClass()))
fun FullType.getGenericParent(parentClassOrInterface: (K)Class<*>)
And as an example:
class MiddleMap<K, V>(val middle: K) : Map<V, K>()
class SomeMap<T>(val some: T) : MiddleMap<Int, T>(10) {
fullTypeOf<SomeMap<String>>().getGenericParent(Map::class) // -> FullType<Map<String, Int>>
// -> Traverses up from SomeMap<String> to MiddleMap<Int, String> to Map<String, Int>
val t = fullTypeOf<SomeMap<List<String>>>()
val someField = SomeMap::class.java.getDeclaredField("some")
val middleField = MiddleMap::class.java.getDeclaredField("middle")
someField.getFullType(t) // -> FullType<List<String>>
// -> Maps type of field "some" to type of parameter "T" in SomeMap
middleField.getFullType(t) // -> Int
// -> Maps type of field "middle" to type parameter K, which then is mapped to the type passed in by SomeMap, which is Int
One big thing is that this would work with Java classes as well, mostly because I develop a small library that is primarily used Java code, and it does a lot of black magic with regards to types (it has an annotation-based serialization system)
(Just in case it helps, GSON has a lot of this, they could probably help you not tie your brain in a knot trying to piece apart Java’s type system like I did.)
I just built what was needed for my use case. I don’t have the time to make a library which covers all possible cases (of which I am sure there are a lot).
My code also complete ignores the variance of the type parameters as it is not needed for what I am doing: checking if a type is a subtype of another type.
And I am not interested in Java interoperability.
I would love to see this as then I can drop my own (limited) code.
After a lot of experiments, and using another solution with more verbose syntax, I now came up with this. Note: This only works on the JVM, and uses the experimental function reflect():
import kotlin.reflect.jvm.reflect
@Suppress("CAST_NEVER_SUCCEEDS", "UNCHECKED_CAST")
fun <T> t() = null as T
fun <T> (() -> T).asKType() =
reflect()?.returnType ?: throw IllegalStateException()
How do you use it? If a function needs a type, define a parameter accepting a parameterless function that returns the desired type as a result:
fun <TObject> needsType(functionWithTypeDefiningResult: () -> TObject) { ... }
Now, you can pass the type to the function using a short notation. Function t is generic, but because it is called in a lambda, the lambda will have a non-generic return type: