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