Is there an idiomatic way to “bridge” inlined reif...
# announcements
s
Is there an idiomatic way to “bridge” inlined reified functions to concrete named functions so that the inlined function is callable from Java? Such as the following two prototypes.
h
I think you already got it, i am always using inline fun <reified T> foo() = foo(T::class) And implement everything in the java compatible oberload that takes the class :)
p
you’d want to use
Class<T>
, not
KClass
, since Java doesn’t know about kotlin classes; other than that 👍🏾
s
for this direction it is easy. but i thought sam meant the other direction: taking an inline function (possibly from the public api of a library) and writing a wrapper function that takes a class object and somehow calls the inline function. the question got me spiralling into an irresistible dive into the technical spec of the language, but i have not found any solution yet. I very strongly believe that this is not possible, but I do not yet know why exactly :)
the core of the problem is that the compiler does not inline the same bytecode into every call site, but replaces the inline call in such a way that it is as efficient as it would be, if one would copy the code itself... so there are definitely some optimizations that are done to make this performant. (the documentation says that inlining reified type parameters eliminates the need for reflection, when only instance checks are performed) inlining a function into a call site where the type parameter is only known at runtime obviously would ruin all optimizations that could be done. But I dont know, whether it would technically work. there basically are these two options: • the inlining would work, but the bytecode would always need to use reflection. that could be a reason for why the compiler does not allow this... • or maybe a class object on its own somehow does not contain enough information to cover everything that can be done with a reified type parameter. but i cannot think of anything that cannot be done with reflection and a class object. then again, it probably would be slow as hell...
it would make sense that the compiler would not allow this, if the main purpose of reified type parameters is to eliminate the need of reflection. because a function that takes a class object at runtime has no other choice than to do everything via reflection
s
@Stefan Beyer Yes that I what I am trying to do. As far as I can tell, you really do just need to drill down the inline chain to find the underlying unsafe cast location.
Other than being inefficient, I don’t see why there’s a technical constraint preventing the compiler from synthesizing a reflection-based overload that accepts
Class<T>
The main reason for doing this is to allow Java interoperability with inlined functions.
s
that is my understanding too. every inline chain should end at an unsafe cast or a reflection call. (maybe also some low level jvm stuff that almost nobody knows about?)
but i think it is reasonable to assume that the kotlin team does not allow this, since the performance loss can arbitrarily range from almost zero to reflection overdose, without the java user knowing what is causing it
s
Would be an incredibly great opt-in feature. Obviously the preference is that users actually use the inlined functions and let the compiler optimize, but such libraries should really have the ability to support interoperability with Java users as well.
I’d love to hear from a JB team member about this, whether its a technical constraint or otherwise.
s
me too 🙂 personally, i am not sure if it would be a good idea to allow this, but i was wrong in the past and always will be again ^^ and that answer would be very insightful either way...
s
I think something like this could benefit from being an
RequiresOptIn
along with an annotation on an inlined function to actually synthesize the non-reified function.
s
do you mean like this
Copy code
@RequiresOptIn
annotation class ReflectiveInlinePermitted

@ReflectiveInlinePermitted
inline fun <reified T> libraryFunction(): T = TODO()


@OptIn(ReflectiveInlinePermitted::class)
fun consumerFunction() {
    println(libraryFunction<String>())
}
or like this
Copy code
@RequiresOptIn
annotation class ReflectiveInline

inline fun <reified T> libraryFunction(): T = TODO()

@ReflectiveInline
fun <T> slowLibraryFunction(clazz: Class<T>) = libraryFunction<T>()


fun consumerFunction() {
    println(libraryFunction<String>())
}
the first one would be annoying to kotlin users and the second one would only prevent accidental use from kotlin but java users cant opt in anyway...
s
Copy code
@RequiresOptIn
annotation class SynthesizeReflectiveOverload

@SynthesizeReflectiveOverload
inline fun <reified T> libraryFunction(): T = TODO()

fun consumer() {
    println(libraryFunction(String::class.java))
}
s
then the kotlin users that use the reified version also need to opt in, right?
s
Hmm good point
s
• kotlin users that use the reified function should not have to opt in • the creator of that library function does already opt in by using the annotation • java users cannot opt in at all, but it would be nice for them to notice that this could be slow so the only group that needs the opt in cannot use the opt in 😭
🤦‍♂️ 1
s
@Stefan Beyer This has been a fun thought experiment but I do really wish the compiler could implement this. It could probably be implemented by a compiler plugin, but that API is pretty horrible to work with (extremely undocumented, tightly coupled to compiler version, etc.).
Actually I think a compiler plugin would be perfect for this. I know it would definitely save me some major headaches.
s
it probably will be easier to create compiler plugins once the new IR backend is stable 🙂
s
#CJ699L62W might also help with this. They have a compiler API that wraps the Kotlin compiler’s