diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/ExpressionCodegen.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/ExpressionCodegen.java index 4be5d9a4335..b56beabd1cd 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/ExpressionCodegen.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/ExpressionCodegen.java @@ -1869,21 +1869,30 @@ public class ExpressionCodegen extends KtVisitor impleme } @NotNull - public StackValue genCoroutineInstanceValueFromResolvedCall(ResolvedCall resolvedCall) { - // Currently only handleResult/suspend members are supported - ReceiverValue dispatchReceiver = resolvedCall.getDispatchReceiver(); - assert dispatchReceiver != null : "Dispatch receiver is null for handleResult/suspend to " + resolvedCall.getResultingDescriptor(); - assert dispatchReceiver instanceof ExtensionReceiver - : "Argument for handleResult call to " + resolvedCall.getResultingDescriptor() + - " should be a coroutine receiver parameter, but " + dispatchReceiver + " found"; + public StackValue genCoroutineInstanceValueFromResolvedCall(@NotNull ResolvedCall resolvedCall) { + ExtensionReceiver controllerReceiver = getControllerReceiverFromResolvedCall(resolvedCall); ClassDescriptor coroutineClassDescriptor = - bindingContext.get(CodegenBinding.CLASS_FOR_CALLABLE, ((ExtensionReceiver) dispatchReceiver).getDeclarationDescriptor()); + bindingContext.get(CodegenBinding.CLASS_FOR_CALLABLE, controllerReceiver.getDeclarationDescriptor()); assert coroutineClassDescriptor != null : "Coroutine class descriptor should not be null"; // second argument for handleResult is always Continuation ('this'-object in current implementation) return StackValue.thisOrOuter(this, coroutineClassDescriptor, false, false); } + private static ExtensionReceiver getControllerReceiverFromResolvedCall(@NotNull ResolvedCall resolvedCall) { + ReceiverValue controllerReceiver = + resolvedCall.getDispatchReceiver() != null + ? resolvedCall.getDispatchReceiver() + : resolvedCall.getExtensionReceiver(); + + assert controllerReceiver != null : "Both dispatch and extension receivers are null for handleResult/suspend to " + resolvedCall.getResultingDescriptor(); + assert controllerReceiver instanceof ExtensionReceiver + : "Argument for handleResult call to " + resolvedCall.getResultingDescriptor() + + " should be a coroutine receiver parameter, but " + controllerReceiver + " found"; + + return (ExtensionReceiver) controllerReceiver; + } + @NotNull private Type getVariableType(@NotNull VariableDescriptor variableDescriptor) { Type sharedVarType = typeMapper.getSharedVarType(variableDescriptor); diff --git a/compiler/resolution/src/org/jetbrains/kotlin/resolve/calls/tower/ScopeTower.kt b/compiler/resolution/src/org/jetbrains/kotlin/resolve/calls/tower/ScopeTower.kt index f7dc9981913..1fceee38d93 100644 --- a/compiler/resolution/src/org/jetbrains/kotlin/resolve/calls/tower/ScopeTower.kt +++ b/compiler/resolution/src/org/jetbrains/kotlin/resolve/calls/tower/ScopeTower.kt @@ -69,6 +69,8 @@ interface CandidateWithBoundDispatchReceiver { val diagnostics: List val dispatchReceiver: ReceiverValue? + + fun copy(newDescriptor: @UnsafeVariance D): CandidateWithBoundDispatchReceiver } data class ResolutionCandidateStatus(val diagnostics: List) { diff --git a/compiler/resolution/src/org/jetbrains/kotlin/resolve/calls/tower/ScopeTowerProcessors.kt b/compiler/resolution/src/org/jetbrains/kotlin/resolve/calls/tower/ScopeTowerProcessors.kt index 90f281fe789..0eb6937188c 100644 --- a/compiler/resolution/src/org/jetbrains/kotlin/resolve/calls/tower/ScopeTowerProcessors.kt +++ b/compiler/resolution/src/org/jetbrains/kotlin/resolve/calls/tower/ScopeTowerProcessors.kt @@ -21,6 +21,8 @@ import org.jetbrains.kotlin.descriptors.FunctionDescriptor import org.jetbrains.kotlin.descriptors.VariableDescriptor import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.resolve.calls.tasks.ExplicitReceiverKind +import org.jetbrains.kotlin.resolve.coroutine.CoroutineReceiverValue +import org.jetbrains.kotlin.resolve.coroutine.createCoroutineSuspensionFunctionView import org.jetbrains.kotlin.resolve.scopes.receivers.QualifierReceiver import org.jetbrains.kotlin.resolve.scopes.receivers.Receiver import org.jetbrains.kotlin.resolve.scopes.receivers.ReceiverValue @@ -102,9 +104,23 @@ private class NoExplicitReceiverScopeTowerProcessor { - data.level.collectCandidates(name, data.implicitReceiver).filter { it.requiresExtensionReceiver }.map { - context.createCandidate(it, ExplicitReceiverKind.NO_EXPLICIT_RECEIVER, extensionReceiver = data.implicitReceiver) + val result = mutableListOf() + + data.level.collectCandidates(name, data.implicitReceiver).filter { it.requiresExtensionReceiver }.forEach { + result.add( + context.createCandidate( + it, ExplicitReceiverKind.NO_EXPLICIT_RECEIVER, extensionReceiver = data.implicitReceiver)) + + if (data.implicitReceiver is CoroutineReceiverValue) { + val newDescriptor = it.descriptor.createCoroutineSuspensionFunctionView() ?: return@forEach + result.add( + context.createCandidate( + it.copy(newDescriptor = newDescriptor), + ExplicitReceiverKind.NO_EXPLICIT_RECEIVER, extensionReceiver = data.implicitReceiver)) + } } + + result } else -> emptyList() } diff --git a/compiler/resolution/src/org/jetbrains/kotlin/resolve/calls/tower/TowerUtils.kt b/compiler/resolution/src/org/jetbrains/kotlin/resolve/calls/tower/TowerUtils.kt index fce3fb12e0e..b084bc260d2 100644 --- a/compiler/resolution/src/org/jetbrains/kotlin/resolve/calls/tower/TowerUtils.kt +++ b/compiler/resolution/src/org/jetbrains/kotlin/resolve/calls/tower/TowerUtils.kt @@ -35,4 +35,7 @@ internal class CandidateWithBoundDispatchReceiverImpl -) : CandidateWithBoundDispatchReceiver +) : CandidateWithBoundDispatchReceiver { + override fun copy(newDescriptor: @UnsafeVariance D) = + CandidateWithBoundDispatchReceiverImpl(dispatchReceiver, newDescriptor, diagnostics) +} diff --git a/compiler/testData/codegen/box/coroutines/suspendExtension.kt b/compiler/testData/codegen/box/coroutines/suspendExtension.kt new file mode 100644 index 00000000000..e46bef9323a --- /dev/null +++ b/compiler/testData/codegen/box/coroutines/suspendExtension.kt @@ -0,0 +1,36 @@ +@AllowSuspendExtensions +class Controller { + suspend fun String.suspendHere(x: Continuation) { + x.resume(this) + } + + inline suspend fun String.inlineSuspendHere(x: Continuation) { + suspendHere(x) + } +} + +suspend fun Controller.suspendExtension(v: String, x: Continuation) { + v.suspendHere(x) +} + +inline suspend fun Controller.inlineSuspendExtension(v: String, x: Continuation) { + v.inlineSuspendHere(x) +} + +fun builder(coroutine c: Controller.() -> Continuation) { + c(Controller()).resume(Unit) +} + +fun box(): String { + var result = "" + + builder { + if ("56".suspendHere() != "56") throw java.lang.RuntimeException("fail 1") + if ("28".inlineSuspendHere() != "28") throw java.lang.RuntimeException("fail 2") + + if (suspendExtension("123") != "123") throw java.lang.RuntimeException("fail 3") + result = inlineSuspendExtension("OK") + } + + return result +} diff --git a/compiler/testData/diagnostics/tests/coroutines/suspendExtensionFunctions.kt b/compiler/testData/diagnostics/tests/coroutines/suspendExtensionFunctions.kt new file mode 100644 index 00000000000..b4599c1b987 --- /dev/null +++ b/compiler/testData/diagnostics/tests/coroutines/suspendExtensionFunctions.kt @@ -0,0 +1,60 @@ +// !DIAGNOSTICS: -UNUSED_PARAMETER +// !CHECK_TYPE +@AllowSuspendExtensions +class Controller + +suspend fun Controller.noParams(c: Continuation) { + +} +suspend fun Controller.yieldString(value: String, c: Continuation) { +} + +suspend fun Controller.await(f: () -> V, machine: Continuation) { +} + +suspend fun Controller.await(f: Int, machine: Continuation) { +} + +suspend fun Controller.severalParams(x: String, y: Int, machine: Continuation) { +} + +// These two must be prohibited because String and Any are not properly annotated +suspend fun String.wrongReceiver(y: Int, machine: Continuation) { +} + +suspend fun Any.anyReceiver(y: Int, machine: Continuation) { +} + +fun builder(coroutine c: Controller.() -> Continuation) {} + +fun test() { + builder { + noParams() + yieldString("abc") checkType { _() } + yieldString(1) checkType { _() } + + await { "123" } checkType { _() } + + // Inference from lambda return type + await { 123 } checkType { _() } + + // Inference from expected type + checkSubtype(await(567)) + + await(123) checkType { _() } + + severalParams("", 89) checkType { _() } + // TODO: prohibit such calls + severalParams("", 89, 6.9) checkType { _() } + severalParams("", 89, this as Continuation) checkType { _() } + + wrongReceiver(1) + + with("") { + wrongReceiver(2) + } + + // Though such calls are allowed declarations with not-annotated receiver should be prohibited + anyReceiver(3) + } +} diff --git a/compiler/testData/diagnostics/tests/coroutines/suspendExtensionFunctions.txt b/compiler/testData/diagnostics/tests/coroutines/suspendExtensionFunctions.txt new file mode 100644 index 00000000000..fc84664b62a --- /dev/null +++ b/compiler/testData/diagnostics/tests/coroutines/suspendExtensionFunctions.txt @@ -0,0 +1,18 @@ +package + +public fun builder(/*0*/ coroutine c: Controller.() -> kotlin.coroutines.Continuation): kotlin.Unit +public fun test(): kotlin.Unit +public suspend fun kotlin.Any.anyReceiver(/*0*/ y: kotlin.Int, /*1*/ machine: kotlin.coroutines.Continuation): kotlin.Unit +public suspend fun Controller.await(/*0*/ f: () -> V, /*1*/ machine: kotlin.coroutines.Continuation): kotlin.Unit +public suspend fun Controller.await(/*0*/ f: kotlin.Int, /*1*/ machine: kotlin.coroutines.Continuation): kotlin.Unit +public suspend fun Controller.noParams(/*0*/ c: kotlin.coroutines.Continuation): kotlin.Unit +public suspend fun Controller.severalParams(/*0*/ x: kotlin.String, /*1*/ y: kotlin.Int, /*2*/ machine: kotlin.coroutines.Continuation): kotlin.Unit +public suspend fun kotlin.String.wrongReceiver(/*0*/ y: kotlin.Int, /*1*/ machine: kotlin.coroutines.Continuation): kotlin.Unit +public suspend fun Controller.yieldString(/*0*/ value: kotlin.String, /*1*/ c: kotlin.coroutines.Continuation): kotlin.Unit + +@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 + 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 267de57f91f..1bb1d10fa0c 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java @@ -3996,6 +3996,12 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest { doTest(fileName); } + @TestMetadata("suspendExtensionFunctions.kt") + public void testSuspendExtensionFunctions() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/coroutines/suspendExtensionFunctions.kt"); + doTest(fileName); + } + @TestMetadata("suspendFunctions.kt") public void testSuspendFunctions() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/coroutines/suspendFunctions.kt"); diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java index 3a789cf965a..b9aba8e7d8c 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java @@ -4327,6 +4327,12 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest { doTest(fileName); } + @TestMetadata("suspendExtension.kt") + public void testSuspendExtension() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/suspendExtension.kt"); + doTest(fileName); + } + @TestMetadata("suspendFromInlineLambda.kt") public void testSuspendFromInlineLambda() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/suspendFromInlineLambda.kt");