c62e4b4fcf
* Move FinallyBlockLowering to common part * Fix catching of dynamic exception * Fix bridges for suspend functions * Disable explicit cast to Unit * Run lowering per module * Update some test data
323 lines
12 KiB
Kotlin
323 lines
12 KiB
Kotlin
/*
|
|
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
|
* that can be found in the license/LICENSE.txt file.
|
|
*/
|
|
|
|
package kotlin.coroutines.experimental
|
|
|
|
import kotlin.coroutines.experimental.intrinsics.*
|
|
|
|
/**
|
|
* Starts coroutine with receiver type [R] and result type [T].
|
|
* This function creates and start a new, fresh instance of suspendable computation every time it is invoked.
|
|
* The [completion] continuation is invoked when coroutine completes with result or exception.
|
|
*/
|
|
@SinceKotlin("1.1")
|
|
@Suppress("UNCHECKED_CAST")
|
|
public fun <R, T> (suspend R.() -> T).startCoroutine(
|
|
receiver: R,
|
|
completion: Continuation<T>
|
|
) {
|
|
createCoroutineUnchecked(receiver, completion).resume(Unit)
|
|
}
|
|
|
|
/**
|
|
* Starts coroutine without receiver and with result type [T].
|
|
* This function creates and start a new, fresh instance of suspendable computation every time it is invoked.
|
|
* The [completion] continuation is invoked when coroutine completes with result or exception.
|
|
*/
|
|
@SinceKotlin("1.1")
|
|
@Suppress("UNCHECKED_CAST")
|
|
public fun <T> (suspend () -> T).startCoroutine(
|
|
completion: Continuation<T>
|
|
) {
|
|
createCoroutineUnchecked(completion).resume(Unit)
|
|
}
|
|
|
|
/**
|
|
* Creates a coroutine with receiver type [R] and result type [T].
|
|
* This function creates a new, fresh instance of suspendable computation every time it is invoked.
|
|
*
|
|
* To start executing the created coroutine, invoke `resume(Unit)` on the returned [Continuation] instance.
|
|
* The [completion] continuation is invoked when coroutine completes with result or exception.
|
|
* Repeated invocation of any resume function on the resulting continuation produces [IllegalStateException].
|
|
*/
|
|
@SinceKotlin("1.1")
|
|
@Suppress("UNCHECKED_CAST")
|
|
public fun <R, T> (suspend R.() -> T).createCoroutine(
|
|
receiver: R,
|
|
completion: Continuation<T>
|
|
): Continuation<Unit> = SafeContinuation(createCoroutineUnchecked(receiver, completion), COROUTINE_SUSPENDED)
|
|
|
|
/**
|
|
* Creates a coroutine without receiver and with result type [T].
|
|
* This function creates a new, fresh instance of suspendable computation every time it is invoked.
|
|
*
|
|
* To start executing the created coroutine, invoke `resume(Unit)` on the returned [Continuation] instance.
|
|
* The [completion] continuation is invoked when coroutine completes with result or exception.
|
|
* Repeated invocation of any resume function on the resulting continuation produces [IllegalStateException].
|
|
*/
|
|
@SinceKotlin("1.1")
|
|
@Suppress("UNCHECKED_CAST")
|
|
public fun <T> (suspend () -> T).createCoroutine(
|
|
completion: Continuation<T>
|
|
): Continuation<Unit> = SafeContinuation(createCoroutineUnchecked(completion), COROUTINE_SUSPENDED)
|
|
|
|
// TODO: remove this once implemented in stdlib
|
|
inline fun <T, R> T.let(f: (T) -> R) = f(this)
|
|
inline fun <R> run(f: () -> R) = f()
|
|
|
|
public interface CoroutineContext {
|
|
/**
|
|
* Returns the element with the given [key] from this context or `null`.
|
|
* Keys are compared _by reference_, that is to get an element from the context the reference to its actual key
|
|
* object must be presented to this function.
|
|
*/
|
|
public operator fun <E : Element> get(key: Key<E>): E?
|
|
|
|
/**
|
|
* Accumulates entries of this context starting with [initial] value and applying [operation]
|
|
* from left to right to current accumulator value and each element of this context.
|
|
*/
|
|
public fun <R> fold(initial: R, operation: (R, Element) -> R): R
|
|
|
|
/**
|
|
* Returns a context containing elements from this context and elements from other [context].
|
|
* The elements from this context with the same key as in the other one are dropped.
|
|
*/
|
|
public operator fun plus(context: CoroutineContext): CoroutineContext =
|
|
if (context === EmptyCoroutineContext) this else // fast path -- avoid lambda creation
|
|
context.fold(this) { acc, element ->
|
|
val removed = acc.minusKey(element.key)
|
|
if (removed === EmptyCoroutineContext) element else {
|
|
// make sure interceptor is always last in the context (and thus is fast to get when present)
|
|
val interceptor = removed[ContinuationInterceptor]
|
|
if (interceptor == null) CombinedContext(removed, element) else {
|
|
val left = removed.minusKey(ContinuationInterceptor)
|
|
if (left === EmptyCoroutineContext) CombinedContext(element, interceptor) else
|
|
CombinedContext(CombinedContext(left, element), interceptor)
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns a context containing elements from this context, but without an element with
|
|
* the specified [key]. Keys are compared _by reference_, that is to remove an element from the context
|
|
* the reference to its actual key object must be presented to this function.
|
|
*/
|
|
public fun minusKey(key: Key<*>): CoroutineContext
|
|
|
|
/**
|
|
* An element of the [CoroutineContext]. An element of the coroutine context is a singleton context by itself.
|
|
*/
|
|
public interface Element : CoroutineContext {
|
|
/**
|
|
* A key of this coroutine context element.
|
|
*/
|
|
public val key: Key<*>
|
|
|
|
@Suppress("UNCHECKED_CAST")
|
|
public override operator fun <E : Element> get(key: Key<E>): E? =
|
|
if (this.key === key) this as E else null
|
|
|
|
public override fun <R> fold(initial: R, operation: (R, Element) -> R): R =
|
|
operation(initial, this)
|
|
|
|
public override fun minusKey(key: Key<*>): CoroutineContext =
|
|
if (this.key === key) EmptyCoroutineContext else this
|
|
}
|
|
|
|
/**
|
|
* Key for the elements of [CoroutineContext]. [E] is a type of element with this key.
|
|
* Keys in the context are compared _by reference_.
|
|
*/
|
|
public interface Key<E : Element>
|
|
}
|
|
|
|
public abstract class AbstractCoroutineContextElement(public override val key: CoroutineContext.Key<*>) : CoroutineContext.Element
|
|
|
|
public interface ContinuationInterceptor : CoroutineContext.Element {
|
|
/**
|
|
* The key that defines *the* context interceptor.
|
|
*/
|
|
companion object Key : CoroutineContext.Key<ContinuationInterceptor>
|
|
|
|
/**
|
|
* Returns continuation that wraps the original [continuation], thus intercepting all resumptions.
|
|
* This function is invoked by coroutines framework when needed and the resulting continuations are
|
|
* cached internally per each instance of the original [continuation].
|
|
*
|
|
* By convention, implementations that install themselves as *the* interceptor in the context with
|
|
* the [Key] shall also scan the context for other element that implement [ContinuationInterceptor] interface
|
|
* and use their [interceptContinuation] functions, too.
|
|
*/
|
|
public fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T>
|
|
}
|
|
|
|
internal class CombinedContext(val left: CoroutineContext, val element: CoroutineContext.Element) : CoroutineContext {
|
|
override fun <E : CoroutineContext.Element> get(key: CoroutineContext.Key<E>): E? {
|
|
var cur = this
|
|
while (true) {
|
|
cur.element[key]?.let { return it }
|
|
val next = cur.left
|
|
if (next is CombinedContext) {
|
|
cur = next
|
|
} else {
|
|
return next[key]
|
|
}
|
|
}
|
|
}
|
|
|
|
public override fun <R> fold(initial: R, operation: (R, CoroutineContext.Element) -> R): R =
|
|
operation(left.fold(initial, operation), element)
|
|
|
|
public override fun minusKey(key: CoroutineContext.Key<*>): CoroutineContext {
|
|
element[key]?.let { return left }
|
|
val newLeft = left.minusKey(key)
|
|
return when {
|
|
newLeft === left -> this
|
|
newLeft === EmptyCoroutineContext -> element
|
|
else -> CombinedContext(newLeft, element)
|
|
}
|
|
}
|
|
|
|
private fun size(): Int =
|
|
if (left is CombinedContext) left.size() + 1 else 2
|
|
|
|
private fun contains(element: CoroutineContext.Element): Boolean =
|
|
get(element.key) == element
|
|
|
|
private fun containsAll(context: CombinedContext): Boolean {
|
|
var cur = context
|
|
while (true) {
|
|
if (!contains(cur.element)) return false
|
|
val next = cur.left
|
|
if (next is CombinedContext) {
|
|
cur = next
|
|
} else {
|
|
return contains(next as CoroutineContext.Element)
|
|
}
|
|
}
|
|
}
|
|
|
|
override fun equals(other: Any?): Boolean =
|
|
this === other || other is CombinedContext && other.size() == size() && other.containsAll(this)
|
|
|
|
override fun hashCode(): Int = left.hashCode() + element.hashCode()
|
|
|
|
override fun toString(): String = "CC"
|
|
}
|
|
|
|
public object EmptyCoroutineContext : CoroutineContext {
|
|
public override fun <E : CoroutineContext.Element> get(key: CoroutineContext.Key<E>): E? = null
|
|
public override fun <R> fold(initial: R, operation: (R, CoroutineContext.Element) -> R): R = initial
|
|
public override fun plus(context: CoroutineContext): CoroutineContext = context
|
|
public override fun minusKey(key: CoroutineContext.Key<*>): CoroutineContext = this
|
|
public override fun hashCode(): Int = 0
|
|
public override fun toString(): String = "EmptyCoroutineContext"
|
|
}
|
|
|
|
public interface Continuation<in T> {
|
|
/**
|
|
* Context of the coroutine that corresponds to this continuation.
|
|
*/
|
|
public val context: CoroutineContext
|
|
|
|
/**
|
|
* Resumes the execution of the corresponding coroutine passing [value] as the return value of the last suspension point.
|
|
*/
|
|
public fun resume(value: T)
|
|
|
|
/**
|
|
* Resumes the execution of the corresponding coroutine so that the [exception] is re-thrown right after the
|
|
* last suspension point.
|
|
*/
|
|
public fun resumeWithException(exception: Throwable)
|
|
}
|
|
|
|
public class SafeContinuation<in T>
|
|
public constructor(
|
|
private val delegate: Continuation<T>,
|
|
initialResult: Any?
|
|
) : Continuation<T> {
|
|
|
|
public constructor(delegate: Continuation<T>) : this(delegate, UNDECIDED)
|
|
|
|
public override val context: CoroutineContext
|
|
get() = delegate.context
|
|
|
|
private var result: Any? = initialResult
|
|
|
|
override fun resume(value: T) {
|
|
when {
|
|
result === UNDECIDED -> {
|
|
result = value
|
|
}
|
|
result === COROUTINE_SUSPENDED -> {
|
|
result = RESUMED
|
|
delegate.resume(value)
|
|
}
|
|
else -> {
|
|
throw IllegalStateException("Already resumed")
|
|
}
|
|
}
|
|
}
|
|
|
|
override fun resumeWithException(exception: Throwable) {
|
|
when {
|
|
result === UNDECIDED -> {
|
|
result = Fail(exception)
|
|
}
|
|
result === COROUTINE_SUSPENDED -> {
|
|
result = RESUMED
|
|
delegate.resumeWithException(exception)
|
|
}
|
|
else -> {
|
|
throw IllegalStateException("Already resumed")
|
|
}
|
|
}
|
|
}
|
|
|
|
public fun getResult(): Any? {
|
|
if (result === UNDECIDED) {
|
|
result = COROUTINE_SUSPENDED
|
|
}
|
|
val result = this.result
|
|
return when {
|
|
result === RESUMED -> {
|
|
COROUTINE_SUSPENDED // already called continuation, indicate SUSPENDED upstream
|
|
}
|
|
result is Fail -> {
|
|
throw result.exception
|
|
}
|
|
else -> {
|
|
result // either SUSPENDED or data
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
suspend inline fun <T> suspendCoroutine(crossinline block: (Continuation<T>) -> Unit): T =
|
|
suspendCoroutineOrReturn { c: Continuation<T> ->
|
|
val safe = SafeContinuation(c)
|
|
block(safe)
|
|
safe.getResult()
|
|
}
|
|
|
|
private val UNDECIDED: Any? = Any()
|
|
private val RESUMED: Any? = Any()
|
|
|
|
private class Fail(val exception: Throwable)
|
|
|
|
@kotlin.internal.InlineOnly
|
|
internal inline fun processBareContinuationResume(completion: Continuation<*>, block: () -> Any?) {
|
|
try {
|
|
val result = block()
|
|
if (result !== COROUTINE_SUSPENDED) {
|
|
@Suppress("UNCHECKED_CAST")
|
|
(completion as Continuation<Any?>).resume(result)
|
|
}
|
|
} catch (t: Throwable) {
|
|
completion.resumeWithException(t)
|
|
}
|
|
} |