Search code examples
javakotlinreification

How can I store reified type data in instance fields in Kotlin?


I'm currently writing a DSL for a library and I'd like to supply type metadata using reified type parameters like this:

    val config = Config.create()
            .consumerFor<MyType>{
              // consume
            }

My problem is that i can only use the reified keyword in inline functions and in an inline function I can't use instance fields like this:

    inline fun <reified T> consumerFor(consumer: (T) -> Unit) {
        consumers.put(T::class.java, consumer)
        return this
    }

because I get an error:

Public-API inline function cannot access non-public-API 'private final val consumers...

It seems so far that I can't use reified type parameters where they would be most useful. Is there a workaround for this?


Solution

  • Public inline functions cannot use private declarations directly, because, when inlined at the call sites outside the class, the usages will have incorrect access level (on JVM, a private member of a class cannot be accessed from outside).

    What you can do is use the internal visibility in Kotlin: on JVM, the members with this visibility modifier will be compiled into public members with their names mangled (therefore still visible but not easy-to-call from Java), and the Kotlin compiler will at least control the usages from the Kotlin code.

    There are a few ways to access an internal member from within a public inline fun, see this question: (link)

    In your particular case, I would prefer doing it with @PublishedApi:

    private val consumers = mutableMapOf<Class<*>, Any>()
    
    @PublishedApi
    internal fun <T> putConsumer(clazz: Class<out T>, consumer: (T) -> Unit) {
        consumers.put(clazz, consumer)
    }
    
    inline fun <reified T> consumerFor(noinline consumer: (T) -> Unit): C {
        putConsumer(T::class.java, consumer)
        return this
    }
    

    Or, if you don't mind exposing consumers with @PublishedApi, then you can do it as follows:

    @PublishedApi
    internal val consumers = mutableMapOf<Class<*>, Any>()
    
    inline fun <reified T> consumerFor(noinline consumer: (T) -> Unit): C {
        consumers.put(T::class.java, consumer)
        return this
    }