5

I came from the C# background and I know how to implement this in C#, but I'm struggling with Kotlin. I've got 2 extension functions:

fun <T> Foo<T>.myFunction(func: () -> Unit): Foo<T>

and

fun <T> Foo<T>.myFunction(func: () -> Foo<T>): Foo<T>

Clearly, the return type of func is different in both functions. The first function executes it and returns this, the second executes func and returns the result of func. But it gives me an error:

"Platform declaration clash: The following declarations have the same JVM signature".

How to implement this correctly in Kotlin?

Alex
  • 199
  • 1
  • 15

2 Answers2

11

Your functions have a conflicting signature within the JVM due to type erasure (the internal Function0<T> class being used to represent the function parameters); and you can fix this by giving each of them a JVM specific name. From Kotlin you would still access them by the original name, but from Java or internally another name is actually used. Simply use the @JvmName annotation on the alternative versions:

fun <T> Foo<T>.myFunction(func: () -> Unit): Foo<T>

@JvmName("myfunctionWithFoo")   
fun <T> Foo<T>.myFunction(func: () -> Foo<T>): Foo<T>
Jayson Minard
  • 74,658
  • 30
  • 167
  • 210
  • I cannot access them from Kotlin, it gives me an error, I asked another question and the answer is there (it's not possible to access them) - https://stackoverflow.com/questions/51581179/jvmname-for-overloads-makes-code-to-compile-but-when-calling-the-functions-an-e?noredirect=1#comment90128671_51581477 – Alex Jul 29 '18 at 16:40
  • It is possible to access them @alex as mentioned in the other question, but this answer here is the correct answer to this specific question. The issue with your lambdas inferring their type at the same time the compiler is trying to disambiguate functions is a different issue; and a separate answer is given there for that problem. So for the error you have here, this is the correct answer. For others reading this with similar problems, they may not encounter the second issue because the method signatures might be more specific without the ambiguity. – Jayson Minard Jul 29 '18 at 17:39
10

On the JVM, we have to contend with type erasure. Meaning essentially that the types (T in this case) are thrown away in the compiled bytecode and that required checks are only done at compile time. Given that, you have to look at your function declaration with that in mind.

Kotlin will define your function argument as a Function0 in both cases. Because the types are erased, () -> Unit and () -> Foo<T> both look the same in the bytecode. We can prove this out by decompiling the code you've provided (I renamed one of these myFunction2 to get this to work):

public final class com/ginsberg/KotlinStuffKt {
    public final static myFunction(Lcom/ginsberg/Foo;Lkotlin/jvm/functions/Function0;)Lcom/ginsberg/Foo;

    public final static myFunction2(Lcom/ginsberg/Foo;Lkotlin/jvm/functions/Function0;)Lcom/ginsberg/Foo;

}

This is what the Kotlin compiler is generating (it does more, but I have removed the non-essential parts from this example). As you can see, our types are gone thanks to type erasure. And if we undo my change (myFunction2 becomes myFunction), there's no way at all to tell these apart. This is what the compiler is complaining about - if you erase the types the JVM can't tell these functions apart.

Todd
  • 26,412
  • 10
  • 67
  • 80
  • Thank you, good answer! I'll try to also use the Function for an overload to avoid renaming my method.. – Alex Jul 29 '18 at 12:59
  • 1
    Would the answer at https://stackoverflow.com/questions/35579828/i-have-two-kotlin-extension-methods-for-the-same-class-but-with-a-different-gen also work there? Since extensions functions are resolved statically and the function shape is known at compile time, but I could be wrong. – Hay Jul 29 '18 at 12:59
  • @Hay thanks, the methods now compile, but when I call them, it gives the following error: "Cannot choose among the following candidates without completing type inference: atJvmName public fun Foo.onSuccess(func: () → Foo): Foo defined in common.result atJvmName public fun Foo.onSuccess(func: () → Unit): Foo defined in common.result" – Alex Jul 29 '18 at 13:25
  • This answer doesn't tell you how to do it, it just identifies the problem. – Jayson Minard Jul 29 '18 at 15:44