Given two functions, foo()
and foo()
, the first one is standard and the second is suspendible
fun foo(x: Int): Int {
return 2*x
}
suspend fun foo(x: Int): Int {
return 4*x
}
The following code does not compile, because two functions with the same signature are conflicting.
conflicting overloads: public fun foo(x: Int): Int defined in file t.kt, public suspend fun foo(x: Int): Int defined in file t.kt
If my understanding of suspending function is correct, then:
Continuation
parameter is added to a suspending function, used by the state machine to stop and start the suspending codeAny
(Thus Object
for java)Those two side effects in theory should be enough to alter the second foo()
function signature, hence to view the suspend-marked functions differently from the first one.
At first, I have supposed that the function signature check may be performed before actually compiling the code into bytecode. However, having the two presented functions turned into actual bytecode actually results in 2 methods having 2 different signatures.
@Metadata(
mv = {1, 1, 16},
bv = {1, 0, 3},
k = 2,
d1 = {"\u0000\n\n\u0000\n\u0002\u0010\b\n\u0002\b\u0003\u001a\u000e\u0010\u0000\u001a\u00020\u00012\u0006\u0010\u0002\u001a\u00020\u0001\u001a\u0019\u0010\u0000\u001a\u00020\u00012\u0006\u0010\u0002\u001a\u00020\u0001H\u0086@ø\u0001\u0000¢\u0006\u0002\u0010\u0003\u0082\u0002\u0004\n\u0002\b\u0019¨\u0006\u0004"},
d2 = {"foo", "", "x", "(ILkotlin/coroutines/Continuation;)Ljava/lang/Object;", "app"}
)
public final class TKt {
public static final int foo(int x) {
return 2 * x;
}
@Nullable
public static final Object foo(int x, @NotNull Continuation $completion) {
return Boxing.boxInt(4 * x);
}
}
// access flags 0x19
public final static foo(I)I
// annotable parameter count: 1 (visible)
// annotable parameter count: 1 (invisible)
L0
LINENUMBER 4 L0
ICONST_2
ILOAD 0
IMUL
IRETURN
L1
LOCALVARIABLE x I L0 L1 0
MAXSTACK = 2
MAXLOCALS = 1
// access flags 0x19
// signature (ILkotlin/coroutines/Continuation<-Ljava/lang/Integer;>;)Ljava/lang/Object;
// declaration: foo(int, kotlin.coroutines.Continuation<? super java.lang.Integer>)
public final static foo(ILkotlin/coroutines/Continuation;)Ljava/lang/Object; @Lorg/jetbrains/annotations/Nullable;() // invisible
// annotable parameter count: 2 (visible)
// annotable parameter count: 2 (invisible)
@Lorg/jetbrains/annotations/NotNull;() // invisible, parameter 1
L0
LINENUMBER 8 L0
ICONST_4
ILOAD 0
IMUL
INVOKESTATIC kotlin/coroutines/jvm/internal/Boxing.boxInt (I)Ljava/lang/Integer;
ARETURN
L1
LOCALVARIABLE x I L0 L1 0
LOCALVARIABLE $completion Lkotlin/coroutines/Continuation; L0 L1 1
MAXSTACK = 2
MAXLOCALS = 2
At this point I thought that it is not always possible for kotlin to decide which function to call. Sure, these two functions are completely different and separate, their signature has not even a partial match (Different return type and arguments)
The point is that in the kotlin word, the suspending function can only be called from inside a coroutine scope, but the normal function can be called from both places. The following table can serve as a great example to graphically analyse the situation.
+---------------+---------------+-----------------+
| | Default Scope | Coroutine Scope |
+---------------+---------------+-----------------+
| foo() | ✓ | ✓ |
+---------------+---------------+-----------------+
| suspend foo() | ✘ | ✓ |
+---------------+---------------+-----------------+
The only scenario that may involve a definition collision between these two entities is the following.
fun foo(x: Int): Int {
return 2*x
}
suspend fun foo(x: Int): Int {
return 4*x
}
GlobalScope.launch {
println(foo(7))
}
In this case, without an hypothetical (a.k.a. Existing only in my head) operator letting Kotlin know which function to invoke, if the suspendible one or the standard one, you can't be sure about which function you are invoking.
Is this analysis correct or am i missing something in between?
This question will be linked in a YouTrack issue with a similar content, and this may be the starting point for a compiler improvement (Maybe differentiating overload errors from suspendible clashing with standard function error), or for a new Kotlin feature, expanding the suspendible functions interoperability with normal functions (I'm imagining a sort of spread-like operator which is prefixed to the function call, and the presence of the operator differentiates one call from another).
You are right in regards of bytecode - the signatures are different.
However it is unable to determine function unambiguously from Kotlin language side. For example, what method should be called below?
fun main() {
runBlocking {
println(foo(1)) // which one should be called here?
}
}
fun foo(x: Int): Int {
return 2*x
}
suspend fun foo(x: Int): Int {
return 4*x
}
The same behavior is with Java Synthetic methods. Please check this answer - you can define two methods in bytecode, however it isn't allowed in Java Language syntax.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With