diff --git a/compiler/backend-common/src/org/jetbrains/kotlin/backend/common/commonCoroutineCodegenUtil.kt b/compiler/backend-common/src/org/jetbrains/kotlin/backend/common/commonCoroutineCodegenUtil.kt index c417567114f..21573b0a974 100644 --- a/compiler/backend-common/src/org/jetbrains/kotlin/backend/common/commonCoroutineCodegenUtil.kt +++ b/compiler/backend-common/src/org/jetbrains/kotlin/backend/common/commonCoroutineCodegenUtil.kt @@ -16,6 +16,7 @@ package org.jetbrains.kotlin.backend.common +import org.jetbrains.kotlin.descriptors.CallableDescriptor import org.jetbrains.kotlin.descriptors.FunctionDescriptor import org.jetbrains.kotlin.descriptors.PropertyGetterDescriptor import org.jetbrains.kotlin.incremental.components.NoLookupLocation @@ -26,11 +27,23 @@ import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe import org.jetbrains.kotlin.resolve.descriptorUtil.module val SUSPEND_COROUTINE_OR_RETURN_NAME = Name.identifier("suspendCoroutineOrReturn") +val INTERCEPTED_NAME = Name.identifier("intercepted") val COROUTINE_SUSPENDED_NAME = Name.identifier("COROUTINE_SUSPENDED") val COROUTINES_INTRINSICS_PACKAGE_FQ_NAME = DescriptorUtils.COROUTINES_PACKAGE_FQ_NAME.child(Name.identifier("intrinsics")) val COROUTINE_CONTEXT_FQ_NAME = COROUTINES_INTRINSICS_PACKAGE_FQ_NAME.child(Name.identifier("coroutineContext")) +val SUSPEND_COROUTINE_UNINTERCEPTED_OR_RETURN_NAME = Name.identifier("suspendCoroutineUninterceptedOrReturn") + +fun FunctionDescriptor.isBuiltInIntercepted(): Boolean { + if (name != INTERCEPTED_NAME) return false + val original = + module.getPackage(COROUTINES_INTRINSICS_PACKAGE_FQ_NAME).memberScope + .getContributedFunctions(INTERCEPTED_NAME, NoLookupLocation.FROM_BACKEND) + .singleOrNull() as CallableDescriptor + return DescriptorEquivalenceForOverrides.areEquivalent(original, this) +} + fun FunctionDescriptor.isBuiltInSuspendCoroutineOrReturn(): Boolean { if (name != SUSPEND_COROUTINE_OR_RETURN_NAME) return false @@ -49,3 +62,10 @@ fun FunctionDescriptor.getBuiltInSuspendCoroutineOrReturn() = fun FunctionDescriptor.isBuiltInCoroutineContext() = (this as? PropertyGetterDescriptor)?.correspondingProperty?.fqNameSafe == COROUTINE_CONTEXT_FQ_NAME +fun FunctionDescriptor.isBuiltInSuspendCoroutineUninterceptedOrReturn(): Boolean { + if (name != SUSPEND_COROUTINE_UNINTERCEPTED_OR_RETURN_NAME) return false + val original = module.getPackage(COROUTINES_INTRINSICS_PACKAGE_FQ_NAME).memberScope + .getContributedFunctions(SUSPEND_COROUTINE_UNINTERCEPTED_OR_RETURN_NAME, NoLookupLocation.FROM_BACKEND) + .singleOrNull() as CallableDescriptor + return DescriptorEquivalenceForOverrides.areEquivalent(original, this) +} diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/coroutines/coroutineCodegenUtil.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/coroutines/coroutineCodegenUtil.kt index e7c22b636e5..0640a54765a 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/coroutines/coroutineCodegenUtil.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/coroutines/coroutineCodegenUtil.kt @@ -172,7 +172,7 @@ private fun NewResolvedCallImpl.asDummyOldResolvedCall(bindi fun ResolvedCall<*>.isSuspendNoInlineCall() = resultingDescriptor.safeAs() ?.let { - it.isSuspend && (!it.isInline || it.isBuiltInSuspendCoroutineOrReturnInJvm()) + it.isSuspend && (!it.isInline || it.isBuiltInSuspendCoroutineOrReturnInJvm() || it.isBuiltInSuspendCoroutineUninterceptedOrReturnInJvm()) } == true fun CallableDescriptor.isSuspendFunctionNotSuspensionView(): Boolean { @@ -245,7 +245,6 @@ fun ModuleDescriptor.getContinuationOfTypeOrAny(kotlinType: KotlinType) = ) } ?: module.builtIns.nullableAnyType - fun FunctionDescriptor.isBuiltInSuspendCoroutineOrReturnInJvm() = getUserData(INITIAL_DESCRIPTOR_FOR_SUSPEND_FUNCTION)?.isBuiltInSuspendCoroutineOrReturn() == true @@ -289,6 +288,40 @@ fun createMethodNodeForSuspendCoroutineOrReturn( return node } +fun FunctionDescriptor.isBuiltInSuspendCoroutineUninterceptedOrReturnInJvm() = + getUserData(INITIAL_DESCRIPTOR_FOR_SUSPEND_FUNCTION)?.isBuiltInSuspendCoroutineUninterceptedOrReturn() == true + +fun createMethodNodeForIntercepted( + functionDescriptor: FunctionDescriptor, + typeMapper: KotlinTypeMapper +): MethodNode { + assert(functionDescriptor.isBuiltInIntercepted()) { + "functionDescriptor must be kotlin.coroutines.intrinsics.intercepted" + } + + val node = + MethodNode( + Opcodes.ASM5, + Opcodes.ACC_STATIC, + "fake", + typeMapper.mapAsmMethod(functionDescriptor).descriptor, null, null + ) + + node.visitVarInsn(Opcodes.ALOAD, 0) + + node.visitMethodInsn( + Opcodes.INVOKESTATIC, + INTERNAL_COROUTINE_INTRINSICS_OWNER_INTERNAL_NAME, + NORMALIZE_CONTINUATION_METHOD_NAME, + Type.getMethodDescriptor(CONTINUATION_ASM_TYPE, CONTINUATION_ASM_TYPE), + false + ) + node.visitInsn(Opcodes.ARETURN) + node.visitMaxs(1, 1) + + return node +} + fun createMethodNodeForCoroutineContext(functionDescriptor: FunctionDescriptor): MethodNode { assert(functionDescriptor.isBuiltInCoroutineContext()) { "functionDescriptor must be kotlin.coroutines.intrinsics.coroutineContext property getter" @@ -318,6 +351,39 @@ fun createMethodNodeForCoroutineContext(functionDescriptor: FunctionDescriptor): return node } + +fun createMethodNodeForSuspendCoroutineUninterceptedOrReturn( + functionDescriptor: FunctionDescriptor, + typeMapper: KotlinTypeMapper +): MethodNode { + assert(functionDescriptor.isBuiltInSuspendCoroutineUninterceptedOrReturnInJvm()) { + "functionDescriptor must be kotlin.coroutines.intrinsics.suspendCoroutineUninterceptedOrReturn" + } + + val node = + MethodNode( + Opcodes.ASM5, + Opcodes.ACC_STATIC, + "fake", + typeMapper.mapAsmMethod(functionDescriptor).descriptor, null, null + ) + + node.visitVarInsn(Opcodes.ALOAD, 0) + node.visitVarInsn(Opcodes.ALOAD, 1) + + node.visitMethodInsn( + Opcodes.INVOKEINTERFACE, + typeMapper.mapType(functionDescriptor.valueParameters[0]).internalName, + OperatorNameConventions.INVOKE.identifier, + "(${AsmTypes.OBJECT_TYPE})${AsmTypes.OBJECT_TYPE}", + true + ) + node.visitInsn(Opcodes.ARETURN) + node.visitMaxs(2, 2) + + return node +} + @Suppress("UNCHECKED_CAST") fun D.unwrapInitialDescriptorForSuspendFunction(): D = this.safeAs()?.getUserData(INITIAL_DESCRIPTOR_FOR_SUSPEND_FUNCTION) as D ?: this diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/InlineCodegen.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/InlineCodegen.kt index 9b8d5f55936..5ae4c6d8d8d 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/InlineCodegen.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/InlineCodegen.kt @@ -19,6 +19,7 @@ package org.jetbrains.kotlin.codegen.inline import com.intellij.psi.PsiElement import com.intellij.util.ArrayUtil import org.jetbrains.kotlin.backend.common.isBuiltInCoroutineContext +import org.jetbrains.kotlin.backend.common.isBuiltInIntercepted import org.jetbrains.kotlin.builtins.BuiltInsPackageFragment import org.jetbrains.kotlin.codegen.* import org.jetbrains.kotlin.codegen.AsmUtil.getMethodAsmFlags @@ -490,13 +491,23 @@ abstract class InlineCodegen( } functionDescriptor.isBuiltInSuspendCoroutineOrReturnInJvm() -> return SMAPAndMethodNode( - createMethodNodeForSuspendCoroutineOrReturn(functionDescriptor, state.typeMapper), - SMAPParser.parseOrCreateDefault(null, null, "fake", -1, -1) + createMethodNodeForSuspendCoroutineOrReturn(functionDescriptor, state.typeMapper), + SMAPParser.parseOrCreateDefault(null, null, "fake", -1, -1) + ) + functionDescriptor.isBuiltInIntercepted() -> + return SMAPAndMethodNode( + createMethodNodeForIntercepted(functionDescriptor, state.typeMapper), + SMAPParser.parseOrCreateDefault(null, null, "fake", -1, -1) ) functionDescriptor.isBuiltInCoroutineContext() -> return SMAPAndMethodNode( - createMethodNodeForCoroutineContext(functionDescriptor), - SMAPParser.parseOrCreateDefault(null, null, "fake", -1, -1) + createMethodNodeForCoroutineContext(functionDescriptor), + SMAPParser.parseOrCreateDefault(null, null, "fake", -1, -1) + ) + functionDescriptor.isBuiltInSuspendCoroutineUninterceptedOrReturnInJvm() -> + return SMAPAndMethodNode( + createMethodNodeForSuspendCoroutineUninterceptedOrReturn(functionDescriptor, state.typeMapper), + SMAPParser.parseOrCreateDefault(null, null, "fake", -1, -1) ) } diff --git a/compiler/testData/codegen/box/coroutines/intrinsicSemantics/intercepted.kt b/compiler/testData/codegen/box/coroutines/intrinsicSemantics/intercepted.kt new file mode 100644 index 00000000000..ed7d186092d --- /dev/null +++ b/compiler/testData/codegen/box/coroutines/intrinsicSemantics/intercepted.kt @@ -0,0 +1,82 @@ +// WITH_RUNTIME +// WITH_COROUTINES +import helpers.* +import kotlin.coroutines.experimental.* +import kotlin.coroutines.experimental.intrinsics.* +import kotlin.test.assertEquals + +suspend fun suspendHereUnintercepted(): String = suspendCoroutineUninterceptedOrReturn { x -> + x.resume("OK") + COROUTINE_SUSPENDED +} + +suspend fun suspendWithExceptionUnintercepted(): String = suspendCoroutineUninterceptedOrReturn { x -> + x.resumeWithException(RuntimeException("OK")) + COROUTINE_SUSPENDED +} + +suspend fun suspendHereIntercepted(): String = suspendCoroutineUninterceptedOrReturn { x -> + x.intercepted().resume("OK") + COROUTINE_SUSPENDED +} + +suspend fun suspendWithExceptionIntercepted(): String = suspendCoroutineUninterceptedOrReturn { x -> + x.intercepted().resumeWithException(RuntimeException("OK")) + COROUTINE_SUSPENDED +} + +fun builder(expectedCount: Int, c: suspend () -> String): String { + var fromSuspension: String? = null + var counter = 0 + + val result = try { + c.startCoroutineUninterceptedOrReturn(object: Continuation { + override val context: CoroutineContext + get() = ContinuationDispatcher { counter++ } + + override fun resumeWithException(exception: Throwable) { + fromSuspension = "Exception: " + exception.message!! + } + + override fun resume(value: String) { + fromSuspension = value + } + }) + } catch (e: Exception) { + "Exception: ${e.message}" + } + + if (counter != expectedCount) throw RuntimeException("fail 0") + return fromSuspension!! +} + +class ContinuationDispatcher(val dispatcher: () -> Unit) : AbstractCoroutineContextElement(ContinuationInterceptor), ContinuationInterceptor { + override fun interceptContinuation(continuation: Continuation): Continuation = DispatchedContinuation(dispatcher, continuation) +} + +private class DispatchedContinuation( + val dispatcher: () -> Unit, + val continuation: Continuation +): Continuation { + override val context: CoroutineContext = continuation.context + + override fun resume(value: T) { + dispatcher() + continuation.resume(value) + } + + override fun resumeWithException(exception: Throwable) { + dispatcher() + continuation.resumeWithException(exception) + } +} + +fun box(): String { + if (builder(0) { suspendHereUnintercepted() } != "OK") return "fail 2" + if (builder(1) { suspendHereIntercepted() } != "OK") return "fail 3" + + if (builder(0) { suspendWithExceptionUnintercepted() } != "Exception: OK") return "fail 4" + if (builder(1) { suspendWithExceptionIntercepted() } != "Exception: OK") return "fail 5" + + return "OK" +} diff --git a/compiler/testData/codegen/box/coroutines/intrinsicSemantics/suspendCoroutineUninterceptedOrReturn.kt b/compiler/testData/codegen/box/coroutines/intrinsicSemantics/suspendCoroutineUninterceptedOrReturn.kt new file mode 100644 index 00000000000..a190beb19c1 --- /dev/null +++ b/compiler/testData/codegen/box/coroutines/intrinsicSemantics/suspendCoroutineUninterceptedOrReturn.kt @@ -0,0 +1,80 @@ +// WITH_RUNTIME +// WITH_COROUTINES +import helpers.* +import kotlin.coroutines.experimental.* +import kotlin.coroutines.experimental.intrinsics.* +import kotlin.test.assertEquals + +suspend fun suspendHere(): String = suspendCoroutineUninterceptedOrReturn { x -> + x.resume("OK") + COROUTINE_SUSPENDED +} + +suspend fun suspendWithException(): String = suspendCoroutineUninterceptedOrReturn { x -> + x.resumeWithException(RuntimeException("OK")) + COROUTINE_SUSPENDED +} + +fun builder(shouldSuspend: Boolean, c: suspend () -> String): String { + var fromSuspension: String? = null + var counter = 0 + + val result = try { + c.startCoroutineUninterceptedOrReturn(object: Continuation { + override val context: CoroutineContext + get() = ContinuationDispatcher { counter++ } + + override fun resumeWithException(exception: Throwable) { + fromSuspension = "Exception: " + exception.message!! + } + + override fun resume(value: String) { + fromSuspension = value + } + }) + } catch (e: Exception) { + "Exception: ${e.message}" + } + + if (counter != 0) throw RuntimeException("fail 0 $counter") + + if (shouldSuspend) { + if (result !== COROUTINE_SUSPENDED) throw RuntimeException("fail 1") + if (fromSuspension == null) throw RuntimeException("fail 2") + return fromSuspension!! + } + + if (result === COROUTINE_SUSPENDED) throw RuntimeException("fail 3") + return result as String +} + +class ContinuationDispatcher(val dispatcher: () -> Unit) : AbstractCoroutineContextElement(ContinuationInterceptor), ContinuationInterceptor { + override fun interceptContinuation(continuation: Continuation): Continuation = DispatchedContinuation(dispatcher, continuation) +} + +private class DispatchedContinuation( + val dispatcher: () -> Unit, + val continuation: Continuation +): Continuation { + override val context: CoroutineContext = continuation.context + + override fun resume(value: T) { + dispatcher() + continuation.resume(value) + } + + override fun resumeWithException(exception: Throwable) { + dispatcher() + continuation.resumeWithException(exception) + } +} + +fun box(): String { + if (builder(false) { "OK" } != "OK") return "fail 4" + if (builder(true) { suspendHere() } != "OK") return "fail 5" + + if (builder(false) { throw RuntimeException("OK") } != "Exception: OK") return "fail 6" + if (builder(true) { suspendWithException() } != "Exception: OK") return "fail 7" + + return "OK" +} 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 53424e99073..28ec7714bd6 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 @@ -6064,6 +6064,12 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes doTest(fileName); } + @TestMetadata("intercepted.kt") + public void testIntercepted() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/intrinsicSemantics/intercepted.kt"); + doTest(fileName); + } + @TestMetadata("startCoroutine.kt") public void testStartCoroutine() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/intrinsicSemantics/startCoroutine.kt"); @@ -6081,6 +6087,12 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/intrinsicSemantics/startCoroutineUninterceptedOrReturnInterception.kt"); doTest(fileName); } + + @TestMetadata("suspendCoroutineUninterceptedOrReturn.kt") + public void testSuspendCoroutineUninterceptedOrReturn() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/intrinsicSemantics/suspendCoroutineUninterceptedOrReturn.kt"); + doTest(fileName); + } } @TestMetadata("compiler/testData/codegen/box/coroutines/multiModule") diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java index d70eaba2f00..4cfe1785e1e 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java @@ -6064,6 +6064,12 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest { doTest(fileName); } + @TestMetadata("intercepted.kt") + public void testIntercepted() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/intrinsicSemantics/intercepted.kt"); + doTest(fileName); + } + @TestMetadata("startCoroutine.kt") public void testStartCoroutine() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/intrinsicSemantics/startCoroutine.kt"); @@ -6081,6 +6087,12 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/intrinsicSemantics/startCoroutineUninterceptedOrReturnInterception.kt"); doTest(fileName); } + + @TestMetadata("suspendCoroutineUninterceptedOrReturn.kt") + public void testSuspendCoroutineUninterceptedOrReturn() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/intrinsicSemantics/suspendCoroutineUninterceptedOrReturn.kt"); + doTest(fileName); + } } @TestMetadata("compiler/testData/codegen/box/coroutines/multiModule") diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java index 67faf1bfbb5..7ebf7601057 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java @@ -6064,6 +6064,12 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes doTest(fileName); } + @TestMetadata("intercepted.kt") + public void testIntercepted() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/intrinsicSemantics/intercepted.kt"); + doTest(fileName); + } + @TestMetadata("startCoroutine.kt") public void testStartCoroutine() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/intrinsicSemantics/startCoroutine.kt"); @@ -6081,6 +6087,12 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/intrinsicSemantics/startCoroutineUninterceptedOrReturnInterception.kt"); doTest(fileName); } + + @TestMetadata("suspendCoroutineUninterceptedOrReturn.kt") + public void testSuspendCoroutineUninterceptedOrReturn() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/intrinsicSemantics/suspendCoroutineUninterceptedOrReturn.kt"); + doTest(fileName); + } } @TestMetadata("compiler/testData/codegen/box/coroutines/multiModule") 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 de9a96c3d71..2d29aad4e52 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 @@ -6628,6 +6628,12 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest { doTest(fileName); } + @TestMetadata("intercepted.kt") + public void testIntercepted() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/intrinsicSemantics/intercepted.kt"); + doTest(fileName); + } + @TestMetadata("startCoroutine.kt") public void testStartCoroutine() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/intrinsicSemantics/startCoroutine.kt"); @@ -6645,6 +6651,12 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/intrinsicSemantics/startCoroutineUninterceptedOrReturnInterception.kt"); doTest(fileName); } + + @TestMetadata("suspendCoroutineUninterceptedOrReturn.kt") + public void testSuspendCoroutineUninterceptedOrReturn() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/intrinsicSemantics/suspendCoroutineUninterceptedOrReturn.kt"); + doTest(fileName); + } } @TestMetadata("compiler/testData/codegen/box/coroutines/multiModule") diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/intrinsic/functions/FunctionIntrinsics.java b/js/js.translator/src/org/jetbrains/kotlin/js/translate/intrinsic/functions/FunctionIntrinsics.java index b13c59e06a2..6f3f36e6b0a 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/intrinsic/functions/FunctionIntrinsics.java +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/intrinsic/functions/FunctionIntrinsics.java @@ -52,6 +52,8 @@ public final class FunctionIntrinsics { register(ExceptionPropertyIntrinsicFactory.INSTANCE); register(AsDynamicFIF.INSTANCE); register(CoroutineContextFIF.INSTANCE); + register(SuspendCoroutineUninterceptedOrReturnFIF.INSTANCE); + register(InterceptedFIF.INSTANCE); } private void register(@NotNull FunctionIntrinsicFactory instance) { diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/intrinsic/functions/factories/InterceptedFIF.kt b/js/js.translator/src/org/jetbrains/kotlin/js/translate/intrinsic/functions/factories/InterceptedFIF.kt new file mode 100644 index 00000000000..190e73f0a9c --- /dev/null +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/intrinsic/functions/factories/InterceptedFIF.kt @@ -0,0 +1,41 @@ +/* + * Copyright 2010-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.js.translate.intrinsic.functions.factories + +import org.jetbrains.kotlin.backend.common.isBuiltInIntercepted +import org.jetbrains.kotlin.descriptors.FunctionDescriptor +import org.jetbrains.kotlin.js.backend.ast.JsExpression +import org.jetbrains.kotlin.js.translate.callTranslator.CallInfo +import org.jetbrains.kotlin.js.translate.context.TranslationContext +import org.jetbrains.kotlin.js.translate.intrinsic.functions.basic.FunctionIntrinsic +import org.jetbrains.kotlin.js.translate.utils.JsAstUtils +import org.jetbrains.kotlin.js.translate.utils.TranslationUtils + +object InterceptedFIF: FunctionIntrinsicFactory { + override fun getIntrinsic(descriptor: FunctionDescriptor): FunctionIntrinsic? { + if (!descriptor.isBuiltInIntercepted()) return null + return Intrinsic + } + + object Intrinsic: FunctionIntrinsic() { + override fun apply(callInfo: CallInfo, arguments: List, context: TranslationContext): JsExpression { + val continuation = callInfo.extensionReceiver ?: error("intercepted shall be extension function") + val facadeName = context.getNameForDescriptor(TranslationUtils.getCoroutineProperty(context, "facade")) + return JsAstUtils.pureFqn(facadeName, continuation) + } + } +} \ No newline at end of file diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/intrinsic/functions/factories/SuspendCoroutineUninterceptedOrReturnFIF.kt b/js/js.translator/src/org/jetbrains/kotlin/js/translate/intrinsic/functions/factories/SuspendCoroutineUninterceptedOrReturnFIF.kt new file mode 100644 index 00000000000..d36b9a70ed4 --- /dev/null +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/intrinsic/functions/factories/SuspendCoroutineUninterceptedOrReturnFIF.kt @@ -0,0 +1,42 @@ +/* + * Copyright 2010-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.js.translate.intrinsic.functions.factories + +import org.jetbrains.kotlin.backend.common.isBuiltInSuspendCoroutineUninterceptedOrReturn +import org.jetbrains.kotlin.descriptors.FunctionDescriptor +import org.jetbrains.kotlin.js.backend.ast.JsExpression +import org.jetbrains.kotlin.js.backend.ast.JsInvocation +import org.jetbrains.kotlin.js.backend.ast.metadata.isSuspend +import org.jetbrains.kotlin.js.translate.callTranslator.CallInfo +import org.jetbrains.kotlin.js.translate.context.TranslationContext +import org.jetbrains.kotlin.js.translate.intrinsic.functions.basic.FunctionIntrinsic + +object SuspendCoroutineUninterceptedOrReturnFIF: FunctionIntrinsicFactory { + override fun getIntrinsic(descriptor: FunctionDescriptor): FunctionIntrinsic? { + if (!descriptor.isBuiltInSuspendCoroutineUninterceptedOrReturn()) return null + return Intrinsic + } + object Intrinsic: FunctionIntrinsic() { + override fun apply(callInfo: CallInfo, arguments: List, context: TranslationContext): JsExpression { + val lambda = arguments.first() + val continuation = arguments.last() + val invocation = JsInvocation(lambda, continuation) + invocation.isSuspend = true + return invocation + } + } +} \ No newline at end of file diff --git a/libraries/stdlib/src/kotlin/coroutines/experimental/intrinsics/Intrinsics.kt b/libraries/stdlib/src/kotlin/coroutines/experimental/intrinsics/Intrinsics.kt index 41d4c23c8fc..09c8cf571cd 100644 --- a/libraries/stdlib/src/kotlin/coroutines/experimental/intrinsics/Intrinsics.kt +++ b/libraries/stdlib/src/kotlin/coroutines/experimental/intrinsics/Intrinsics.kt @@ -23,8 +23,8 @@ import kotlin.coroutines.experimental.CoroutineContext import kotlin.coroutines.experimental.processBareContinuationResume /** - * Obtains the current continuation instance inside suspend functions and either suspend - * currently running coroutine or return result immediately without suspension. + * Obtains the current continuation instance inside suspend functions and either suspends + * currently running coroutine or returns result immediately without suspension. * * If the [block] returns the special [COROUTINE_SUSPENDED] value, it means that suspend function did suspend the execution and will * not return any result immediately. In this case, the [Continuation] provided to the [block] shall be invoked at some moment in the @@ -42,9 +42,29 @@ import kotlin.coroutines.experimental.processBareContinuationResume @SinceKotlin("1.1") @kotlin.internal.InlineOnly @Suppress("UNUSED_PARAMETER") +// todo: this shall be ordinary function, but, unfortunately, bootstrap compiler does not have suspendCoroutineUninterceptedOrReturn and intercepted intrinsics public inline suspend fun suspendCoroutineOrReturn(crossinline block: (Continuation) -> Any?): T = throw NotImplementedError("Implementation is intrinsic") +/** + * Obtains the current continuation instance inside suspend functions and either suspends + * currently running coroutine or returns result immediately without suspension. + * + * Unlike [suspendCoroutineOrReturn] it does not intercept continuation. + */ +@SinceKotlin("1.2") +@kotlin.internal.InlineOnly +public inline suspend fun suspendCoroutineUninterceptedOrReturn(crossinline block: (Continuation) -> Any?): T = + throw NotImplementedError("Implementation of suspendCoroutineUninterceptedOrReturn is intrinsic") + +/** + * Intercept continuation with [ContinuationInterceptor]. + */ +@SinceKotlin("1.2") +@kotlin.internal.InlineOnly +public inline fun Continuation.intercepted(): Continuation = + throw NotImplementedError("Implementation of intercepted is intrinsic") + /** * Continuation context of current coroutine. *