Search code examples
genericskotlinkotlin-reified-type-parameters

Kotlin compiler mixing up reified type parameters


I think I may have encountered a compiler bug regarding reified type parameters. It’s probably easiest to explain with a code example:

fun main() {
    println("Same:");

    proxySame<Int>();
    directSame<String, Int>();

    println("\nDifferent:");

    proxyDiff<Int>();
    directDiff<String, Int>();
}


inline fun <reified P> proxySame() = directSame<String, P>();
inline fun <reified P, reified Z> directSame(
    func: () -> Unit = {
        println(Z::class.java.simpleName)
    }
) = func();


inline fun <reified P> proxyDiff() = directDiff<String, P>();
inline fun <reified Q, reified Z> directDiff(
    func: () -> Unit = {
        println(Z::class.java.simpleName)
    }
) = func();

This example is obviously not a real-world scenario, but it’s the simplest program I could find that still manifests the unexpected behavior.

In this code there are 2 types of functions: directX and proxyX, where X is either Same of Diff. The only difference between the Same and Diff functions is the name of the first type parameter of the directX function. For Same it’s the same as the proxyX function, for Diff it’s different.

One would expect the output to be the same, regardless of whether directX was called through the proxy function or not. Unfortunately, this is apparently not always the case:

Same:
String         (proxy, wrong)
Integer        (direct, correct)

Different:
Integer        (proxy, correct)
Integer        (direct, correct)

As you can see the output is different when calling through the proxy function, but only if the reified type parameters have the same name. The value of P of the proxy function has somehow ended up in the Z of the direct function. However, this only happens inside the lambda function, printing the class name in the regular function body leads to the expected result.

I’m at a complete loss here. I can’t find any other explanation for this, other than it being a compiler bug. Have I truly run into some obscure bug, or have I somehow missed something here?

Edit: I have opened a bug report for this here.


Solution

  • If you take a look at the decompiled bytecode you can see that proxyDiff() seems to regard the generic parameter P and proxySame() does not.

    public final void proxyDiff() {
       // ...
       Intrinsics.reifiedOperationMarker(4, "P");
       String var5 = Object.class.getSimpleName();
       // ...
    }
    
    public final void proxySame() {
       // ...
       String var5 = String.class.getSimpleName();
       // ...
    }
    

    My guess is that it is optimized away during inlining.

    The compiler sees that P is not used in directSame() and thus infers that it is also not used in proxySame(). So this:

    inline fun <reified P> proxySame() = directSame<String, P>();
    inline fun <reified P, reified Z> directSame(
            func: () -> Unit = {
                println(Z::class.java.simpleName)
            }
    ) = func();
    

    becomes this under the hood:

    inline fun proxySame() = directSame<String>();
    inline fun <reified Z> directSame(
            func: () -> Unit = {
                println(Z::class.java.simpleName)
            }
    ) = func(); 
    

    So, yes I would definitely say it is a bug.