Compile-Time Reflection

What consideration is there to add some compile-time reflection to eliminate the overhead of reflection at runtime?

For example, currently, Kotlin 1.4 intends to optimize out any KProperty that would be generated for delegation methods like Lazy's getValue, which doesn’t use the KPropery parameter. We could go further and replace any use of property.name with the property’s name, which should be known at compilation time, to eliminate the need for a reflection call.

We could go further and inline the getters and setters on a KProperty as well. swap(::a, ::b) could be an inline method:

private inline fun <T> swap(a: KMutableProperty0<T>, b: KMutableProperty0<T>) {
        val temp = a.get()
        a.set(b.get())
        b.set(temp)
}

as if swap actually took as arguments the two gets and sets, and inlined them together.

Finally, we could inline iterating over properties, as would be common in most serialization frameworks:

inline fun <reified T : Any> T.toJson() : String {
    return T::class.memberProperties
        .filter { it.javaField != null }
        .joinToString(prefix = "{", postfix = "}", separator = ", ") { property ->
            '"' + property.name + "\": " + when (property.returnType) {
                String::class, Character::class -> "\"" + property.get(this) + "\""
                Number::class, Boolean::class -> property.get(this)
                else -> property.get(this).toJson()
            }
        }
}

This would require a few more concepts:

  • An inlined list, that supports a few methods at compilation time so that each element is inlined into each operator (like loop unrolling) so that they can be optimized (which would be a useful feature in its own right)
  • Optimizing a when statement when the class argument is known at compilation time
  • A convenient syntax for the above when statement to use Number::class to include all subtypes (as Int::class isn’t equal to Number::class, but it shouldn’t be necessary to list every subclass)
  • Support for recursive calls to inline methods. This would probably require that each method not be actually inlined, but instead just reified to the specific type. That way, if someone were to serialize a large data structure, it wouldn’t repeat the serialization code for each type it encounters everywhere it is encountered, and it would be possible for a data structure to potentially contain itself and still be serializable.

To my knowledge, the only current way to do something like the above is to write either a compiler plugin (which isn’t nearly as portable, and is complicated) or an annotation processor (which has a high engineering overhead, it would take an order of magnitude or two more code to write an annotation processor that generates a class that does the same thing as toJson for each annotated class, and then anyone using an IDE will have error messages until they fully compile their project).

3 Likes

There is an issue for that: https://youtrack.jetbrains.com/issue/KT-16304

Like it.

While I like most of the points I don’t see how any of those can be done without inlineing. I mean sure maybe something like C++ templates can be done using the new klib format, but I’m not sure what I think of that.
That said, at least the first 2 points (list unrolling and optimized when) is probably something kotlin should add at some point.

1 Like

What I mean for fixing the recursive call is that the method toJson, instead of being inlined for every caller, would be compiled once for every type called. For example, given some data classes Person(name: String, item: Item) and Item(name: String), you would have compiled methods toJson$Person (which internally calls toJson$Item) and toJson$Item). Perhaps they would need to be called reified fun instead of inline fun to distinguish this. As only the type is being “inlined” and not any lambdas, it should be possible.