Make suspension calls generation more stable
Instead of performing signature change during transformation it's convinient to make just when generating corresponding call, for example there is no need to think about default parameters as they just work as is See comment above replaceSuspensionFunctionViewWithRealDescriptor for clarification
This commit is contained in:
@@ -39,6 +39,7 @@ import org.jetbrains.kotlin.codegen.binding.CodegenBinding;
|
||||
import org.jetbrains.kotlin.codegen.context.*;
|
||||
import org.jetbrains.kotlin.codegen.coroutines.CoroutineCodegen;
|
||||
import org.jetbrains.kotlin.codegen.coroutines.CoroutineCodegenUtilKt;
|
||||
import org.jetbrains.kotlin.codegen.coroutines.ResolvedCallWithRealDescriptor;
|
||||
import org.jetbrains.kotlin.codegen.extensions.ExpressionCodegenExtension;
|
||||
import org.jetbrains.kotlin.codegen.inline.*;
|
||||
import org.jetbrains.kotlin.codegen.intrinsics.*;
|
||||
@@ -1665,26 +1666,31 @@ public class ExpressionCodegen extends KtVisitor<StackValue, StackValue> impleme
|
||||
tempVariables.put(argumentExpression, valueToReturn);
|
||||
}
|
||||
|
||||
// Currently only handleResult members are supported
|
||||
ReceiverValue dispatchReceiver = resolvedCall.getDispatchReceiver();
|
||||
assert dispatchReceiver != null : "Dispatch receiver is null for handleResult to " + resolvedCall.getResultingDescriptor();
|
||||
assert dispatchReceiver instanceof ExtensionReceiver
|
||||
: "Argument for handleResult call to " + resolvedCall.getResultingDescriptor() +
|
||||
" should be a coroutine receiver parameter, but " + dispatchReceiver + " found";
|
||||
ClassDescriptor coroutineClassDescriptor =
|
||||
bindingContext.get(CodegenBinding.CLASS_FOR_CALLABLE, ((ExtensionReceiver) dispatchReceiver).getDeclarationDescriptor());
|
||||
assert coroutineClassDescriptor != null : "Coroutine class descriptor should not be null";
|
||||
|
||||
// second argument for handleResult is always Continuation<T> ('this'-object in current implementation)
|
||||
tempVariables.put(
|
||||
resolvedCall.getValueArgumentsByIndex().get(1).getArguments().get(0).getArgumentExpression(),
|
||||
StackValue.thisOrOuter(this, coroutineClassDescriptor, false, false));
|
||||
genCoroutineInstanceValueFromResolvedCall(resolvedCall));
|
||||
|
||||
return invokeFunction(resolvedCall, StackValue.none());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private 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";
|
||||
ClassDescriptor coroutineClassDescriptor =
|
||||
bindingContext.get(CodegenBinding.CLASS_FOR_CALLABLE, ((ExtensionReceiver) dispatchReceiver).getDeclarationDescriptor());
|
||||
assert coroutineClassDescriptor != null : "Coroutine class descriptor should not be null";
|
||||
|
||||
// second argument for handleResult is always Continuation<T> ('this'-object in current implementation)
|
||||
return StackValue.thisOrOuter(this, coroutineClassDescriptor, false, false);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private Type getVariableType(@NotNull VariableDescriptor variableDescriptor) {
|
||||
Type sharedVarType = typeMapper.getSharedVarType(variableDescriptor);
|
||||
@@ -2477,6 +2483,12 @@ public class ExpressionCodegen extends KtVisitor<StackValue, StackValue> impleme
|
||||
|
||||
@NotNull
|
||||
public StackValue invokeFunction(@NotNull Call call, @NotNull ResolvedCall<?> resolvedCall, @NotNull StackValue receiver) {
|
||||
ResolvedCallWithRealDescriptor callWithRealDescriptor =
|
||||
CoroutineCodegenUtilKt.replaceSuspensionFunctionViewWithRealDescriptor(resolvedCall, state.getProject());
|
||||
if (callWithRealDescriptor != null) {
|
||||
tempVariables.put(callWithRealDescriptor.getFakeThisExpression(), genCoroutineInstanceValueFromResolvedCall(resolvedCall));
|
||||
return invokeFunction(callWithRealDescriptor.getResolvedCall(), receiver);
|
||||
}
|
||||
FunctionDescriptor fd = accessibleFunctionDescriptor(resolvedCall);
|
||||
ClassDescriptor superCallTarget = getSuperCallTarget(call);
|
||||
|
||||
|
||||
+1
-6
@@ -198,9 +198,7 @@ class CoroutineTransformerMethodVisitor(
|
||||
private fun transformCallAndReturnContinuationLabel(suspension: SuspensionPoint, methodNode: MethodNode): LabelNode {
|
||||
val call = suspension.suspensionCall
|
||||
val method = Method(call.name, call.desc)
|
||||
val newParameters = method.argumentTypes + CONTINUATION_INTERFACE_ASM_TYPE
|
||||
|
||||
call.desc = Method(method.name, Type.VOID_TYPE, newParameters).descriptor
|
||||
call.desc = Method(method.name, Type.VOID_TYPE, method.argumentTypes).descriptor
|
||||
|
||||
val continuationLabel = LabelNode()
|
||||
|
||||
@@ -213,9 +211,6 @@ class CoroutineTransformerMethodVisitor(
|
||||
FieldInsnNode(
|
||||
Opcodes.PUTFIELD, classBuilder.thisName, COROUTINE_LABEL_FIELD_NAME, Type.INT_TYPE.descriptor)))
|
||||
|
||||
// Pass continuation
|
||||
insertBefore(call, VarInsnNode(Opcodes.ALOAD, 0))
|
||||
|
||||
val nextInsnAfterCall = call.next
|
||||
|
||||
// Exit
|
||||
|
||||
+76
-5
@@ -16,11 +16,20 @@
|
||||
|
||||
package org.jetbrains.kotlin.codegen.coroutines
|
||||
|
||||
import org.jetbrains.kotlin.coroutines.CONTINUATION_INTERFACE_FQ_NAME
|
||||
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
|
||||
import org.jetbrains.org.objectweb.asm.Type
|
||||
|
||||
val CONTINUATION_INTERFACE_ASM_TYPE = Type.getObjectType(JvmClassName.byFqNameWithoutInnerClasses(CONTINUATION_INTERFACE_FQ_NAME).internalName)
|
||||
import com.intellij.openapi.project.Project
|
||||
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
|
||||
import org.jetbrains.kotlin.psi.KtExpression
|
||||
import org.jetbrains.kotlin.psi.KtPsiFactory
|
||||
import org.jetbrains.kotlin.resolve.BindingTraceContext
|
||||
import org.jetbrains.kotlin.resolve.DelegatingBindingTrace
|
||||
import org.jetbrains.kotlin.resolve.calls.model.ExpressionValueArgument
|
||||
import org.jetbrains.kotlin.resolve.calls.model.MutableDataFlowInfoForArguments
|
||||
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
|
||||
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCallImpl
|
||||
import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowInfo
|
||||
import org.jetbrains.kotlin.resolve.calls.tasks.TracingStrategy
|
||||
import org.jetbrains.kotlin.types.TypeConstructorSubstitution
|
||||
import org.jetbrains.kotlin.types.typeUtil.asTypeProjection
|
||||
|
||||
// These classes do not actually exist at runtime
|
||||
val CONTINUATION_METHOD_ANNOTATION_DESC = "Lkotlin/ContinuationMethod;"
|
||||
@@ -30,3 +39,65 @@ const val SUSPENSION_POINT_MARKER_NAME = "suspensionPoint"
|
||||
|
||||
const val COROUTINE_CONTROLLER_FIELD_NAME = "controller"
|
||||
const val COROUTINE_LABEL_FIELD_NAME = "label"
|
||||
|
||||
data class ResolvedCallWithRealDescriptor(val resolvedCall: ResolvedCall<*>, val fakeThisExpression: KtExpression)
|
||||
|
||||
// Resolved calls to suspension function contain descriptors as they visible within coroutines:
|
||||
// E.g. `fun <V> await(f: CompletableFuture<V>): V` instead of `fun <V> await(f: CompletableFuture<V>, machine: Continuation<V>): Unit`
|
||||
// See `createCoroutineSuspensionFunctionView` and it's usages for clarification
|
||||
// But for call generation it's convenient to have `machine` (continuation) parameter/argument within resolvedCall.
|
||||
// So this function returns resolved call with descriptor looking like `fun <V> await(f: CompletableFuture<V>, machine: Continuation<V>): V`
|
||||
// and fake `this` expression that used as argument for second parameter
|
||||
fun ResolvedCall<*>.replaceSuspensionFunctionViewWithRealDescriptor(
|
||||
project: Project
|
||||
): ResolvedCallWithRealDescriptor? {
|
||||
val function = candidateDescriptor as? FunctionDescriptor ?: return null
|
||||
if (!function.isSuspend) return null
|
||||
|
||||
val initialSignatureDescriptor = function.initialSignatureDescriptor ?: return null
|
||||
val newCandidateDescriptor =
|
||||
initialSignatureDescriptor.createCustomCopy {
|
||||
// Here we know that last parameter should be Continuation<T> where T is return type
|
||||
setReturnType(it.valueParameters.last().type.arguments.single().type)
|
||||
}
|
||||
|
||||
val newCall = ResolvedCallImpl(
|
||||
call,
|
||||
newCandidateDescriptor,
|
||||
dispatchReceiver, extensionReceiver, explicitReceiverKind,
|
||||
null, DelegatingBindingTrace(BindingTraceContext().bindingContext, "Temporary trace for unwrapped suspension function"),
|
||||
TracingStrategy.EMPTY, MutableDataFlowInfoForArguments.WithoutArgumentsCheck(DataFlowInfo.EMPTY))
|
||||
|
||||
this.valueArguments.forEach {
|
||||
newCall.recordValueArgument(newCandidateDescriptor.valueParameters[it.key.index], it.value)
|
||||
}
|
||||
|
||||
val psiFactory = KtPsiFactory(project)
|
||||
val arguments = psiFactory.createCallArguments("(this)").arguments.single()
|
||||
val thisExpression = arguments.getArgumentExpression()!!
|
||||
newCall.recordValueArgument(
|
||||
newCandidateDescriptor.valueParameters.last(),
|
||||
ExpressionValueArgument(arguments))
|
||||
|
||||
newCall.setResultingSubstitutor(
|
||||
TypeConstructorSubstitution.createByParametersMap(typeArguments.mapValues { it.value.asTypeProjection() }).buildSubstitutor())
|
||||
|
||||
return ResolvedCallWithRealDescriptor(newCall, thisExpression)
|
||||
}
|
||||
|
||||
private fun FunctionDescriptor.createCustomCopy(
|
||||
copySettings: FunctionDescriptor.CopyBuilder<out FunctionDescriptor>.(FunctionDescriptor) -> FunctionDescriptor.CopyBuilder<out FunctionDescriptor>
|
||||
): FunctionDescriptor {
|
||||
|
||||
val newOriginal =
|
||||
if (original !== this)
|
||||
original.createCustomCopy(copySettings)
|
||||
else
|
||||
null
|
||||
|
||||
val result = newCopyBuilder().copySettings(this).setOriginal(newOriginal).build()!!
|
||||
|
||||
result.overriddenDescriptors = this.overriddenDescriptors.map { it.createCustomCopy(copySettings) }
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ fun SimpleFunctionDescriptor.createCoroutineSuspensionFunctionView(): SimpleFunc
|
||||
setReturnType(returnType)
|
||||
setOriginal(newOriginal)
|
||||
setValueParameters(valueParameters.subList(0, valueParameters.size - 1))
|
||||
setSignatureChange()
|
||||
}.build()!!
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
class Controller {
|
||||
suspend fun suspendHere(a: String = "abc", i: Int = 2, x: Continuation<String>) {
|
||||
x.resume(a + "#" + (i + 1))
|
||||
}
|
||||
}
|
||||
|
||||
fun builder(coroutine c: Controller.() -> Continuation<Unit>) {
|
||||
c(Controller()).resume(Unit)
|
||||
}
|
||||
|
||||
fun box(): String {
|
||||
var result = "OK"
|
||||
|
||||
builder {
|
||||
var a = suspendHere()
|
||||
if (a != "abc#3") {
|
||||
result = "fail 1: $a"
|
||||
throw RuntimeException(result)
|
||||
}
|
||||
|
||||
a = suspendHere("cde")
|
||||
if (a != "cde#3") {
|
||||
result = "fail 2: $a"
|
||||
throw RuntimeException(result)
|
||||
}
|
||||
|
||||
a = suspendHere(i = 6)
|
||||
if (a != "abc#7") {
|
||||
result = "fail 3: $a"
|
||||
throw RuntimeException(result)
|
||||
}
|
||||
|
||||
a = suspendHere("xyz", 9)
|
||||
if (a != "xyz#10") {
|
||||
result = "fail 4: $a"
|
||||
throw RuntimeException(result)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
@@ -4111,6 +4111,12 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
|
||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/coroutines"), Pattern.compile("^(.+)\\.kt$"), true);
|
||||
}
|
||||
|
||||
@TestMetadata("defaultParametersInSuspend.kt")
|
||||
public void testDefaultParametersInSuspend() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/defaultParametersInSuspend.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("generate.kt")
|
||||
public void testGenerate() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/generate.kt");
|
||||
|
||||
Reference in New Issue
Block a user