From 2cc09f928e1207a03025f8165d84f64f656334f2 Mon Sep 17 00:00:00 2001 From: Denis Zharkov Date: Tue, 5 Jul 2016 15:32:46 +0300 Subject: [PATCH] Allow suspend extensions for specially annotated controllers --- .../kotlin/resolve/ModifiersChecker.kt | 2 +- .../kotlin/resolve/SuspendModifierChecker.kt | 23 +++++++++++++++++++ .../tests/coroutines/suspendApplicability.kt | 13 ++++++++++- .../tests/coroutines/suspendApplicability.txt | 5 +++- core/builtins/src/kotlin/Coroutines.kt | 8 +++++++ .../testData/keywords/AfterClasses.kt | 1 + .../keywords/GlobalPropertyAccessors.kt | 1 + .../keywords/InTopScopeAfterPackage.kt | 1 + .../testData/keywords/TopScope.kt | 1 + .../checker/infos/suspendApplicability.kt | 15 ++++++++++-- .../reference-public-api/kotlin-runtime.txt | 3 +++ 11 files changed, 68 insertions(+), 5 deletions(-) diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/ModifiersChecker.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/ModifiersChecker.kt index 997ef1c989d..a586cdaa2ae 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/ModifiersChecker.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/ModifiersChecker.kt @@ -83,7 +83,7 @@ object ModifierCheckerCore { NOINLINE_KEYWORD to EnumSet.of(VALUE_PARAMETER), COROUTINE_KEYWORD to EnumSet.of(VALUE_PARAMETER), TAILREC_KEYWORD to EnumSet.of(FUNCTION), - SUSPEND_KEYWORD to EnumSet.of(MEMBER_FUNCTION), + SUSPEND_KEYWORD to EnumSet.of(FUNCTION), EXTERNAL_KEYWORD to EnumSet.of(FUNCTION, PROPERTY_GETTER, PROPERTY_SETTER), ANNOTATION_KEYWORD to EnumSet.of(ANNOTATION_CLASS), CROSSINLINE_KEYWORD to EnumSet.of(VALUE_PARAMETER), diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/SuspendModifierChecker.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/SuspendModifierChecker.kt index e9b83ef22c7..6dd790dd63b 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/SuspendModifierChecker.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/SuspendModifierChecker.kt @@ -20,12 +20,14 @@ import org.jetbrains.kotlin.builtins.getReceiverTypeFromFunctionType import org.jetbrains.kotlin.builtins.getReturnTypeFromFunctionType import org.jetbrains.kotlin.builtins.isExtensionFunctionType import org.jetbrains.kotlin.coroutines.isValidContinuation +import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.descriptors.DeclarationDescriptor import org.jetbrains.kotlin.descriptors.FunctionDescriptor import org.jetbrains.kotlin.diagnostics.DiagnosticSink import org.jetbrains.kotlin.diagnostics.Errors import org.jetbrains.kotlin.incremental.KotlinLookupLocation import org.jetbrains.kotlin.lexer.KtTokens +import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.psi.KtDeclaration import org.jetbrains.kotlin.psi.KtDeclarationWithBody import org.jetbrains.kotlin.types.typeUtil.isUnit @@ -33,6 +35,8 @@ import org.jetbrains.kotlin.util.OperatorNameConventions import org.jetbrains.kotlin.utils.sure object SuspendModifierChecker : SimpleDeclarationChecker { + private val ALLOW_SUSPEND_EXTENSIONS_ANNOTATION_FQ_NAME = FqName("kotlin.coroutines.AllowSuspendExtensions") + override fun check( declaration: KtDeclaration, descriptor: DeclarationDescriptor, @@ -47,6 +51,25 @@ object SuspendModifierChecker : SimpleDeclarationChecker { diagnosticHolder.report(Errors.INAPPLICABLE_MODIFIER.on(suspendModifierElement, KtTokens.SUSPEND_KEYWORD, message)) } + if (functionDescriptor.dispatchReceiverParameter == null) { + if (functionDescriptor.extensionReceiverParameter == null) { + report("function must be either a class member or an extension") + return + } + + val classDescriptor = + functionDescriptor.extensionReceiverParameter!!.type.constructor.declarationDescriptor as? ClassDescriptor + if (classDescriptor == null) { + report("function must be an extension to class") + return + } + + if (!classDescriptor.annotations.hasAnnotation(ALLOW_SUSPEND_EXTENSIONS_ANNOTATION_FQ_NAME)) { + report("controller class must be annotated with AllowSuspendExtensions annotation") + return + } + } + val isValidContinuation = functionDescriptor.valueParameters.lastOrNull()?.type?.isValidContinuation() ?: false if (!isValidContinuation) { report("last parameter of suspend function should have a type of Continuation") diff --git a/compiler/testData/diagnostics/tests/coroutines/suspendApplicability.kt b/compiler/testData/diagnostics/tests/coroutines/suspendApplicability.kt index 28af94ccc0d..310fc6a7d8f 100644 --- a/compiler/testData/diagnostics/tests/coroutines/suspendApplicability.kt +++ b/compiler/testData/diagnostics/tests/coroutines/suspendApplicability.kt @@ -1,8 +1,15 @@ // !DIAGNOSTICS: -UNUSED_PARAMETER -NOTHING_TO_INLINE -suspend fun notMember(x: Continuation) { +suspend fun notMember(x: Continuation) { } +suspend fun String.wrongExtension(x: Continuation) { +} + +suspend fun Controller.correctExtension(x: Continuation) { +} + +@AllowSuspendExtensions class Controller { suspend fun valid(x: Continuation) { @@ -23,4 +30,8 @@ class Controller { suspend fun starProjection(vararg x: Continuation) { } + + suspend fun String.memberExtension(x: Continuation) { + + } } diff --git a/compiler/testData/diagnostics/tests/coroutines/suspendApplicability.txt b/compiler/testData/diagnostics/tests/coroutines/suspendApplicability.txt index ace21376544..428a0dcdbe9 100644 --- a/compiler/testData/diagnostics/tests/coroutines/suspendApplicability.txt +++ b/compiler/testData/diagnostics/tests/coroutines/suspendApplicability.txt @@ -1,8 +1,10 @@ package public suspend fun notMember(/*0*/ x: kotlin.coroutines.Continuation): kotlin.Unit +public suspend fun Controller.correctExtension(/*0*/ x: kotlin.coroutines.Continuation): kotlin.Unit +public suspend fun kotlin.String.wrongExtension(/*0*/ x: kotlin.coroutines.Continuation): kotlin.Unit -public final class Controller { +@kotlin.coroutines.AllowSuspendExtensions() 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 @@ -12,4 +14,5 @@ public final class Controller { public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String public final suspend fun valid(/*0*/ x: kotlin.coroutines.Continuation): kotlin.Unit public final suspend fun wrongParam(/*0*/ x: kotlin.collections.Collection): kotlin.Unit + public final suspend fun kotlin.String.memberExtension(/*0*/ x: kotlin.coroutines.Continuation): kotlin.Unit } diff --git a/core/builtins/src/kotlin/Coroutines.kt b/core/builtins/src/kotlin/Coroutines.kt index 83cdf0ce832..14627ee469d 100644 --- a/core/builtins/src/kotlin/Coroutines.kt +++ b/core/builtins/src/kotlin/Coroutines.kt @@ -31,3 +31,11 @@ interface Continuation { */ fun resumeWithException(exception: Throwable) } + + +/** + * Specifies that suspend extensions with a receiver based on corresponding controller class are allowed to be declared + */ +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.BINARY) +annotation class AllowSuspendExtensions diff --git a/idea/idea-completion/testData/keywords/AfterClasses.kt b/idea/idea-completion/testData/keywords/AfterClasses.kt index 6b906e958da..101ee4cc54d 100644 --- a/idea/idea-completion/testData/keywords/AfterClasses.kt +++ b/idea/idea-completion/testData/keywords/AfterClasses.kt @@ -34,4 +34,5 @@ class B { // EXIST: external // EXIST: annotation class // EXIST: const +// EXIST: suspend // NOTHING_ELSE diff --git a/idea/idea-completion/testData/keywords/GlobalPropertyAccessors.kt b/idea/idea-completion/testData/keywords/GlobalPropertyAccessors.kt index 1c3a43e3ba2..4b142976f63 100644 --- a/idea/idea-completion/testData/keywords/GlobalPropertyAccessors.kt +++ b/idea/idea-completion/testData/keywords/GlobalPropertyAccessors.kt @@ -38,4 +38,5 @@ var a : Int // EXIST: external // EXIST: annotation class // EXIST: const +// EXIST: suspend // NOTHING_ELSE diff --git a/idea/idea-completion/testData/keywords/InTopScopeAfterPackage.kt b/idea/idea-completion/testData/keywords/InTopScopeAfterPackage.kt index a586b395277..089af7852b7 100644 --- a/idea/idea-completion/testData/keywords/InTopScopeAfterPackage.kt +++ b/idea/idea-completion/testData/keywords/InTopScopeAfterPackage.kt @@ -25,4 +25,5 @@ package Test // EXIST: external // EXIST: annotation class // EXIST: const +// EXIST: suspend // NOTHING_ELSE diff --git a/idea/idea-completion/testData/keywords/TopScope.kt b/idea/idea-completion/testData/keywords/TopScope.kt index 0881897ad95..ee53e3ce4f4 100644 --- a/idea/idea-completion/testData/keywords/TopScope.kt +++ b/idea/idea-completion/testData/keywords/TopScope.kt @@ -24,4 +24,5 @@ // EXIST: external // EXIST: annotation class // EXIST: const +// EXIST: suspend // NOTHING_ELSE diff --git a/idea/testData/checker/infos/suspendApplicability.kt b/idea/testData/checker/infos/suspendApplicability.kt index 8c08fdb6fc6..029a8c7d762 100644 --- a/idea/testData/checker/infos/suspendApplicability.kt +++ b/idea/testData/checker/infos/suspendApplicability.kt @@ -1,8 +1,15 @@ // !DIAGNOSTICS: -UNUSED_PARAMETER -NOTHING_TO_INLINE -suspend fun notMember(x: Continuation) { +suspend fun notMember(x: Continuation) { } +suspend fun String.wrongExtension(x: Continuation) { +} + +suspend fun Controller.correctExtension(x: Continuation) { +} + +@AllowSuspendExtensions class Controller { suspend fun valid(x: Continuation) { @@ -20,7 +27,11 @@ class Controller { } - suspend fun starProjection(x: Continuation<*>) { + suspend fun starProjection(vararg x: Continuation) { + + } + + suspend fun String.memberExtension(x: Continuation) { } } 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 e3a0b51e198..93200ed5fea 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 @@ -164,6 +164,9 @@ public abstract class kotlin/collections/ShortIterator : java/util/Iterator, kot public fun remove ()V } +public abstract interface annotation class kotlin/coroutines/AllowSuspendExtensions : java/lang/annotation/Annotation { +} + public abstract interface class kotlin/coroutines/Continuation { public abstract fun resume (Ljava/lang/Object;)V public abstract fun resumeWithException (Ljava/lang/Throwable;)V