From b88c8ea2e78af5d3806d2f723170ebc484605503 Mon Sep 17 00:00:00 2001 From: Stanislav Erokhin Date: Thu, 15 Dec 2016 14:52:57 +0300 Subject: [PATCH] Support checks for annotation `RestrictSuspension`. --- .../jetbrains/kotlin/diagnostics/Errors.java | 1 + .../rendering/DefaultErrorMessages.java | 1 + .../calls/checkers/coroutineCallChecker.kt | 35 +++++ .../restrictSuspension/allMembersAllowed.kt | 99 ++++++++++++++ .../restrictSuspension/allMembersAllowed.txt | 38 ++++++ .../restrictSuspension/extensions.kt | 129 ++++++++++++++++++ .../restrictSuspension/extensions.txt | 41 ++++++ .../restrictSuspension/notRelatedFun.kt | 72 ++++++++++ .../restrictSuspension/notRelatedFun.txt | 35 +++++ .../restrictSuspension/sameInstance.kt | 35 +++++ .../restrictSuspension/sameInstance.txt | 13 ++ .../restrictSuspension/simpleForbidden.kt | 14 ++ .../restrictSuspension/simpleForbidden.txt | 13 ++ .../checkers/DiagnosticsTestGenerated.java | 39 ++++++ .../src/kotlin/coroutines/Coroutines.kt | 2 + .../kotlin/resolve/annotationsForResolve.kt | 4 + 16 files changed, 571 insertions(+) create mode 100644 compiler/testData/diagnostics/tests/coroutines/restrictSuspension/allMembersAllowed.kt create mode 100644 compiler/testData/diagnostics/tests/coroutines/restrictSuspension/allMembersAllowed.txt create mode 100644 compiler/testData/diagnostics/tests/coroutines/restrictSuspension/extensions.kt create mode 100644 compiler/testData/diagnostics/tests/coroutines/restrictSuspension/extensions.txt create mode 100644 compiler/testData/diagnostics/tests/coroutines/restrictSuspension/notRelatedFun.kt create mode 100644 compiler/testData/diagnostics/tests/coroutines/restrictSuspension/notRelatedFun.txt create mode 100644 compiler/testData/diagnostics/tests/coroutines/restrictSuspension/sameInstance.kt create mode 100644 compiler/testData/diagnostics/tests/coroutines/restrictSuspension/sameInstance.txt create mode 100644 compiler/testData/diagnostics/tests/coroutines/restrictSuspension/simpleForbidden.kt create mode 100644 compiler/testData/diagnostics/tests/coroutines/restrictSuspension/simpleForbidden.txt diff --git a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java index 7a41752b644..c58dcf2e3d7 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java @@ -884,6 +884,7 @@ public interface Errors { DiagnosticFactory0 NON_LOCAL_SUSPENSION_POINT = DiagnosticFactory0.create(ERROR); DiagnosticFactory0 ILLEGAL_SUSPEND_FUNCTION_CALL = DiagnosticFactory0.create(ERROR); + DiagnosticFactory0 ILLEGAL_RESTRICTED_SUSPENDING_FUNCTION_CALL = DiagnosticFactory0.create(ERROR); DiagnosticFactory0 SUSPENSION_CALL_MUST_BE_USED_AS_RETURN_VALUE = DiagnosticFactory0.create(ERROR); diff --git a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java index f45ec55f137..4c17fee512c 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java @@ -884,6 +884,7 @@ public class DefaultErrorMessages { MAP.put(NON_LOCAL_RETURN_IN_DISABLED_INLINE, "Non-local returns are not allowed with inlining disabled"); MAP.put(NON_LOCAL_SUSPENSION_POINT, "Suspension functions can be called only within coroutine body"); MAP.put(ILLEGAL_SUSPEND_FUNCTION_CALL, "Suspend functions are only allowed to be called from a coroutine or another suspend function"); + MAP.put(ILLEGAL_RESTRICTED_SUSPENDING_FUNCTION_CALL, "Restricted suspending functions can only invoke member or extension suspending functions on their restricted coroutine scope"); MAP.put(SUSPENSION_CALL_MUST_BE_USED_AS_RETURN_VALUE, "Only tail calls are allowed to another suspension function, and these calls must be used as return values"); MAP.setImmutable(); diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/checkers/coroutineCallChecker.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/checkers/coroutineCallChecker.kt index 24710b10569..1fd99f8ce7f 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/checkers/coroutineCallChecker.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/checkers/coroutineCallChecker.kt @@ -23,16 +23,20 @@ import org.jetbrains.kotlin.coroutines.hasSuspendFunctionType import org.jetbrains.kotlin.coroutines.isSuspendLambda import org.jetbrains.kotlin.descriptors.CallableDescriptor import org.jetbrains.kotlin.descriptors.FunctionDescriptor +import org.jetbrains.kotlin.descriptors.ReceiverParameterDescriptor import org.jetbrains.kotlin.descriptors.SimpleFunctionDescriptor import org.jetbrains.kotlin.diagnostics.DiagnosticSink import org.jetbrains.kotlin.diagnostics.Errors import org.jetbrains.kotlin.psi.KtExpression import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall +import org.jetbrains.kotlin.resolve.descriptorUtil.hasRestrictSuspensionAnnotation import org.jetbrains.kotlin.resolve.inline.InlineUtil import org.jetbrains.kotlin.resolve.scopes.LexicalScope import org.jetbrains.kotlin.resolve.scopes.LexicalScopeKind +import org.jetbrains.kotlin.resolve.scopes.receivers.ReceiverValue import org.jetbrains.kotlin.resolve.scopes.utils.parentsWithSelf +import org.jetbrains.kotlin.types.typeUtil.supertypes import org.jetbrains.kotlin.utils.addToStdlib.cast import org.jetbrains.kotlin.utils.addToStdlib.safeAs @@ -60,6 +64,8 @@ object CoroutineSuspendCallChecker : CallChecker { // Tail calls checks happen during control flow analysis // Here we only record enclosing function mapping (for backends purposes) context.trace.record(BindingContext.ENCLOSING_SUSPEND_FUNCTION_FOR_SUSPEND_FUNCTION_CALL, resolvedCall.call, enclosingSuspendFunction) + + checkRestrictSuspension(enclosingSuspendFunction.extensionReceiverParameter, resolvedCall, reportOn, context) } closestSuspensionLambdaDescriptor != null -> { val callElement = resolvedCall.call.callElement as KtExpression @@ -71,6 +77,8 @@ object CoroutineSuspendCallChecker : CallChecker { context.trace.record( BindingContext.ENCLOSING_SUSPEND_LAMBDA_FOR_SUSPENSION_POINT, resolvedCall.call, closestSuspensionLambdaDescriptor ) + + checkRestrictSuspension(closestSuspensionLambdaDescriptor.extensionReceiverParameter, resolvedCall, reportOn, context) } else -> { context.trace.report(Errors.ILLEGAL_SUSPEND_FUNCTION_CALL.on(reportOn)) @@ -98,4 +106,31 @@ private fun checkCoroutinesFeature(languageVersionSettings: LanguageVersionSetti else if (languageVersionSettings.supportsFeature(LanguageFeature.WarnOnCoroutines)) { diagnosticHolder.report(Errors.EXPERIMENTAL_FEATURE_WARNING.on(reportOn, LanguageFeature.Coroutines)) } +} + +private fun checkRestrictSuspension( + enclosingSuspendReceiver: ReceiverParameterDescriptor?, + resolvedCall: ResolvedCall<*>, + reportOn: PsiElement, + context: CallCheckerContext +) { + if (enclosingSuspendReceiver == null) return + val enclosingSuspendReceiverValue = enclosingSuspendReceiver.value + + fun ReceiverValue.isRestrictSuspensionReceiver() = (type.supertypes() + type).any { + it.constructor.declarationDescriptor?.hasRestrictSuspensionAnnotation() == true + } + + // todo explicit this and implicit this is not equals now + infix fun ReceiverValue.sameInstance(other: ReceiverValue?) = this === other + + if (!enclosingSuspendReceiverValue.isRestrictSuspensionReceiver()) return + + // member of suspend receiver + if (enclosingSuspendReceiverValue sameInstance resolvedCall.dispatchReceiver) return + + if (enclosingSuspendReceiverValue sameInstance resolvedCall.extensionReceiver && + resolvedCall.candidateDescriptor.extensionReceiverParameter!!.value.isRestrictSuspensionReceiver()) return + + context.trace.report(Errors.ILLEGAL_RESTRICTED_SUSPENDING_FUNCTION_CALL.on(reportOn)) } \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/coroutines/restrictSuspension/allMembersAllowed.kt b/compiler/testData/diagnostics/tests/coroutines/restrictSuspension/allMembersAllowed.kt new file mode 100644 index 00000000000..73427ad008d --- /dev/null +++ b/compiler/testData/diagnostics/tests/coroutines/restrictSuspension/allMembersAllowed.kt @@ -0,0 +1,99 @@ +// !DIAGNOSTICS: -UNUSED_PARAMETER -SUSPENSION_CALL_MUST_BE_USED_AS_RETURN_VALUE + +interface SuperInterface { + suspend fun superFun() {} + suspend fun String.superExtFun() {} +} + +@kotlin.coroutines.RestrictSuspension +open class RestrictedController : SuperInterface { + suspend fun memberFun() {} + suspend fun String.memberExtFun() {} +} + +class SubClass : RestrictedController() { + suspend fun subFun() {} + suspend fun String.subExtFun() {} +} + +fun generate1(f: suspend SuperInterface.() -> Unit) {} +fun generate2(f: suspend RestrictedController.() -> Unit) {} +fun generate3(f: suspend SubClass.() -> Unit) {} + +fun String.test() { + generate1 { + superFun() + superExtFun() + with("") { + superFun() + superExtFun() + } + } + generate2 { + superFun() + superExtFun() + memberFun() + memberExtFun() + with("") { + superFun() + superExtFun() + memberFun() + memberExtFun() + } + } + generate3 { + superFun() + superExtFun() + memberFun() + memberExtFun() + subFun() + superExtFun() + with("") { + superFun() + superExtFun() + memberFun() + memberExtFun() + subFun() + superExtFun() + } + } + + suspend fun SuperInterface.fun1() { + superFun() + superExtFun() + with("") { + superFun() + superExtFun() + } + } + suspend fun RestrictedController.fun2() { + superFun() + superExtFun() + memberFun() + memberExtFun() + with("") { + superFun() + superExtFun() + memberFun() + memberExtFun() + } + } + suspend fun SubClass.fun3() { + superFun() + superExtFun() + memberFun() + memberExtFun() + subFun() + superExtFun() + with("") { + superFun() + superExtFun() + memberFun() + memberExtFun() + subFun() + superExtFun() + } + } + +} + diff --git a/compiler/testData/diagnostics/tests/coroutines/restrictSuspension/allMembersAllowed.txt b/compiler/testData/diagnostics/tests/coroutines/restrictSuspension/allMembersAllowed.txt new file mode 100644 index 00000000000..559316cb938 --- /dev/null +++ b/compiler/testData/diagnostics/tests/coroutines/restrictSuspension/allMembersAllowed.txt @@ -0,0 +1,38 @@ +package + +public fun generate1(/*0*/ f: suspend SuperInterface.() -> kotlin.Unit): kotlin.Unit +public fun generate2(/*0*/ f: suspend RestrictedController.() -> kotlin.Unit): kotlin.Unit +public fun generate3(/*0*/ f: suspend SubClass.() -> kotlin.Unit): kotlin.Unit +public fun kotlin.String.test(): kotlin.Unit + +@kotlin.coroutines.RestrictSuspension public open class RestrictedController : SuperInterface { + public constructor RestrictedController() + 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 memberFun(): kotlin.Unit + public open suspend override /*1*/ /*fake_override*/ fun superFun(): kotlin.Unit + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + public final suspend fun kotlin.String.memberExtFun(): kotlin.Unit + public open suspend override /*1*/ /*fake_override*/ fun kotlin.String.superExtFun(): kotlin.Unit +} + +public final class SubClass : RestrictedController { + public constructor SubClass() + 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 override /*1*/ /*fake_override*/ fun memberFun(): kotlin.Unit + public final suspend fun subFun(): kotlin.Unit + public open suspend override /*1*/ /*fake_override*/ fun superFun(): kotlin.Unit + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + public final suspend override /*1*/ /*fake_override*/ fun kotlin.String.memberExtFun(): kotlin.Unit + public final suspend fun kotlin.String.subExtFun(): kotlin.Unit + public open suspend override /*1*/ /*fake_override*/ fun kotlin.String.superExtFun(): kotlin.Unit +} + +public interface SuperInterface { + 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 open suspend fun superFun(): kotlin.Unit + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + public open suspend fun kotlin.String.superExtFun(): kotlin.Unit +} diff --git a/compiler/testData/diagnostics/tests/coroutines/restrictSuspension/extensions.kt b/compiler/testData/diagnostics/tests/coroutines/restrictSuspension/extensions.kt new file mode 100644 index 00000000000..487892a8a1d --- /dev/null +++ b/compiler/testData/diagnostics/tests/coroutines/restrictSuspension/extensions.kt @@ -0,0 +1,129 @@ +// !DIAGNOSTICS: -UNUSED_PARAMETER -SUSPENSION_CALL_MUST_BE_USED_AS_RETURN_VALUE + +interface SuperInterface + +@kotlin.coroutines.RestrictSuspension +open class RestrictedController : SuperInterface + +class SubClass : RestrictedController() + +suspend fun Any?.extAny() {} +suspend fun SuperInterface.extSuper() {} +suspend fun RestrictedController.ext() {} +suspend fun SubClass.extSub() {} + +class A { + suspend fun Any?.memExtAny() {} + suspend fun SuperInterface.memExtSuper() {} + suspend fun RestrictedController.memExt() {} + suspend fun SubClass.memExtSub() {} +} + + +fun generate1(f: suspend SuperInterface.() -> Unit) {} +fun generate2(f: suspend RestrictedController.() -> Unit) {} +fun generate3(f: suspend SubClass.() -> Unit) {} + +fun A.test() { + generate1 { + extAny() + memExtAny() + extSuper() + memExtSuper() + with(A()) { + extAny() + memExtAny() + extSuper() + memExtSuper() + } + } + generate2 { + extAny() + memExtAny() + extSuper() + memExtSuper() + + ext() + memExt() + with(A()) { + extAny() + memExtAny() + extSuper() + memExtSuper() + ext() + memExt() + } + } + generate3 { + extAny() + memExtAny() + extSuper() + memExtSuper() + + ext() + memExt() + extSub() + memExtSub() + with(A()) { + extAny() + memExtAny() + extSuper() + memExtSuper() + ext() + memExt() + extSub() + memExtSub() + } + } + + suspend fun SuperInterface.fun1() { + extAny() + memExtAny() + extSuper() + memExtSuper() + with(A()) { + extAny() + memExtAny() + extSuper() + memExtSuper() + } + } + suspend fun RestrictedController.fun2() { + extAny() + memExtAny() + extSuper() + memExtSuper() + + ext() + memExt() + with(A()) { + extAny() + memExtAny() + extSuper() + memExtSuper() + ext() + memExt() + } + } + suspend fun SubClass.fun3() { + extAny() + memExtAny() + extSuper() + memExtSuper() + + ext() + memExt() + extSub() + memExtSub() + with(A()) { + extAny() + memExtAny() + extSuper() + memExtSuper() + ext() + memExt() + extSub() + memExtSub() + } + } +} diff --git a/compiler/testData/diagnostics/tests/coroutines/restrictSuspension/extensions.txt b/compiler/testData/diagnostics/tests/coroutines/restrictSuspension/extensions.txt new file mode 100644 index 00000000000..187df8ab4d0 --- /dev/null +++ b/compiler/testData/diagnostics/tests/coroutines/restrictSuspension/extensions.txt @@ -0,0 +1,41 @@ +package + +public fun generate1(/*0*/ f: suspend SuperInterface.() -> kotlin.Unit): kotlin.Unit +public fun generate2(/*0*/ f: suspend RestrictedController.() -> kotlin.Unit): kotlin.Unit +public fun generate3(/*0*/ f: suspend SubClass.() -> kotlin.Unit): kotlin.Unit +public suspend fun RestrictedController.ext(): kotlin.Unit +public suspend fun kotlin.Any?.extAny(): kotlin.Unit +public suspend fun SubClass.extSub(): kotlin.Unit +public suspend fun SuperInterface.extSuper(): kotlin.Unit +public fun A.test(): kotlin.Unit + +public final class A { + public constructor A() + 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 open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + public final suspend fun RestrictedController.memExt(): kotlin.Unit + public final suspend fun kotlin.Any?.memExtAny(): kotlin.Unit + public final suspend fun SubClass.memExtSub(): kotlin.Unit + public final suspend fun SuperInterface.memExtSuper(): kotlin.Unit +} + +@kotlin.coroutines.RestrictSuspension public open class RestrictedController : SuperInterface { + public constructor RestrictedController() + 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 open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public final class SubClass : RestrictedController { + public constructor SubClass() + 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 open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public interface SuperInterface { + 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 open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} diff --git a/compiler/testData/diagnostics/tests/coroutines/restrictSuspension/notRelatedFun.kt b/compiler/testData/diagnostics/tests/coroutines/restrictSuspension/notRelatedFun.kt new file mode 100644 index 00000000000..873187e2d50 --- /dev/null +++ b/compiler/testData/diagnostics/tests/coroutines/restrictSuspension/notRelatedFun.kt @@ -0,0 +1,72 @@ +// !DIAGNOSTICS: -UNUSED_PARAMETER -SUSPENSION_CALL_MUST_BE_USED_AS_RETURN_VALUE + +interface SuperInterface + +@kotlin.coroutines.RestrictSuspension +open class RestrictedController : SuperInterface + +class SubClass : RestrictedController() + +suspend fun topLevel() {} + +class A { + suspend fun member() {} +} + +fun generate1(f: suspend SuperInterface.() -> Unit) {} +fun generate2(f: suspend RestrictedController.() -> Unit) {} +fun generate3(f: suspend SubClass.() -> Unit) {} + +fun A.test() { + generate1 { + topLevel() + member() + with(A()) { + topLevel() + member() + } + } + generate2 { + topLevel() + member() + with(A()) { + topLevel() + member() + } + } + generate3 { + topLevel() + member() + with(A()) { + topLevel() + member() + } + } + + suspend fun SuperInterface.fun1() { + topLevel() + member() + with(A()) { + topLevel() + member() + } + } + suspend fun RestrictedController.fun2() { + topLevel() + member() + with(A()) { + topLevel() + member() + } + } + suspend fun SubClass.fun3() { + topLevel() + member() + with(A()) { + topLevel() + member() + } + } + +} + diff --git a/compiler/testData/diagnostics/tests/coroutines/restrictSuspension/notRelatedFun.txt b/compiler/testData/diagnostics/tests/coroutines/restrictSuspension/notRelatedFun.txt new file mode 100644 index 00000000000..4f6398a4832 --- /dev/null +++ b/compiler/testData/diagnostics/tests/coroutines/restrictSuspension/notRelatedFun.txt @@ -0,0 +1,35 @@ +package + +public fun generate1(/*0*/ f: suspend SuperInterface.() -> kotlin.Unit): kotlin.Unit +public fun generate2(/*0*/ f: suspend RestrictedController.() -> kotlin.Unit): kotlin.Unit +public fun generate3(/*0*/ f: suspend SubClass.() -> kotlin.Unit): kotlin.Unit +public suspend fun topLevel(): kotlin.Unit +public fun A.test(): kotlin.Unit + +public final class A { + public constructor A() + 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 member(): kotlin.Unit + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +@kotlin.coroutines.RestrictSuspension public open class RestrictedController : SuperInterface { + public constructor RestrictedController() + 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 open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public final class SubClass : RestrictedController { + public constructor SubClass() + 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 open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public interface SuperInterface { + 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 open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} diff --git a/compiler/testData/diagnostics/tests/coroutines/restrictSuspension/sameInstance.kt b/compiler/testData/diagnostics/tests/coroutines/restrictSuspension/sameInstance.kt new file mode 100644 index 00000000000..d5d4703e51a --- /dev/null +++ b/compiler/testData/diagnostics/tests/coroutines/restrictSuspension/sameInstance.kt @@ -0,0 +1,35 @@ +// !DIAGNOSTICS: -UNUSED_PARAMETER -SUSPENSION_CALL_MUST_BE_USED_AS_RETURN_VALUE + +@kotlin.coroutines.RestrictSuspension +class RestrictedController { + suspend fun member() {} +} + +suspend fun RestrictedController.extension() {} + +fun generate(f: suspend RestrictedController.() -> Unit) {} + +fun test() { + generate() l@ { + member() + extension() + + // todo + this.member() + this.extension() + + val foo = this + foo.member() + foo.extension() + + // todo + this@l.member() + this@l.extension() + + with(1) { + // todo + this@l.member() + this@l.extension() + } + } +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/coroutines/restrictSuspension/sameInstance.txt b/compiler/testData/diagnostics/tests/coroutines/restrictSuspension/sameInstance.txt new file mode 100644 index 00000000000..d9a1ef457a9 --- /dev/null +++ b/compiler/testData/diagnostics/tests/coroutines/restrictSuspension/sameInstance.txt @@ -0,0 +1,13 @@ +package + +public fun generate(/*0*/ f: suspend RestrictedController.() -> kotlin.Unit): kotlin.Unit +public fun test(): kotlin.Unit +public suspend fun RestrictedController.extension(): kotlin.Unit + +@kotlin.coroutines.RestrictSuspension public final class RestrictedController { + public constructor RestrictedController() + 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 member(): kotlin.Unit + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} diff --git a/compiler/testData/diagnostics/tests/coroutines/restrictSuspension/simpleForbidden.kt b/compiler/testData/diagnostics/tests/coroutines/restrictSuspension/simpleForbidden.kt new file mode 100644 index 00000000000..05a2f4f199d --- /dev/null +++ b/compiler/testData/diagnostics/tests/coroutines/restrictSuspension/simpleForbidden.kt @@ -0,0 +1,14 @@ +@kotlin.coroutines.RestrictSuspension +class RestrictedController + +suspend fun Any?.extFun() {} +suspend fun suspendFun() {} + +fun generate(c: suspend RestrictedController.() -> Unit) {} + +fun test() { + generate { + extFun() + suspendFun() + } +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/coroutines/restrictSuspension/simpleForbidden.txt b/compiler/testData/diagnostics/tests/coroutines/restrictSuspension/simpleForbidden.txt new file mode 100644 index 00000000000..27dc5442ca9 --- /dev/null +++ b/compiler/testData/diagnostics/tests/coroutines/restrictSuspension/simpleForbidden.txt @@ -0,0 +1,13 @@ +package + +public fun generate(/*0*/ c: suspend RestrictedController.() -> kotlin.Unit): kotlin.Unit +public suspend fun suspendFun(): kotlin.Unit +public fun test(): kotlin.Unit +public suspend fun kotlin.Any?.extFun(): kotlin.Unit + +@kotlin.coroutines.RestrictSuspension public final class RestrictedController { + public constructor RestrictedController() + 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 open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java index f659acec75f..8a17fca3a8c 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java @@ -4417,6 +4417,45 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest { } } + @TestMetadata("compiler/testData/diagnostics/tests/coroutines/restrictSuspension") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class RestrictSuspension extends AbstractDiagnosticsTest { + public void testAllFilesPresentInRestrictSuspension() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/diagnostics/tests/coroutines/restrictSuspension"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true); + } + + @TestMetadata("allMembersAllowed.kt") + public void testAllMembersAllowed() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/coroutines/restrictSuspension/allMembersAllowed.kt"); + doTest(fileName); + } + + @TestMetadata("extensions.kt") + public void testExtensions() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/coroutines/restrictSuspension/extensions.kt"); + doTest(fileName); + } + + @TestMetadata("notRelatedFun.kt") + public void testNotRelatedFun() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/coroutines/restrictSuspension/notRelatedFun.kt"); + doTest(fileName); + } + + @TestMetadata("sameInstance.kt") + public void testSameInstance() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/coroutines/restrictSuspension/sameInstance.kt"); + doTest(fileName); + } + + @TestMetadata("simpleForbidden.kt") + public void testSimpleForbidden() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/coroutines/restrictSuspension/simpleForbidden.kt"); + doTest(fileName); + } + } + @TestMetadata("compiler/testData/diagnostics/tests/coroutines/suspendFunctionType") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) diff --git a/core/builtins/src/kotlin/coroutines/Coroutines.kt b/core/builtins/src/kotlin/coroutines/Coroutines.kt index 9c13bf81c63..7ec2ed1a745 100644 --- a/core/builtins/src/kotlin/coroutines/Coroutines.kt +++ b/core/builtins/src/kotlin/coroutines/Coroutines.kt @@ -55,4 +55,6 @@ public val SUSPENDED: Any? = Any() * receiver only and are restricted from calling arbitrary suspension functions. */ @SinceKotlin("1.1") +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.BINARY) public annotation class RestrictSuspension diff --git a/core/descriptors/src/org/jetbrains/kotlin/resolve/annotationsForResolve.kt b/core/descriptors/src/org/jetbrains/kotlin/resolve/annotationsForResolve.kt index edbe29c6b87..2470a4410fd 100644 --- a/core/descriptors/src/org/jetbrains/kotlin/resolve/annotationsForResolve.kt +++ b/core/descriptors/src/org/jetbrains/kotlin/resolve/annotationsForResolve.kt @@ -17,6 +17,8 @@ package org.jetbrains.kotlin.resolve.descriptorUtil import org.jetbrains.kotlin.descriptors.CallableDescriptor +import org.jetbrains.kotlin.descriptors.ClassDescriptor +import org.jetbrains.kotlin.descriptors.ClassifierDescriptor import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor import org.jetbrains.kotlin.descriptors.annotations.AnnotationWithTarget @@ -31,6 +33,7 @@ val LOW_PRIORITY_IN_OVERLOAD_RESOLUTION_FQ_NAME = FqName("kotlin.internal.LowPri private val HIDES_MEMBERS_ANNOTATION_FQ_NAME = FqName("kotlin.internal.HidesMembers") private val ONLY_INPUT_TYPES_FQ_NAME = FqName("kotlin.internal.OnlyInputTypes") private val DYNAMIC_EXTENSION_FQ_NAME = FqName("kotlin.internal.DynamicExtension") +private val RESTRICT_SUSPENSION_FQ_NAME = FqName("kotlin.coroutines.RestrictSuspension") // @HidesMembers annotation only has effect for members with these names val HIDES_MEMBERS_NAME_LIST = setOf(Name.identifier("forEach")) @@ -48,6 +51,7 @@ fun CallableDescriptor.hasLowPriorityInOverloadResolution(): Boolean = annotatio fun CallableDescriptor.hasHidesMembersAnnotation(): Boolean = annotations.hasAnnotation(HIDES_MEMBERS_ANNOTATION_FQ_NAME) fun CallableDescriptor.hasDynamicExtensionAnnotation(): Boolean = annotations.hasAnnotation(DYNAMIC_EXTENSION_FQ_NAME) +fun ClassifierDescriptor.hasRestrictSuspensionAnnotation(): Boolean = annotations.hasAnnotation(RESTRICT_SUSPENSION_FQ_NAME) fun TypeParameterDescriptor.hasOnlyInputTypesAnnotation(): Boolean = annotations.hasAnnotation(ONLY_INPUT_TYPES_FQ_NAME)