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?
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
}
                        If all that you need the reified type parameter for is to reflect its java class, then you can manage with a non-inline overload with additional Class<T> parameter.
inline fun <reified T> consumerFor(noinline consumer: (T) -> Unit) =
    consumerFor(T::class.java, consumer)
fun <T> consumerFor(key: Class<T>, consumer: (T) -> Unit) = apply {
    consumers.put(key, consumer)
}
This way you haven't expose consumers property effectively published. The bonus point is that you can call that non-inline overload also from Java.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With