From 2c3b0aeddb6b42b69968aad960fb547aea836424 Mon Sep 17 00:00:00 2001 From: Denis Zharkov Date: Thu, 24 Nov 2016 16:25:32 +0300 Subject: [PATCH] Add 'suspendWithCurrentContinuation' and 'Suspend' object in built-ins They are part of the new suspension convention, relevant support in backends will be added in later commits #KT-14924 In Progress --- .../default/kotlin-coroutines.txt | 16 +++++++++ .../suspendWithCurrentContinuation.kt | 31 +++++++++++++++++ .../suspendWithCurrentContinuation.txt | 13 +++++++ .../checkers/DiagnosticsTestGenerated.java | 6 ++++ .../builtins/LoadBuiltinsTest.java | 2 +- core/builtins/native/kotlin/Coroutines.kt | 34 +++++++++++++++++++ .../src/kotlin/coroutines/Coroutines.kt | 8 +++++ .../reference-public-api/kotlin-runtime.txt | 4 +++ 8 files changed, 113 insertions(+), 1 deletion(-) create mode 100644 compiler/testData/builtin-classes/default/kotlin-coroutines.txt create mode 100644 compiler/testData/diagnostics/tests/coroutines/suspendWithCurrentContinuation.kt create mode 100644 compiler/testData/diagnostics/tests/coroutines/suspendWithCurrentContinuation.txt create mode 100644 core/builtins/native/kotlin/Coroutines.kt diff --git a/compiler/testData/builtin-classes/default/kotlin-coroutines.txt b/compiler/testData/builtin-classes/default/kotlin-coroutines.txt new file mode 100644 index 00000000000..045b92f3468 --- /dev/null +++ b/compiler/testData/builtin-classes/default/kotlin-coroutines.txt @@ -0,0 +1,16 @@ +package-fragment kotlin.coroutines + +@kotlin.SinceKotlin(version = "1.1") public inline suspend fun suspendWithCurrentContinuation(/*0*/ body: (kotlin.coroutines.Continuation) -> kotlin.Any?): T + +@kotlin.SinceKotlin(version = "1.1") @kotlin.annotation.Target(allowedTargets = {AnnotationTarget.CLASS}) @kotlin.annotation.Retention(value = AnnotationRetention.BINARY) public final annotation class AllowSuspendExtensions : kotlin.Annotation { + /*primary*/ public constructor AllowSuspendExtensions() +} + +@kotlin.SinceKotlin(version = "1.1") public interface Continuation { + public abstract fun resume(/*0*/ data: P): kotlin.Unit + public abstract fun resumeWithException(/*0*/ exception: kotlin.Throwable): kotlin.Unit +} + +@kotlin.SinceKotlin(version = "1.1") public object Suspend { + /*primary*/ private constructor Suspend() +} diff --git a/compiler/testData/diagnostics/tests/coroutines/suspendWithCurrentContinuation.kt b/compiler/testData/diagnostics/tests/coroutines/suspendWithCurrentContinuation.kt new file mode 100644 index 00000000000..0fe8e2e087b --- /dev/null +++ b/compiler/testData/diagnostics/tests/coroutines/suspendWithCurrentContinuation.kt @@ -0,0 +1,31 @@ +// !DIAGNOSTICS: -UNUSED_PARAMETER +// !CHECK_TYPE +class Controller { + suspend fun noParams(): Unit = suspendWithCurrentContinuation { + if (hashCode() % 2 == 0) { + it.resume(Unit) + Suspend + } + else { + Unit + } + } + suspend fun yieldString(value: String) = suspendWithCurrentContinuation { + it.resume(1) + it checkType { _>() } + it.resume("") + + // We can return anything here, 'suspendWithCurrentContinuation' is not very type-safe + // Also we can call resume and then return the value too, but it's still just our problem + "Not-int" + } +} + +fun builder(coroutine c: Controller.() -> Continuation) {} + +fun test() { + builder { + noParams() checkType { _() } + yieldString("abc") checkType { _() } + } +} diff --git a/compiler/testData/diagnostics/tests/coroutines/suspendWithCurrentContinuation.txt b/compiler/testData/diagnostics/tests/coroutines/suspendWithCurrentContinuation.txt new file mode 100644 index 00000000000..cd0a40938e0 --- /dev/null +++ b/compiler/testData/diagnostics/tests/coroutines/suspendWithCurrentContinuation.txt @@ -0,0 +1,13 @@ +package + +public fun builder(/*0*/ coroutine c: Controller.() -> kotlin.coroutines.Continuation): kotlin.Unit +public fun test(): kotlin.Unit + +public final class Controller { + public constructor Controller() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public final suspend fun noParams(): kotlin.Unit + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + public final suspend fun yieldString(/*0*/ value: kotlin.String): kotlin.Int +} diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java index 096e6c28165..871e443e9f8 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java @@ -4300,6 +4300,12 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest { doTest(fileName); } + @TestMetadata("suspendWithCurrentContinuation.kt") + public void testSuspendWithCurrentContinuation() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/coroutines/suspendWithCurrentContinuation.kt"); + doTest(fileName); + } + @TestMetadata("tryCatchLambda.kt") public void testTryCatchLambda() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/coroutines/tryCatchLambda.kt"); diff --git a/compiler/tests/org/jetbrains/kotlin/serialization/builtins/LoadBuiltinsTest.java b/compiler/tests/org/jetbrains/kotlin/serialization/builtins/LoadBuiltinsTest.java index 1230a4d5b1b..2ed43a090a5 100644 --- a/compiler/tests/org/jetbrains/kotlin/serialization/builtins/LoadBuiltinsTest.java +++ b/compiler/tests/org/jetbrains/kotlin/serialization/builtins/LoadBuiltinsTest.java @@ -91,7 +91,7 @@ public class LoadBuiltinsTest extends KotlinTestWithEnvironment { ModuleDescriptor module = LazyResolveTestUtilsKt.createResolveSessionForFiles(getEnvironment().getProject(), files, false).getModuleDescriptor(); - for (FqName packageFqName : CollectionsKt.listOf(BUILT_INS_PACKAGE_FQ_NAME, COLLECTIONS_PACKAGE_FQ_NAME, RANGES_PACKAGE_FQ_NAME)) { + for (FqName packageFqName : CollectionsKt.listOf(BUILT_INS_PACKAGE_FQ_NAME, COLLECTIONS_PACKAGE_FQ_NAME, RANGES_PACKAGE_FQ_NAME, COROUTINES_PACKAGE_FQ_NAME)) { PackageFragmentDescriptor fromLazyResolve = CollectionsKt.single(module.getPackage(packageFqName).getFragments()); if (fromLazyResolve instanceof LazyPackageDescriptor) { diff --git a/core/builtins/native/kotlin/Coroutines.kt b/core/builtins/native/kotlin/Coroutines.kt new file mode 100644 index 00000000000..abf1dbebd5b --- /dev/null +++ b/core/builtins/native/kotlin/Coroutines.kt @@ -0,0 +1,34 @@ +/* + * Copyright 2010-2016 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 kotlin.coroutines + +/** + * This function allows to obtain a continuation instance inside the suspend functions. + * As a suspend function may be only tail-called inside another suspend function, this call will be used as a return value of the latter one. + * + * If the [body] returns the [Suspend] object, that means that suspend-function cannot return a value immediately, + * i.e. it's literally suspends continuation, and the continuation will be resumed at some moment in the future. + * + * Otherwise return value must have a type assignable to 'T'. + * As the latter part cannot be correctly typechecked, it remains on the conscience of the suspend function's author. + * + * Note that it's not recommended to call a [Continuation] method in the same stackframe where suspension function is run, + * they should be called asynchronously later (probably from the different thread). + * + */ +@SinceKotlin("1.1") +public inline suspend fun suspendWithCurrentContinuation(body: (Continuation) -> Any?): T diff --git a/core/builtins/src/kotlin/coroutines/Coroutines.kt b/core/builtins/src/kotlin/coroutines/Coroutines.kt index bcda4195c2e..3f25cf0350f 100644 --- a/core/builtins/src/kotlin/coroutines/Coroutines.kt +++ b/core/builtins/src/kotlin/coroutines/Coroutines.kt @@ -41,3 +41,11 @@ public interface Continuation { @Target(AnnotationTarget.CLASS) @Retention(AnnotationRetention.BINARY) public annotation class AllowSuspendExtensions + +/** + * This object can be used as a return value of [kotlin.coroutines.suspendWithCurrentContinuation] lambda-argument to state that + * the continuation will be resumed at some moment in the future, that means that suspend-function cannot return a value immediately, + * i.e. it's literally suspends continuation, so no stack-unwinding will be performed by the continuation. + */ +@SinceKotlin("1.1") +public object Suspend diff --git a/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-runtime.txt b/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-runtime.txt index f47d8a099b6..f44ba2a2fc6 100644 --- a/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-runtime.txt +++ b/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-runtime.txt @@ -184,6 +184,10 @@ public abstract interface class kotlin/coroutines/Continuation { public abstract fun resumeWithException (Ljava/lang/Throwable;)V } +public final class kotlin/coroutines/Suspend { + public static final field INSTANCE Lkotlin/coroutines/Suspend; +} + public final class kotlin/jvm/JvmClassMappingKt { public static final fun getAnnotationClass (Ljava/lang/annotation/Annotation;)Lkotlin/reflect/KClass; public static final fun getJavaClass (Ljava/lang/Object;)Ljava/lang/Class;