Generic parameter is lost in `Supplier` lambda but not in `Callable`

Can anybody explain why generic parameter is lost in Supplier lambda but not in Callable?

I created interface that captures generic type and helper function that instantiates it

abstract class TypeRef<T> protected constructor() {
    val type: Type = (javaClass.genericSuperclass as ParameterizedType).actualTypeArguments[0]

    override fun toString(): String {
        return type.toString()
    }
}

inline fun <reified T> getTypeRef(): TypeRef<T> {
    return object: TypeRef<T>() {};
}

But testing it leads to different results depending on where it’s called

inline fun <reified T> testTypeRef(): TypeRef<T> {
    val ref0 = getTypeRef<T>()
    println("In root $ref0")

    Callable {
        val ref1 = getTypeRef<T>()
        println("In callable $ref1")
    }.call()

    Supplier {
        val ref2 = getTypeRef<T>()
        println("In supplier $ref2")
        ""
    }.get()

    return ref0
}

produces

In root java.util.List<? extends java.util.concurrent.atomic.AtomicReference<java.lang.String>>
In callable java.util.List<? extends java.util.concurrent.atomic.AtomicReference<java.lang.String>>
In supplier T

What am I missing ? Original question is here.

I’m just guessing here but it might have something to do with the generic definition of Callable and Supplier.
They are defined like this in the java stdlib.

@FunctionalInterface
public interface Callable<V> {
    V call() throws Exception;
}
@FunctionalInterface
public interface Supplier<T> {
    T get();
}

My guess is that T in the last case no longer referes to your reified T but instead refers to the T of the Supplier interface. My test shows that if you rename your type parameter to V you get this output

In root java.util.List<? extends java.lang.String>
In callable V
In supplier java.util.List<? extends java.lang.String>

So that seems to agree with my guess.
Not sure if this is a bug or an unintended side effect of how generics work.

1 Like

Wow, so actual name of generic parameter plays role here. It’s even more confusing than I thought. I renamed T to Z and all three prints gave correct generic parameter. It would be nice if Kotlin compiler can take care of it.

Yeah, this feels like there should at least be a warning here. I only noticed this because I looked up the declarations of Supplier and Callable. If I hadn’t seen that Callable uses a different name for the type parameter I don’t think I would have found this.

I created an issue for this here: https://youtrack.jetbrains.com/issue/KT-34856?project=kt

3 Likes