Support open suspend members and super-calls
The problem was that the resume call (from doResume) for open members was based on common INVOKEVIRTUAL to the original function that lead to the invocation of the override when it was expected to be the overridden (after super-call being suspended) The solution is to generate method bodies for open members into the special $suspendImpl synthetic function that may be called from the doResume implementation #KT-17587 Fixed
This commit is contained in:
@@ -201,6 +201,46 @@ public class FunctionCodegen {
|
||||
// Native methods are only defined in facades and do not need package part implementations
|
||||
return;
|
||||
}
|
||||
|
||||
boolean isOpenSuspendInClass =
|
||||
functionDescriptor.isSuspend() && DescriptorUtilKt.isEffectivelyOpen(functionDescriptor) &&
|
||||
!isInterface(functionDescriptor.getContainingDeclaration()) &&
|
||||
origin.getOriginKind() != JvmDeclarationOriginKind.CLASS_MEMBER_DELEGATION_TO_DEFAULT_IMPL;
|
||||
|
||||
if (isOpenSuspendInClass) {
|
||||
MethodVisitor mv =
|
||||
v.newMethod(origin,
|
||||
flags,
|
||||
asmMethod.getName(),
|
||||
asmMethod.getDescriptor(),
|
||||
jvmSignature.getGenericsSignature(),
|
||||
getThrownExceptions(functionDescriptor, typeMapper)
|
||||
);
|
||||
|
||||
mv.visitCode();
|
||||
mv.visitVarInsn(Opcodes.ALOAD, 0);
|
||||
int index = 1;
|
||||
for (Type type : asmMethod.getArgumentTypes()) {
|
||||
mv.visitVarInsn(type.getOpcode(Opcodes.ILOAD), index);
|
||||
index += type.getSize();
|
||||
}
|
||||
|
||||
asmMethod = CoroutineCodegenUtilKt.getImplForOpenMethod(asmMethod, v.getThisName());
|
||||
|
||||
mv.visitMethodInsn(
|
||||
Opcodes.INVOKESTATIC,
|
||||
v.getThisName(), asmMethod.getName(), asmMethod.getDescriptor(),
|
||||
false
|
||||
);
|
||||
|
||||
mv.visitInsn(Opcodes.ARETURN);
|
||||
mv.visitEnd();
|
||||
|
||||
flags |= Opcodes.ACC_STATIC | Opcodes.ACC_SYNTHETIC;
|
||||
flags &= ~getVisibilityAccessFlag(functionDescriptor);
|
||||
flags |= AsmUtil.NO_FLAG_PACKAGE_PRIVATE;
|
||||
}
|
||||
|
||||
MethodVisitor mv =
|
||||
strategy.wrapMethodVisitor(
|
||||
v.newMethod(origin,
|
||||
|
||||
+17
-2
@@ -32,6 +32,7 @@ import org.jetbrains.kotlin.psi.KtDeclarationWithBody
|
||||
import org.jetbrains.kotlin.psi.KtElement
|
||||
import org.jetbrains.kotlin.psi.KtFunction
|
||||
import org.jetbrains.kotlin.psi.KtFunctionLiteral
|
||||
import org.jetbrains.kotlin.resolve.DescriptorUtils
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.builtIns
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.module
|
||||
import org.jetbrains.kotlin.resolve.jvm.AsmTypes
|
||||
@@ -400,14 +401,28 @@ class CoroutineCodegenForNamedFunction private constructor(
|
||||
).put(captureThisType, codegen.v)
|
||||
}
|
||||
|
||||
val callableMethod = typeMapper.mapToCallableMethod(suspendFunctionJvmView, false)
|
||||
val isInterfaceMethod = DescriptorUtils.isInterface(suspendFunctionJvmView.containingDeclaration)
|
||||
val callableMethod =
|
||||
typeMapper.mapToCallableMethod(
|
||||
suspendFunctionJvmView,
|
||||
// Obtain default impls method for interfaces
|
||||
isInterfaceMethod
|
||||
)
|
||||
|
||||
for (argumentType in callableMethod.getAsmMethod().argumentTypes.dropLast(1)) {
|
||||
AsmUtil.pushDefaultValueOnStack(argumentType, codegen.v)
|
||||
}
|
||||
|
||||
codegen.v.load(0, AsmTypes.OBJECT_TYPE)
|
||||
callableMethod.genInvokeInstruction(codegen.v)
|
||||
|
||||
if (suspendFunctionJvmView.isEffectivelyOpen() && !isInterfaceMethod && captureThisType != null) {
|
||||
val owner = captureThisType.internalName
|
||||
val impl = callableMethod.getAsmMethod().getImplForOpenMethod(owner)
|
||||
codegen.v.invokestatic(owner, impl.name, impl.descriptor, false)
|
||||
}
|
||||
else {
|
||||
callableMethod.genInvokeInstruction(codegen.v)
|
||||
}
|
||||
|
||||
codegen.v.visitInsn(Opcodes.ARETURN)
|
||||
}
|
||||
|
||||
@@ -49,6 +49,7 @@ import org.jetbrains.kotlin.utils.addToStdlib.safeAs
|
||||
import org.jetbrains.org.objectweb.asm.Opcodes
|
||||
import org.jetbrains.org.objectweb.asm.Type
|
||||
import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter
|
||||
import org.jetbrains.org.objectweb.asm.commons.Method
|
||||
import org.jetbrains.org.objectweb.asm.tree.MethodNode
|
||||
|
||||
// These classes do not actually exist at runtime
|
||||
@@ -300,3 +301,6 @@ fun InstructionAdapter.invokeDoResumeWithUnit(thisName: String) {
|
||||
false
|
||||
)
|
||||
}
|
||||
|
||||
fun Method.getImplForOpenMethod(ownerInternalName: String) =
|
||||
Method("$name\$suspendImpl", returnType, arrayOf(Type.getObjectType(ownerInternalName)) + argumentTypes)
|
||||
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
// WITH_RUNTIME
|
||||
// WITH_COROUTINES
|
||||
import helpers.*
|
||||
import kotlin.coroutines.experimental.*
|
||||
import kotlin.coroutines.experimental.intrinsics.*
|
||||
|
||||
open class A(val v: String) {
|
||||
suspend fun suspendThere(v: String): String = suspendCoroutineOrReturn { x ->
|
||||
x.resume(v)
|
||||
COROUTINE_SUSPENDED
|
||||
}
|
||||
|
||||
open suspend fun suspendHere(): String = suspendThere("O") + suspendThere(v)
|
||||
}
|
||||
|
||||
class B(v: String) : A(v) {
|
||||
override suspend fun suspendHere(): String = super.suspendHere() + suspendThere("56")
|
||||
}
|
||||
|
||||
fun builder(c: suspend A.() -> Unit) {
|
||||
c.startCoroutine(B("K"), EmptyContinuation)
|
||||
}
|
||||
|
||||
fun box(): String {
|
||||
var result = ""
|
||||
|
||||
builder {
|
||||
result = suspendHere()
|
||||
}
|
||||
|
||||
if (result != "OK56") return "fail 1: $result"
|
||||
|
||||
return "OK"
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
// WITH_RUNTIME
|
||||
// WITH_COROUTINES
|
||||
import helpers.*
|
||||
import kotlin.coroutines.experimental.*
|
||||
import kotlin.coroutines.experimental.intrinsics.*
|
||||
|
||||
interface A {
|
||||
val v: String
|
||||
|
||||
suspend fun suspendThere(v: String): String = suspendCoroutineOrReturn { x ->
|
||||
x.resume(v)
|
||||
COROUTINE_SUSPENDED
|
||||
}
|
||||
|
||||
suspend fun suspendHere(): String = suspendThere("O") + suspendThere(v)
|
||||
}
|
||||
|
||||
interface A2 : A {
|
||||
override suspend fun suspendHere(): String = super.suspendHere() + suspendThere("56")
|
||||
}
|
||||
|
||||
class B(override val v: String) : A2
|
||||
|
||||
fun builder(c: suspend A.() -> Unit) {
|
||||
c.startCoroutine(B("K"), EmptyContinuation)
|
||||
}
|
||||
|
||||
fun box(): String {
|
||||
var result = ""
|
||||
|
||||
builder {
|
||||
result = suspendHere()
|
||||
}
|
||||
|
||||
if (result != "OK56") return "fail 1: $result"
|
||||
|
||||
return "OK"
|
||||
}
|
||||
+12
@@ -5680,6 +5680,18 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("superCall.kt")
|
||||
public void testSuperCall() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/suspendFunctionAsCoroutine/superCall.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("superCallInterface.kt")
|
||||
public void testSuperCallInterface() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/suspendFunctionAsCoroutine/superCallInterface.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("withVariables.kt")
|
||||
public void testWithVariables() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/suspendFunctionAsCoroutine/withVariables.kt");
|
||||
|
||||
@@ -5680,6 +5680,18 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("superCall.kt")
|
||||
public void testSuperCall() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/suspendFunctionAsCoroutine/superCall.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("superCallInterface.kt")
|
||||
public void testSuperCallInterface() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/suspendFunctionAsCoroutine/superCallInterface.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("withVariables.kt")
|
||||
public void testWithVariables() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/suspendFunctionAsCoroutine/withVariables.kt");
|
||||
|
||||
@@ -5680,6 +5680,18 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("superCall.kt")
|
||||
public void testSuperCall() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/suspendFunctionAsCoroutine/superCall.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("superCallInterface.kt")
|
||||
public void testSuperCallInterface() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/suspendFunctionAsCoroutine/superCallInterface.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("withVariables.kt")
|
||||
public void testWithVariables() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/suspendFunctionAsCoroutine/withVariables.kt");
|
||||
|
||||
@@ -19,6 +19,7 @@ package org.jetbrains.kotlin.descriptors
|
||||
import org.jetbrains.kotlin.incremental.components.LookupLocation
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.resolve.DescriptorUtils
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
|
||||
import org.jetbrains.kotlin.utils.sure
|
||||
|
||||
fun ModuleDescriptor.resolveClassByFqName(fqName: FqName, lookupLocation: LookupLocation): ClassDescriptor? {
|
||||
@@ -37,3 +38,6 @@ fun ModuleDescriptor.findContinuationClassDescriptorOrNull(lookupLocation: Looku
|
||||
|
||||
fun ModuleDescriptor.findContinuationClassDescriptor(lookupLocation: LookupLocation) =
|
||||
findContinuationClassDescriptorOrNull(lookupLocation).sure { "Continuation interface is not found" }
|
||||
|
||||
fun FunctionDescriptor.isEffectivelyOpen() =
|
||||
modality == Modality.OPEN && containingDeclaration.safeAs<ClassDescriptor>()?.modality == Modality.OPEN
|
||||
|
||||
+12
@@ -6371,6 +6371,18 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest {
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("superCall.kt")
|
||||
public void testSuperCall() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/suspendFunctionAsCoroutine/superCall.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("superCallInterface.kt")
|
||||
public void testSuperCallInterface() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/suspendFunctionAsCoroutine/superCallInterface.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("withVariables.kt")
|
||||
public void testWithVariables() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/suspendFunctionAsCoroutine/withVariables.kt");
|
||||
|
||||
Reference in New Issue
Block a user