7
votes

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?

2

2 Answers

8
votes

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
}
2
votes

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.