i have an interface whose function has a default v...
# reflect
j
i have an interface whose function has a default value but when i try to call its implementation via reflection i cannot call it as it says the parameter is missing
Copy code
fun main() {
    val function = ::myFunction
    val parameters = function.parameters

    function.callBy(mapOf(parameters[0] to 1)) // a: 1; b: default value

    val myClass = MyClass()
    val classFunction = MyClass::myClassFunction
    val classFunctionParameters = classFunction.parameters

    classFunction.callBy(mapOf(classFunctionParameters[0] to myClass, classFunctionParameters[1] to 2)) // a: 2; b: default value

    val myInterfaceImpl = MyInterfaceImpl()
    val interfaceFunction = MyInterface::myInterfaceFunction
    val interfaceFunctionParameters = interfaceFunction.parameters

    interfaceFunction.callBy(mapOf(interfaceFunctionParameters[0] to myInterfaceImpl, interfaceFunctionParameters[1] to 2))
    // throws IllegalArgumentException: No argument provided for a required parameter: instance parameter of fun MyInterfaceImpl.myInterfaceFunction(<http://kotlin.Int|kotlin.Int>, kotlin.String): kotlin.Unit
}

fun myFunction(a: Int, b: String = "default value") {
    println("a: $a; b: $b")
}

class MyClass {
    fun myClassFunction(a: Int, b: String = "default value") {
        println("a: $a; b: $b")
    }
}

interface MyInterface {
    fun myInterfaceFunction(a: Int, b: String = "default value")
}

class MyInterfaceImpl : MyInterface {
    override fun myInterfaceFunction(a: Int, b: String) {
        println("a: $a; b: $b")
    }

}
how can i get around this? or should i file a bug
j
5 years sad panda
e
a workaround is to use Java reflection,
Copy code
val myInterfaceImpl = MyInterfaceImpl()
val interfaceDefaults = Class.forName("${MyInterface::class.java.name}\$DefaultImpls")
val interfaceDefaultFunction = interfaceDefaults.getDeclaredMethod(
    "${MyInterface::myInterfaceFunction.name}\$default",
    MyInterface::class.java,
    Int::class.java,
    String::class.java,
    Int::class.java,    // mask
    Object::class.java, // marker
)

interfaceDefaultFunction.invoke(
    null, // static
    myInterfaceImpl,
    2,
    null, // default
    3,    // parameters 0 and 1 are given
    null, // marker
)
basically it has to do with how Kotlin generates bytecode. a function with default parameters gets a
$default
wrapper that fills in the defaults. for a class this gets implemented in the same class, but as Java interfaces (at the time) didn't support final or static methods, Kotlin puts it in a different class
j
unfortunately that won't work for my use-case. i'm building these maps of parameters dynamically so i only know at runtime which values i need to call defaults for
e
sure you can,
Copy code
val myInterfaceImpl = MyInterfaceImpl()
val interfaceFunction = MyInterface::myInterfaceFunction
val interfaceFunctionParameters = interfaceFunction.parameters
val interfaceFunctionParametersMap = mapOf(
    interfaceFunctionParameters[0] to myInterfaceImpl,
    interfaceFunctionParameters[1] to 2,
)
val interfaceDefaults = Class.forName("${MyInterface::class.java.name}\$DefaultImpls")
val interfaceDefaultFunction = interfaceDefaults.getDeclaredMethod(
    "${MyInterface::myInterfaceFunction.name}\$default",
    *interfaceFunctionParameters.map { (it.type.classifier as KClass<*>).java }.toTypedArray(),
    Int::class.java,   // need more ints if you have more than 32 parameters,
    Object::class.java // marker
)

interfaceDefaultFunction.invoke(
    null, // static
    *interfaceFunctionParameters.map {
        if (it in interfaceFunctionParametersMap) {
            interfaceFunctionParametersMap[it]
        } else {
            when (it.type.classifier) {
                Boolean::class -> false
                Byte::class -> 0.toByte()
                Short::class -> 0.toShort()
                Int::class -> 0
                Long::class -> 0L
                Float::class -> 0f
                Double::class -> 0.0
                Char::class -> '\u0000'
                else -> null
            }
        }
    }.toTypedArray(),
    interfaceFunctionParameters.withIndex().fold(0) { acc, (i, param) ->
        acc or if (param in interfaceFunctionParametersMap) 1 shl i else 0
    },    // mask
    null, // marker
)
j
ah wow, this is so close. sorry to keep adding more but wasn't expecting to do this via java. they are actually all suspend functions so i updated it to
Copy code
val interfaceDefaultFunction = interfaceDefaults.getDeclaredMethod(
        "${MyInterface::myInterfaceFunction.name}\$default",
        *interfaceFunctionParameters.map { (it.type.classifier as KClass<*>).java }.toTypedArray(),
        Continuation::class.java,
        Int::class.java,   // need more ints if you have more than 32 parameters,
        Object::class.java // marker
    )
then call it by wrapping it
Copy code
val x = suspendCoroutine<Any> { continuation ->
        interfaceDefaultFunction.invoke(
            null, // static
            *interfaceFunctionParameters.map {
                if (it in interfaceFunctionParametersMap) {
                    interfaceFunctionParametersMap[it]
                } else {
                    when (it.type.classifier) {
                        Boolean::class -> false
                        Byte::class -> 0.toByte()
                        Short::class -> 0.toShort()
                        Int::class -> 0
                        Long::class -> 0L
                        Float::class -> 0f
                        Double::class -> 0.0
                        Char::class -> '\u0000'
                        else -> null
                    }
                }
            }.toTypedArray(),
            continuation,
            interfaceFunctionParameters.withIndex().fold(0) { acc, (i, param) ->
                acc or if (param in interfaceFunctionParametersMap) 1 shl i else 0
            },    // mask
            null, // marker
        )
    }

    println(x)
but it never finishes the continuation nor prints the return value
e
either use
kotlin.coroutines.intrinsics.suspendCoroutineUninterceptedOrReturn
or handle the non-
COROUTINE_SUSPENDED
case yourself
j
amazing, i think i've got this mostly working
appreciate your help a ton, would still love some solution to this in kotlin-reflect one day though