Document coroutines codegen: callable references
returning inline classes
This commit is contained in:
committed by
Ilmir Usmanov
parent
fce06305b9
commit
995062cb19
@@ -3161,7 +3161,7 @@ suspend fun main() {
|
|||||||
println(callSuspend(::callMe))
|
println(callSuspend(::callMe))
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
Yep, `c` is both not suspend functional type and non-suspend functional type.
|
Yep, `c` is both suspend functional type and non-suspend functional type.
|
||||||
|
|
||||||
### Start
|
### Start
|
||||||
Unlike suspend lambdas, we cannot just call `create` when we start a coroutine (in a broad sense) from a callable reference. Since the
|
Unlike suspend lambdas, we cannot just call `create` when we start a coroutine (in a broad sense) from a callable reference. Since the
|
||||||
@@ -3170,4 +3170,46 @@ object does not have a `create` method and is not a continuation.
|
|||||||
Hence, instead, we write the continuation by hand, and in the `invokeSuspend` function, we write a state-machine by hand as well. See
|
Hence, instead, we write the continuation by hand, and in the `invokeSuspend` function, we write a state-machine by hand as well. See
|
||||||
`createCoroutineFromSuspendFunction` for specifics.
|
`createCoroutineFromSuspendFunction` for specifics.
|
||||||
|
|
||||||
FIXME: As explained in the tail-call suspend lambdas section, we can reuse this mechanism for tail-call suspend lambdas.
|
FIXME: As explained in the tail-call suspend lambdas section, we can reuse this mechanism for tail-call suspend lambdas.
|
||||||
|
|
||||||
|
### Returning Inline Classes
|
||||||
|
The following example:
|
||||||
|
```kotlin
|
||||||
|
import kotlin.coroutines.*
|
||||||
|
|
||||||
|
inline class IC(val a: Any)
|
||||||
|
|
||||||
|
suspend fun callSuspend(c: suspend () -> Any) {
|
||||||
|
println(c())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun builder(c: suspend () -> Unit) {
|
||||||
|
c.startCoroutine(Continuation(EmptyCoroutineContext) { it.getOrThrow() })
|
||||||
|
}
|
||||||
|
|
||||||
|
var c: Continuation<IC>? = null
|
||||||
|
|
||||||
|
suspend fun returnsIC() = suspendCoroutine<IC> { c = it }
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
builder {
|
||||||
|
callSuspend(::returnsIC)
|
||||||
|
}
|
||||||
|
c?.resume(IC("OK"))
|
||||||
|
}
|
||||||
|
```
|
||||||
|
used to throw KNPE inside `BaseContinuationImpl.resumeWith` before 1.4.20. The KNPE happens when we try to finish a coroutine (in a broad
|
||||||
|
sense) twice. Why wrapping a suspend function returning unboxed inline class leads to double finish?
|
||||||
|
|
||||||
|
The answer becomes apparent when we try to follow the execution of the example step by step.
|
||||||
|
1. `returnsIC` function returns `COROUTINE_SUSPENDED`.
|
||||||
|
2. The callable reference should return the boxed class since it overrides generic `FunctionN` and its `invoke` method returns the generic
|
||||||
|
type. Thus, it assumes that the function returns the unboxed type and boxes it.
|
||||||
|
3. The `BaseContinuationImpl.resumeWith` function does not receive `COROUTINE_SUSPENDED`, since it is boxed inside the inline class, and
|
||||||
|
runs shutdown procedure.
|
||||||
|
4. When we resume the execution, the suspend function returns the unboxed inline class.
|
||||||
|
5. The callable reference boxes it.
|
||||||
|
6. `BaseContinuationImpl.resumeWith` function runs shutdown procedure once again. The KNPE is thrown.
|
||||||
|
|
||||||
|
To fix the issue, callable references to suspend functions returning unboxed inline classes check for `COROUTINE_SUSPENDED` and only then
|
||||||
|
box the return value.
|
||||||
Reference in New Issue
Block a user