Samuel Neff
Samuel Neff

Reputation: 74909

Get reified type parameter kclass of reified generic type

Given a reified type parameter T, if that type is a List, how can I get the list's item type?

I tried overloading the reified method with T : Any and T : List and also TItem : Any, TList: List<TItem> but that doesn't compile due to conflicting overloads.

Fails to compile, conflicting overloads

inline fun <reified T> getRequiredVariable(variableName: String) =
  // ... implementation

inline fun <reified TItem, reified TList : List> getRequiredVariable(variableName: String) =
  // ... implementation

Failed workaround attempt

inline fun <reified T : Any> getRequiredVariable(variableName: String) =
    if (T::class.isSubclassOf(List::class)) {
        val tKClass = T::class
        val itemType = tKClass.typeParameters[0] // Won't work, even though T is reified, the type parameter apparently is not
        val itemClassifier = itemType.createType().classifier
        val itemKClass = itemClassifier as KClass<*> // Fails, KTypeParamaterImpl is not KClass<*>
        getRequiredListVariable(variableName, itemKClass) as? T
    } else {
        getRequiredVariable(variableName, T::class)
    }

Upvotes: 2

Views: 1241

Answers (1)

Joffrey
Joffrey

Reputation: 37710

The problem here is that you want to access type arguments but the KClass/Class abstraction only gives access to type parameters (the things declared in the class itself, not the actual types passed in this specific reified call site).

You shouldn't use the class for this, but the Type/KType instead. There is a convenient typeOf function to access it on a reified type, and then you can access the type arguments via the arguments property:

val typeArgs = typeOf<T>().arguments

Once you have those, you can access their type and the classifier of that type like you did in your existing code, but this time it will be those of the actual type arguments.

inline fun <reified T : Any> getRequiredVariable(variableName: String) =
    if (T::class.isSubclassOf(List::class)) {
        val itemTypeProjection = typeOf<T>().arguments[0]
        val itemType = itemTypeProjection.type ?: error("Cannot get list item type from List<*>")
        val itemKClass = itemType.classifier as? KClass<*> ?: error("The list item type is not denotable in Kotlin")
        getRequiredListVariable(variableName, itemKClass) as? T
    } else {
        getRequiredVariable(variableName, T::class)
    }

Note, however, that accessing the type argument 0 will not generally give you the item type for any implementation class of List. For instance, if the type is class MyStringList : List<String>, it has no type argument and yet it's a subtype of List. Even worse, it could have a type argument that has nothing to do with the list elements, as in class MyWeirdClass<X> : List<String> (using X for something unrelated). To overcome this, you might need to go up the hierarchy of supertypes in order to find the type of the List interface used in the declaration of T's type - and this will likely be a pain.

Upvotes: 3

Related Questions