Reified generics on class level

Hello,

As far as I could find, this question has not been posted before. If it has, I would be happy to view that forum post.

Edit: I just found Can generic parameters be reified at class level?, no idea why it did not pop up in the first place…

I have a use case where there is a collection of items, with some generic type. I need to filter the items in the collection based on their generic type, and then invoke some specific function on the items using data that only compiles if the types I just checked (filtered on) are correct.

In code:

class Item<T> { 
  val clazz : Class<T>
  // other properties
  fun operation (t: T) { /* ... */ }
}

val value: Long = ...
val specificClass: Class<Long> = ...
val items : List<Item<*>> = ....
items.filter { it.clazz == specificClass }
  .forEach { it.operation(value) } 
          // ^^^^^^^^^^^^ Here I would need to cast each item 
          // to Item<Long>, even though I just checked that 
          // it.clazz == Long::class

I would like to have that code like:

val items : List<Item<*>> = ....
items.filterInstanceOf<Item<Long>>()
  .forEach { it.operation(value) } // <-- Smart cast to Item<Long>

At the moment this is not possible, because classes cannot have reified generic parameters (only inline functions can have them). Would a language extension be possible where classes with some of their generic parameters annotated as reifed be available at runtime?

A small example of the code it could generate automatically:

class Item<reified T>

// ... would be equivalent to ... 

class Item<T> {
  val genericClassArgument0: Class<T>
}

(just like componentN() for data classes).

Then, we would need to fill the parameter for every constructor call. That could work in the following way (just like an inline function):

class Item<reified T>(val value: Long)
val item = Item<UUID>(Random.nextLong())
// ... this would actually call ...
//   Item(UUID::class, Random.nextLong())
// and bytecode would just contain the constructor with the Class<*>, Long arguments

I hope this makes the idea clear. Thanks in advance for any reactions.

Something like that was discussed multiple times and it makes sense in the long run. Still, I do not think that it is really needed right now, since it is rarely used and could be replaced by two lines of code:

class Item<T: Any> private constructor(val type : KClass<out T>) {
  companion object{
    inline operator fun <reified T: Any> invoke() = Item(T::class)
  } 
} 
4 Likes

Thank you for replying. I understand that this would work.

The code works, but implementing a function like

fun <T:Any> Collection<Item<*>>.filterGenericInstance<T>() : List<Item<T>> {
  // ...
}

would still require (unchecked) casting from Item<*> to Item<T>, because during construction you still lose the information that the type T of Item<T> is actually stored in runtime.

My point is that by building such functionality into the language, we would have both the construction and the type-safe filtering of such generic types out of the box.

1 Like

This is correct. It only works in the “factory method” in other methods that need to use the “T” you have to do as you suggest… such a pitY @darksnake any updates if this will be considered in the future? reified generics are so cool but using them like this… is an halfaway measure.

Here’s a write-once-forever solution that does the trick for any nesting of types (i.e it checks Item<Item<T>>). Note that a factory function is recommended for nicer typing, but isn’t strictly required.

import kotlin.reflect.*
import kotlin.reflect.full.*
fun main() {
  val value: Long = 42
  val value2: Item<String> = Item()
  val items: List<Item<*>> = listOf(Item<Item<String>>(), Item<Item<Int>>(), Item<Any>())
  items.filterIsInstanceT<Item<Long>>()
    .forEach { it.operation(value) } // prints only once, for Any
  items.filterIsInstanceT<Item<Item<String>>>()
    .forEach { it.operation(value2) } // prints two, one for Item<String> and one for Any
}

// Factory function needed per class just to make construction nicer
inline fun <reified T> Item() = Item<T>(typeOf<Item<T>>())
data class Item<in T>(override val type: KType) : Typed {
  fun operation(t: T) { println("$this printed $t") }
}

// Written once, used forever
interface Typed {
  val type: KType
}

inline fun <reified T: Typed> List<*>.filterIsInstanceT(): List<T> = filter { it.isT<T>() } as List<T>
inline fun <reified T: Typed> Any?.isT() = this is T && type.isSubtypeOf(typeOf<T>())

It should print the following:

Item(type=Item<kotlin.Any>) printed 42
Item(type=Item<Item<kotlin.String>>) printed Item(type=Item<kotlin.String>)
Item(type=Item<kotlin.Any>) printed Item(type=Item<kotlin.String>)