Reputation: 3171
Consider an interface:
interface Foo {
fun func(argWithDefault: String = "default")
}
and its (minimal) implementation:
class FooImpl() : Foo {
override fun func(argWithDefault: String) {
println(argWithDefault)
}
}
I'm trying too call func
on FooImpl
via reflection:
val func = Foo::func
val instance = FooImpl()
func.callBy(mapOf(func.instanceParameter!! to instance))
but I get the following exception:
kotlin.reflect.jvm.internal.KotlinReflectionInternalError: This callable does not support a default call: public abstract fun func(argWithDefault: kotlin.String = ...): kotlin.Unit defined in com.example.Foo[DeserializedSimpleFunctionDescriptor@2d7275fc]
Foo::func.parameters[1].isOptional
returns true
, and, according to the docs, isOptional
returns
true if this parameter is optional and can be omitted when making a call via KCallable.callBy, or false otherwise.
so I guess this should be possible.
Upvotes: 2
Views: 2112
Reputation: 23262
tl;dr: this seems to be a known issue: KT-13936: KotlinReflectionInternalError on invoking callBy on overridden member with inherited default argument value
Some analysis which I did before:
If we look at what Kotlin actually generates here, it rather becomes obvious why you don't have any default values anymore. Bytecode of Foo
:
public abstract interface Foo {
public abstract func(Ljava/lang/String;)V
LOCALVARIABLE this LFoo; L0 L1 0
LOCALVARIABLE argWithDefault Ljava/lang/String; L0 L1 1
public final static INNERCLASS Foo$DefaultImpls Foo DefaultImpls
}
// ================Foo$DefaultImpls.class =================
public static synthetic func$default(LFoo;Ljava/lang/String;ILjava/lang/Object;)V
ALOAD 3
IFNULL L0
NEW java/lang/UnsupportedOperationException
DUP
LDC "Super calls with default arguments not supported in this target, function: func"
INVOKESPECIAL java/lang/UnsupportedOperationException.<init> (Ljava/lang/String;)V
ATHROW
L0
ILOAD 2
ICONST_1
IAND
IFEQ L1
L2
LINENUMBER 2 L2
LDC "default" // <--- here is our default value? but where are we? Foo$Defaults.func$default?
ASTORE 1
L1
ALOAD 0
ALOAD 1
INVOKEINTERFACE Foo.func (Ljava/lang/String;)V (itf)
RETURN
MAXSTACK = 3
MAXLOCALS = 4
public final static INNERCLASS Foo$DefaultImpls Foo DefaultImpls
// compiled from: Foo.kt
}
So the declared function within Foo
is just fun foo(String)
... No default value anywhere, except in a synthetic function in its own DefaultImpls
-class.
So where is that function called... FooImpl
maybe?
If we look at the FooImpl
-bytecode it becomes clear that it isn't there:
public final class FooImpl implements Foo {
public func(Ljava/lang/String;)V
L0
ALOAD 1
LDC "argWithDefault"
INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull (Ljava/lang/Object;Ljava/lang/String;)V
L1
LINENUMBER 3 L1
L2
ICONST_0
ISTORE 2
L3
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
ALOAD 1
INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/Object;)V
L4
L5
LINENUMBER 4 L5
RETURN
L6
LOCALVARIABLE this LFooImpl; L0 L6 0
LOCALVARIABLE argWithDefault Ljava/lang/String; L0 L6 1
MAXSTACK = 2
MAXLOCALS = 3
...
So for completeness I also created a Caller.kt
just containing the following code:
fun main() = FooImpl().func()
and finally, bytecode again, but also our DefaultImpls.func$default
-call:
public final class CallerKt {
public final static main()V
L0
LINENUMBER 1 L0
NEW FooImpl
DUP
INVOKESPECIAL FooImpl.<init> ()V
ACONST_NULL
ICONST_1
ACONST_NULL
INVOKESTATIC Foo$DefaultImpls.func$default (LFoo;Ljava/lang/String;ILjava/lang/Object;)V // aha! here is our DefaultImpls.func$default!
RETURN
L1
MAXSTACK = 4
MAXLOCALS = 0
So ... as the DefaultImpls
is just generated by the compiler and used when needed, the reflect
utilities could (and in my opinion should) reflect this fact too... I added a similar known issue regarding open classes (which use synthetic ...$default
-functions as well) at the top of the answer. If that issue doesn't fully address your concern, you may want to open up a new Kotlin issue instead.
Just played with the linked issue a bit... actually the sample there would work if A::foo
is used instead of B::foo
(A
is the open class and B
extends A
). However as the A
there would basically be similar to your Foo
-interface, the interface needs special treatment regarding this nonetheless.
Upvotes: 2