diff --git a/compiler/testData/codegen/box/coroutines/createCoroutinesOnManualInstances.kt b/compiler/testData/codegen/box/coroutines/createCoroutinesOnManualInstances.kt new file mode 100644 index 00000000000..e2fbbfd75b1 --- /dev/null +++ b/compiler/testData/codegen/box/coroutines/createCoroutinesOnManualInstances.kt @@ -0,0 +1,93 @@ +// WITH_RUNTIME +// IGNORE_BACKEND: JS +import kotlin.coroutines.experimental.* +import kotlin.coroutines.experimental.intrinsics.SUSPENDED_MARKER + +fun runCustomLambdaAsCoroutine(e: Throwable? = null, x: (Continuation) -> Any?): String { + var result = "fail" + var wasIntercepted = false + val c = (x as suspend () -> String).createCoroutine(object: Continuation { + override fun resumeWithException(exception: Throwable) { + throw exception + } + + override val context: CoroutineContext + get() = object: ContinuationInterceptor { + override fun fold(initial: R, operation: (R, CoroutineContext.Element) -> R): R { + throw IllegalStateException() + } + + override fun get(key: CoroutineContext.Key): E? { + if (key == ContinuationInterceptor.Key) { + return this as E + } + return null + } + + override fun interceptContinuation(continuation: Continuation) = object : Continuation { + override val context: CoroutineContext + get() = continuation.context + + override fun resume(value: T) { + wasIntercepted = true + continuation.resume(value) + } + + override fun resumeWithException(exception: Throwable) { + wasIntercepted = true + continuation.resumeWithException(exception) + } + } + + override fun minusKey(key: CoroutineContext.Key<*>): CoroutineContext { + throw IllegalStateException() + } + + override fun plus(context: CoroutineContext): CoroutineContext { + throw IllegalStateException() + } + + override val key: CoroutineContext.Key<*> + get() = ContinuationInterceptor.Key + } + + override fun resume(value: String) { + result = value + } + }) + + if (e != null) + c.resumeWithException(e) + else + c.resume(Unit) + + if (!wasIntercepted) return "was not intercepted" + + return result +} + +fun box(): String { + val x = runCustomLambdaAsCoroutine { + it.resume("OK") + SUSPENDED_MARKER + } + + if (x != "OK") return "fail 1: $x" + + val y = runCustomLambdaAsCoroutine { + "OK" + } + + if (y != "OK") return "fail 2: $x" + + + try { + runCustomLambdaAsCoroutine(RuntimeException("OK")) { + throw RuntimeException("fail 3") + } + } catch(e: Exception) { + return e.message!! + } + + return "fail 3" +} diff --git a/compiler/tests-ir-jvm/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java b/compiler/tests-ir-jvm/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java index 281e208000e..06cb0b741e2 100644 --- a/compiler/tests-ir-jvm/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java +++ b/compiler/tests-ir-jvm/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java @@ -4655,6 +4655,12 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes doTest(fileName); } + @TestMetadata("createCoroutinesOnManualInstances.kt") + public void testCreateCoroutinesOnManualInstances() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/createCoroutinesOnManualInstances.kt"); + doTest(fileName); + } + @TestMetadata("defaultParametersInSuspend.kt") public void testDefaultParametersInSuspend() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/defaultParametersInSuspend.kt"); diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java index 66b03c73482..e014b68eeab 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java @@ -4655,6 +4655,12 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest { doTest(fileName); } + @TestMetadata("createCoroutinesOnManualInstances.kt") + public void testCreateCoroutinesOnManualInstances() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/createCoroutinesOnManualInstances.kt"); + doTest(fileName); + } + @TestMetadata("defaultParametersInSuspend.kt") public void testDefaultParametersInSuspend() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/defaultParametersInSuspend.kt"); diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeCodegenTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeCodegenTestGenerated.java index 1c56ad44078..adc77e70688 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeCodegenTestGenerated.java @@ -4655,6 +4655,12 @@ public class LightAnalysisModeCodegenTestGenerated extends AbstractLightAnalysis doTest(fileName); } + @TestMetadata("createCoroutinesOnManualInstances.kt") + public void testCreateCoroutinesOnManualInstances() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/createCoroutinesOnManualInstances.kt"); + doTest(fileName); + } + @TestMetadata("defaultParametersInSuspend.kt") public void testDefaultParametersInSuspend() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/defaultParametersInSuspend.kt"); diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/JsCodegenBoxTestGenerated.java b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/JsCodegenBoxTestGenerated.java index 1cf59d826d6..eea89f8ae3a 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/JsCodegenBoxTestGenerated.java +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/JsCodegenBoxTestGenerated.java @@ -5346,6 +5346,18 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest { doTest(fileName); } + @TestMetadata("createCoroutinesOnManualInstances.kt") + public void testCreateCoroutinesOnManualInstances() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/createCoroutinesOnManualInstances.kt"); + try { + doTest(fileName); + } + catch (Throwable ignore) { + return; + } + throw new AssertionError("Looks like this test can be unmuted. Remove IGNORE_BACKEND directive for that."); + } + @TestMetadata("defaultParametersInSuspend.kt") public void testDefaultParametersInSuspend() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/defaultParametersInSuspend.kt"); diff --git a/libraries/stdlib/src/kotlin/coroutines/experimental/CoroutinesLibrary.kt b/libraries/stdlib/src/kotlin/coroutines/experimental/CoroutinesLibrary.kt index 81205bc0a5e..74110aee82e 100644 --- a/libraries/stdlib/src/kotlin/coroutines/experimental/CoroutinesLibrary.kt +++ b/libraries/stdlib/src/kotlin/coroutines/experimental/CoroutinesLibrary.kt @@ -23,6 +23,7 @@ import java.util.concurrent.atomic.AtomicReferenceFieldUpdater import kotlin.coroutines.experimental.intrinsics.SUSPENDED_MARKER import kotlin.coroutines.experimental.intrinsics.suspendCoroutineOrReturn import kotlin.coroutines.experimental.jvm.internal.CoroutineImpl +import kotlin.coroutines.experimental.jvm.internal.interceptContinuationIfNeeded /** * Creates coroutine with receiver type [R] and result type [T]. @@ -35,8 +36,14 @@ import kotlin.coroutines.experimental.jvm.internal.CoroutineImpl public fun (suspend R.() -> T).createCoroutine( receiver: R, completion: Continuation -): Continuation = - ((this as CoroutineImpl).create(receiver, completion) as CoroutineImpl).facade +): Continuation { + if (this !is CoroutineImpl) { + return buildContinuationByInvokeCall(completion) { + (this as Function2, Any?>).invoke(receiver, completion) + } + } + return ((this as CoroutineImpl).create(receiver, completion) as CoroutineImpl).facade +} /** * Starts coroutine with receiver type [R] and result type [T]. @@ -62,7 +69,14 @@ public fun (suspend R.() -> T).startCoroutine( @Suppress("UNCHECKED_CAST") public fun (suspend () -> T).createCoroutine( completion: Continuation -): Continuation = ((this as CoroutineImpl).create(completion) as CoroutineImpl).facade +): Continuation { + if (this !is CoroutineImpl) { + return buildContinuationByInvokeCall(completion) { + (this as Function1, Any?>).invoke(completion) + } + } + return ((this as CoroutineImpl).create(completion) as CoroutineImpl).facade +} /** * Starts coroutine without receiver and with result type [T]. @@ -95,6 +109,39 @@ public inline suspend fun suspendCoroutine(crossinline block: (Continuation< // INTERNAL DECLARATIONS +private inline fun buildContinuationByInvokeCall( + completion: Continuation, + crossinline block: () -> Any? +): Continuation { + val continuation = + object : Continuation { + override val context: CoroutineContext + get() = completion.context + + override fun resume(value: Unit) { + processInvokeCallOnCoroutine(completion, block) + } + + override fun resumeWithException(exception: Throwable) { + completion.resumeWithException(exception) + } + } + + return completion.context.interceptContinuationIfNeeded(continuation) +} + +private inline fun processInvokeCallOnCoroutine(completion: Continuation<*>, block: () -> Any?) { + try { + val result = block() + if (result !== SUSPENDED_MARKER) { + @Suppress("UNCHECKED_CAST") + (completion as Continuation).resume(result) + } + } catch (t: Throwable) { + completion.resumeWithException(t) + } +} + private val UNDECIDED: Any? = Any() private val RESUMED: Any? = Any() private class Fail(val exception: Throwable) diff --git a/libraries/stdlib/src/kotlin/coroutines/experimental/jvm/internal/CoroutineImpl.kt b/libraries/stdlib/src/kotlin/coroutines/experimental/jvm/internal/CoroutineImpl.kt index ec31eea1f74..3876121d9cf 100644 --- a/libraries/stdlib/src/kotlin/coroutines/experimental/jvm/internal/CoroutineImpl.kt +++ b/libraries/stdlib/src/kotlin/coroutines/experimental/jvm/internal/CoroutineImpl.kt @@ -22,6 +22,7 @@ import kotlin.coroutines.experimental.Continuation import kotlin.coroutines.experimental.ContinuationInterceptor import kotlin.coroutines.experimental.CoroutineContext import kotlin.coroutines.experimental.intrinsics.SUSPENDED_MARKER +import kotlin.coroutines.experimental.jvm.internal.interceptContinuationIfNeeded import kotlin.jvm.internal.Lambda abstract class CoroutineImpl( @@ -43,7 +44,7 @@ abstract class CoroutineImpl( private var _facade: Continuation? = null val facade: Continuation get() { - if (_facade == null) _facade = _context!![ContinuationInterceptor]?.interceptContinuation(this) ?: this + if (_facade == null) _facade = _context!!.interceptContinuationIfNeeded(this) return _facade!! } diff --git a/libraries/stdlib/src/kotlin/coroutines/experimental/jvm/internal/CoroutineIntrinsics.kt b/libraries/stdlib/src/kotlin/coroutines/experimental/jvm/internal/CoroutineIntrinsics.kt index 9d63dd1eb24..74c4b8de03c 100644 --- a/libraries/stdlib/src/kotlin/coroutines/experimental/jvm/internal/CoroutineIntrinsics.kt +++ b/libraries/stdlib/src/kotlin/coroutines/experimental/jvm/internal/CoroutineIntrinsics.kt @@ -19,7 +19,13 @@ package kotlin.coroutines.experimental.jvm.internal import kotlin.coroutines.experimental.Continuation +import kotlin.coroutines.experimental.CoroutineContext +import kotlin.coroutines.experimental.ContinuationInterceptor import kotlin.coroutines.experimental.jvm.internal.CoroutineImpl fun normalizeContinuation(continuation: Continuation): Continuation = (continuation as? CoroutineImpl)?.facade ?: continuation + +internal fun CoroutineContext.interceptContinuationIfNeeded( + continuation: Continuation +) = this[ContinuationInterceptor]?.interceptContinuation(continuation) ?: continuation