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 3ded36a2624..8e1f35646b3 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 @@ -8,13 +8,10 @@ package org.jetbrains.kotlin.resolve.calls.checkers import com.intellij.psi.PsiElement import org.jetbrains.kotlin.config.LanguageFeature import org.jetbrains.kotlin.config.LanguageVersionSettings -import org.jetbrains.kotlin.config.restrictsSuspensionFqName import org.jetbrains.kotlin.config.isBuiltInCoroutineContext +import org.jetbrains.kotlin.config.restrictsSuspensionFqName import org.jetbrains.kotlin.coroutines.hasSuspendFunctionType -import org.jetbrains.kotlin.descriptors.CallableDescriptor -import org.jetbrains.kotlin.descriptors.FunctionDescriptor -import org.jetbrains.kotlin.descriptors.PropertyDescriptor -import org.jetbrains.kotlin.descriptors.PropertyGetterDescriptor +import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.diagnostics.DiagnosticSink import org.jetbrains.kotlin.diagnostics.Errors import org.jetbrains.kotlin.name.Name @@ -23,7 +20,6 @@ import org.jetbrains.kotlin.psi.KtThisExpression import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.DescriptorUtils import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall -import org.jetbrains.kotlin.resolve.calls.tower.NewResolvedCallImpl import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe import org.jetbrains.kotlin.resolve.inline.InlineUtil import org.jetbrains.kotlin.resolve.scopes.HierarchicalScope @@ -131,36 +127,59 @@ fun checkCoroutinesFeature(languageVersionSettings: LanguageVersionSettings, dia } private fun checkRestrictsSuspension( - enclosingCallableDescriptor: CallableDescriptor, + enclosingSuspendCallableDescriptor: CallableDescriptor, resolvedCall: ResolvedCall<*>, reportOn: PsiElement, context: CallCheckerContext ) { - val enclosingSuspendReceiverValue = enclosingCallableDescriptor.extensionReceiverParameter?.value ?: return - - fun ReceiverValue.isRestrictsSuspensionReceiver() = (type.supertypes() + type).any { + fun ReceiverValue.isRestrictsSuspensionReceiver() = (listOf(type) + type.supertypes()).any { it.constructor.declarationDescriptor?.annotations?.hasAnnotation(context.languageVersionSettings.restrictsSuspensionFqName()) == true } infix fun ReceiverValue.sameInstance(other: ReceiverValue?): Boolean { if (other == null) return false - if (this === other) return true + // Implicit receiver should be reference equal + if (this.original === other.original) return true val referenceExpression = ((other as? ExpressionReceiver)?.expression as? KtThisExpression)?.instanceReference val referenceTarget = referenceExpression?.let { context.trace.get(BindingContext.REFERENCE_TARGET, referenceExpression) } - return this === (referenceTarget as? CallableDescriptor)?.extensionReceiverParameter?.value + val referenceReceiverValue = when (referenceTarget) { + is CallableDescriptor -> referenceTarget.extensionReceiverParameter?.value + is ClassDescriptor -> referenceTarget.thisAsReceiverParameter.value + else -> null + } + + return this === referenceReceiverValue } - if (!enclosingSuspendReceiverValue.isRestrictsSuspensionReceiver()) return + fun reportError() { + context.trace.report(Errors.ILLEGAL_RESTRICTED_SUSPENDING_FUNCTION_CALL.on(reportOn)) + } + + val enclosingSuspendExtensionReceiverValue = enclosingSuspendCallableDescriptor.extensionReceiverParameter?.value + val enclosingSuspendDispatchReceiverValue = enclosingSuspendCallableDescriptor.dispatchReceiverParameter?.value + + val receivers = listOfNotNull(resolvedCall.dispatchReceiver, resolvedCall.extensionReceiver) + for (receiverValue in receivers) { + if (!receiverValue.isRestrictsSuspensionReceiver()) continue + if (enclosingSuspendExtensionReceiverValue?.sameInstance(receiverValue) == true) continue + if (enclosingSuspendDispatchReceiverValue?.sameInstance(receiverValue) == true) continue + + reportError() + return + } + + if (enclosingSuspendExtensionReceiverValue?.isRestrictsSuspensionReceiver() != true) return // member of suspend receiver - if (enclosingSuspendReceiverValue sameInstance resolvedCall.dispatchReceiver?.original) return + if (enclosingSuspendExtensionReceiverValue sameInstance resolvedCall.dispatchReceiver) return - if (enclosingSuspendReceiverValue sameInstance resolvedCall.extensionReceiver?.original && - resolvedCall.candidateDescriptor.extensionReceiverParameter!!.value.isRestrictsSuspensionReceiver()) return + if (enclosingSuspendExtensionReceiverValue sameInstance resolvedCall.extensionReceiver && + resolvedCall.candidateDescriptor.extensionReceiverParameter!!.value.isRestrictsSuspensionReceiver() + ) return - context.trace.report(Errors.ILLEGAL_RESTRICTED_SUSPENDING_FUNCTION_CALL.on(reportOn)) + reportError() } diff --git a/compiler/testData/diagnostics/testsWithStdLib/coroutines/restrictSuspension/memberExtension.kt b/compiler/testData/diagnostics/testsWithStdLib/coroutines/restrictSuspension/memberExtension.kt new file mode 100644 index 00000000000..629f8bdf393 --- /dev/null +++ b/compiler/testData/diagnostics/testsWithStdLib/coroutines/restrictSuspension/memberExtension.kt @@ -0,0 +1,62 @@ +// COMMON_COROUTINES_TEST +// SKIP_TXT +@COROUTINES_PACKAGE.RestrictsSuspension +class RestrictedController { + suspend fun member() { + ext() + member() + memberExt() + } + + suspend fun RestrictedController.memberExt() { + ext() + member() + memberExt() + } +} + +suspend fun RestrictedController.ext() { + ext() + member() + memberExt() +} + +fun generate(c: suspend RestrictedController.() -> Unit) {} + +fun runBlocking(x: suspend () -> Unit) {} + +fun test() { + generate a@{ + ext() + member() + memberExt() + + this@a.ext() + this@a.member() + this@a.memberExt() + + generate b@{ + ext() + member() + memberExt() + + this@a.ext() + this@a.member() + this@a.memberExt() + + this@b.ext() + this@b.member() + this@b.memberExt() + } + + runBlocking { + ext() + member() + memberExt() + + this@a.ext() + this@a.member() + this@a.memberExt() + } + } +} diff --git a/compiler/testData/diagnostics/testsWithStdLib/coroutines/restrictSuspension/outerYield.kt b/compiler/testData/diagnostics/testsWithStdLib/coroutines/restrictSuspension/outerYield.kt new file mode 100644 index 00000000000..176544dfabc --- /dev/null +++ b/compiler/testData/diagnostics/testsWithStdLib/coroutines/restrictSuspension/outerYield.kt @@ -0,0 +1,91 @@ +// COMMON_COROUTINES_TEST +// SKIP_TXT +@COROUTINES_PACKAGE.RestrictsSuspension +class RestrictedController { + suspend fun yield(x: T) {} + + suspend fun anotherYield(x: T) { + yield(x) + this.yield(x) + + yield2(x) + this.yield2(x) + + with(this) { + yield(x) + this@with.yield(x) + + yield2(x) + this@with.yield2(x) + } + } +} + +fun buildSequence(c: suspend RestrictedController.() -> Unit) {} +suspend fun RestrictedController.yield2(x: T) {} + +fun test() { + buildSequence a@{ + buildSequence b@{ + yield(1) + yield2(1) + this@b.yield(1) + this@b.yield2(1) + + this@a.yield(2) // Should be error + this@a.yield2(2) // Should be error + + with(this) { + yield(3) + this@with.yield(3) + + yield2(3) + this@with.yield2(3) + } + } + } + + buildSequence { + buildSequence { + yield("a") + yield2("a") + this.yield("b") + this.yield2("b") + + yield(1) // Should be error + yield2(1) // Should be error + + with(this) { + yield("") + this@with.yield("") + + yield2("") + this@with.yield2("") + } + } + } + + buildSequence a@{ + yield(1) + yield2(1) + buildSequence { + yield("") + yield2("") + this@a.yield(1) + this@a.yield2(1) + + with(this) { + yield("") + this@with.yield("") + + yield2("") + this@with.yield2("") + } + } + } + + buildSequence { + yield("") + RestrictedController().yield("1") + } +} diff --git a/compiler/testData/diagnostics/testsWithStdLib/coroutines/restrictSuspension/wrongEnclosingFunction.kt b/compiler/testData/diagnostics/testsWithStdLib/coroutines/restrictSuspension/wrongEnclosingFunction.kt new file mode 100644 index 00000000000..fa8c641da80 --- /dev/null +++ b/compiler/testData/diagnostics/testsWithStdLib/coroutines/restrictSuspension/wrongEnclosingFunction.kt @@ -0,0 +1,18 @@ +// COMMON_COROUTINES_TEST +// SKIP_TXT +@COROUTINES_PACKAGE.RestrictsSuspension +class RestrictedController { + suspend fun yield() {} +} + +fun generate(c: suspend RestrictedController.() -> Unit) {} + +fun runBlocking(x: suspend () -> Unit) {} + +fun test() { + generate { + runBlocking { + yield() + } + } +} diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestWithStdLibGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestWithStdLibGenerated.java index 5c186525888..4ee0b14c282 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestWithStdLibGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestWithStdLibGenerated.java @@ -1835,6 +1835,18 @@ public class DiagnosticsTestWithStdLibGenerated extends AbstractDiagnosticsTestW doTestWithCoroutinesPackageReplacement(fileName, "kotlin.coroutines"); } + @TestMetadata("memberExtension.kt") + public void testMemberExtension_1_2() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithStdLib/coroutines/restrictSuspension/memberExtension.kt"); + doTestWithCoroutinesPackageReplacement(fileName, "kotlin.coroutines.experimental"); + } + + @TestMetadata("memberExtension.kt") + public void testMemberExtension_1_3() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithStdLib/coroutines/restrictSuspension/memberExtension.kt"); + doTestWithCoroutinesPackageReplacement(fileName, "kotlin.coroutines"); + } + @TestMetadata("notRelatedFun.kt") public void testNotRelatedFun_1_2() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithStdLib/coroutines/restrictSuspension/notRelatedFun.kt"); @@ -1847,6 +1859,18 @@ public class DiagnosticsTestWithStdLibGenerated extends AbstractDiagnosticsTestW doTestWithCoroutinesPackageReplacement(fileName, "kotlin.coroutines"); } + @TestMetadata("outerYield.kt") + public void testOuterYield_1_2() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithStdLib/coroutines/restrictSuspension/outerYield.kt"); + doTestWithCoroutinesPackageReplacement(fileName, "kotlin.coroutines.experimental"); + } + + @TestMetadata("outerYield.kt") + public void testOuterYield_1_3() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithStdLib/coroutines/restrictSuspension/outerYield.kt"); + doTestWithCoroutinesPackageReplacement(fileName, "kotlin.coroutines"); + } + @TestMetadata("sameInstance.kt") public void testSameInstance_1_2() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithStdLib/coroutines/restrictSuspension/sameInstance.kt"); @@ -1870,6 +1894,18 @@ public class DiagnosticsTestWithStdLibGenerated extends AbstractDiagnosticsTestW String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithStdLib/coroutines/restrictSuspension/simpleForbidden.kt"); doTestWithCoroutinesPackageReplacement(fileName, "kotlin.coroutines"); } + + @TestMetadata("wrongEnclosingFunction.kt") + public void testWrongEnclosingFunction_1_2() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithStdLib/coroutines/restrictSuspension/wrongEnclosingFunction.kt"); + doTestWithCoroutinesPackageReplacement(fileName, "kotlin.coroutines.experimental"); + } + + @TestMetadata("wrongEnclosingFunction.kt") + public void testWrongEnclosingFunction_1_3() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithStdLib/coroutines/restrictSuspension/wrongEnclosingFunction.kt"); + doTestWithCoroutinesPackageReplacement(fileName, "kotlin.coroutines"); + } } @TestMetadata("compiler/testData/diagnostics/testsWithStdLib/coroutines/suspendFunctionType") diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/javac/DiagnosticsTestWithStdLibUsingJavacGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/javac/DiagnosticsTestWithStdLibUsingJavacGenerated.java index 282914a2a37..e6739dc9a73 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/javac/DiagnosticsTestWithStdLibUsingJavacGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/javac/DiagnosticsTestWithStdLibUsingJavacGenerated.java @@ -1835,6 +1835,18 @@ public class DiagnosticsTestWithStdLibUsingJavacGenerated extends AbstractDiagno doTestWithCoroutinesPackageReplacement(fileName, "kotlin.coroutines"); } + @TestMetadata("memberExtension.kt") + public void testMemberExtension_1_2() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithStdLib/coroutines/restrictSuspension/memberExtension.kt"); + doTestWithCoroutinesPackageReplacement(fileName, "kotlin.coroutines.experimental"); + } + + @TestMetadata("memberExtension.kt") + public void testMemberExtension_1_3() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithStdLib/coroutines/restrictSuspension/memberExtension.kt"); + doTestWithCoroutinesPackageReplacement(fileName, "kotlin.coroutines"); + } + @TestMetadata("notRelatedFun.kt") public void testNotRelatedFun_1_2() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithStdLib/coroutines/restrictSuspension/notRelatedFun.kt"); @@ -1847,6 +1859,18 @@ public class DiagnosticsTestWithStdLibUsingJavacGenerated extends AbstractDiagno doTestWithCoroutinesPackageReplacement(fileName, "kotlin.coroutines"); } + @TestMetadata("outerYield.kt") + public void testOuterYield_1_2() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithStdLib/coroutines/restrictSuspension/outerYield.kt"); + doTestWithCoroutinesPackageReplacement(fileName, "kotlin.coroutines.experimental"); + } + + @TestMetadata("outerYield.kt") + public void testOuterYield_1_3() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithStdLib/coroutines/restrictSuspension/outerYield.kt"); + doTestWithCoroutinesPackageReplacement(fileName, "kotlin.coroutines"); + } + @TestMetadata("sameInstance.kt") public void testSameInstance_1_2() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithStdLib/coroutines/restrictSuspension/sameInstance.kt"); @@ -1870,6 +1894,18 @@ public class DiagnosticsTestWithStdLibUsingJavacGenerated extends AbstractDiagno String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithStdLib/coroutines/restrictSuspension/simpleForbidden.kt"); doTestWithCoroutinesPackageReplacement(fileName, "kotlin.coroutines"); } + + @TestMetadata("wrongEnclosingFunction.kt") + public void testWrongEnclosingFunction_1_2() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithStdLib/coroutines/restrictSuspension/wrongEnclosingFunction.kt"); + doTestWithCoroutinesPackageReplacement(fileName, "kotlin.coroutines.experimental"); + } + + @TestMetadata("wrongEnclosingFunction.kt") + public void testWrongEnclosingFunction_1_3() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithStdLib/coroutines/restrictSuspension/wrongEnclosingFunction.kt"); + doTestWithCoroutinesPackageReplacement(fileName, "kotlin.coroutines"); + } } @TestMetadata("compiler/testData/diagnostics/testsWithStdLib/coroutines/suspendFunctionType")