kotlin.time.Duration and java reflection

It seems I’ve encountered a weird problem with interaction of java reflection API (particularly, java.lang.reflect.Proxy and kotlin.time.Duration. It looks like Java Reflection fails to determine method return type for kotlin.time.Duration. Consider the following example:

@file:JvmName("Main")
package org.test.kotlin.time.duration

import java.lang.reflect.Proxy
import kotlin.time.Duration
import kotlin.time.ExperimentalTime
import kotlin.time.seconds

@ExperimentalTime
interface I {
    fun getNullableDuration(): Duration? = 1.seconds
    fun getRange(): IntRange = 1..3
    fun getDuration(): Duration = 1.seconds
}

class IC: I

inline fun <reified T> T.sampleProxy(): T = sampleProxy(T::class.java)

fun <T> sampleProxy(c: Class<T>): T {
    return c.cast(Proxy.newProxyInstance(c.classLoader, arrayOf(c)) { _, method, _ ->
        println("[proxy] ${method.declaringClass.name}::${method.name} return type is ${method.returnType}")
        when (method.name) {
            "getNullableDuration", "getDuration" -> 1.seconds
            "getRange"                           -> 0..1
            else                                 -> TODO("method ${method.name} isn't handled yet")
        }
    })
}

fun main() {
    val v: I = IC()
    println("I::getNullableDuration() return type is ${v::getNullableDuration.returnType}")
    v.sampleProxy().getNullableDuration()
    println("I::getRange() return type is ${v::getRange.returnType}")
    v.sampleProxy().getRange()
    println("I::getDuration() return type is ${v::getDuration.returnType}")
    v.sampleProxy().getDuration()
}

This code produces the following output:

I::getNullableDuration() return type is kotlin.time.Duration?
[proxy] org.test.kotlin.time.duration.I::getNullableDuration return type is class kotlin.time.Duration
I::getRange() return type is kotlin.ranges.IntRange
[proxy] org.test.kotlin.time.duration.I::getRange return type is class kotlin.ranges.IntRange
I::getDuration() return type is kotlin.time.Duration
[proxy] org.test.kotlin.time.duration.I::getDuration return type is double
Exception in thread "main" java.lang.ClassCastException: class kotlin.time.Duration cannot be cast to class java.lang.Double (kotlin.time.Duration is in unnamed module of loader 'app'; java.lang.Double is in module java.base of loader 'bootstrap')
	at com.sun.proxy.$Proxy2.getDuration(Unknown Source)
	at org.test.kotlin.time.duration.Main.main(main.kt:38)
	at org.test.kotlin.time.duration.Main.main(main.kt)

Process finished with exit code 1

As one can see, inside sampleProxy return types for getNullableDuration() and getRange() are determined correctly (kotlin.time.Duration? expectedly becomes kotlin.time.Duration), while getDuration() suddenly becomes a method returning double and the cast from kotlin.time.Duration to double on return from the method fails with the exception.

What could be a reason for the problem? Is it a bug? How can I workaround it?

My environment:

  • OpenJDK 11.0.6+10
  • Kotlin 1.3.71
  • Linux/x86-64

Duration is currently implemented as an inline class: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.time/-duration/
https://kotlinlang.org/docs/reference/inline-classes.html
That means that the compiler will change the type from Duration to Double whenever possible. There are still a few issues with inline classes, especially with reflection.

Ok, I got it, thank you!

Is it possible to somehow determine the “original” type within the InvocationHandler? Probably, the key point of the problem is Java/Kotlin interaction. I do see that within “pure kotlin” code the type is still preserved even through generic calls:

fun <T: Any, R: Any?> determineType(meth: T.() -> R) {
    println("[generic call] $meth return type is ${meth.reflect()?.returnType}")
}
...

determineType(IC::getDuration)

still gives me the correct result:

[generic call] fun org.test.kotlin.time.duration.IC.getDuration(): kotlin.time.Duration return type is kotlin.time.Duration