Introduction
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
Suspending Functions
If my understanding of suspending function is correct, then:
- A
Continuation
parameter is added to a suspending function, used by the state machine to stop and start the suspending code - The return type used under the hood for suspend-marked functions is
Any
(ThusObject
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.
Analysis
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.
Java: t.kt -> t.decompiled.java
@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);
}
}
KB: fun foo(x: Int): Int
// 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
KB: suspend fun foo(x: Int): Int
// 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
Scoped Calls
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?
Conclusion
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).