JS: add support of interceptResume function in coroutine controller
This commit is contained in:
committed by
Alexey Andreev
parent
9eff193ea4
commit
060f08a8dd
+6
@@ -18,9 +18,12 @@ package org.jetbrains.kotlin.backend.common
|
||||
|
||||
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
|
||||
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.SimpleFunctionDescriptor
|
||||
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.builtIns
|
||||
import org.jetbrains.kotlin.types.KotlinType
|
||||
import org.jetbrains.kotlin.util.OperatorNameConventions
|
||||
|
||||
val SUSPEND_WITH_CURRENT_CONTINUATION_NAME = Name.identifier("suspendWithCurrentContinuation")
|
||||
|
||||
@@ -30,4 +33,7 @@ fun FunctionDescriptor.getBuiltInSuspendWithCurrentContinuation() =
|
||||
?.getContributedFunctions(SUSPEND_WITH_CURRENT_CONTINUATION_NAME, NoLookupLocation.FROM_BACKEND)
|
||||
?.singleOrNull()
|
||||
|
||||
fun KotlinType.findInterceptResume() = findOperatorInController(this, OperatorNameConventions.COROUTINE_INTERCEPT_RESUME)
|
||||
|
||||
fun findOperatorInController(controllerType: KotlinType, name: Name): SimpleFunctionDescriptor? =
|
||||
controllerType.memberScope.getContributedFunctions(name, NoLookupLocation.FROM_BACKEND).singleOrNull { it.isOperator }
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package org.jetbrains.kotlin.codegen.coroutines
|
||||
|
||||
import org.jetbrains.kotlin.backend.common.findOperatorInController
|
||||
import org.jetbrains.kotlin.codegen.*
|
||||
import org.jetbrains.kotlin.codegen.binding.CodegenBinding
|
||||
import org.jetbrains.kotlin.codegen.context.ClosureContext
|
||||
|
||||
+2
-8
@@ -29,7 +29,6 @@ import org.jetbrains.kotlin.descriptors.SimpleFunctionDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.SourceElement
|
||||
import org.jetbrains.kotlin.descriptors.annotations.Annotations
|
||||
import org.jetbrains.kotlin.descriptors.impl.ValueParameterDescriptorImpl
|
||||
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.resolve.*
|
||||
@@ -240,14 +239,9 @@ private fun FunctionDescriptor.getContinuationParameterTypeOfSuspendFunction() =
|
||||
|
||||
val KotlinBuiltIns.continuationClassDescriptor get() = getBuiltInClassByFqName(DescriptorUtils.CONTINUATION_INTERFACE_FQ_NAME)
|
||||
|
||||
fun KotlinType.hasInlineInterceptResume() =
|
||||
findOperatorInController(this, OperatorNameConventions.COROUTINE_INTERCEPT_RESUME)?.isInline == true
|
||||
fun KotlinType.hasInlineInterceptResume() = findInterceptResume()?.isInline == true
|
||||
|
||||
fun KotlinType.hasNoinlineInterceptResume() =
|
||||
findOperatorInController(this, OperatorNameConventions.COROUTINE_INTERCEPT_RESUME)?.isInline == false
|
||||
|
||||
fun findOperatorInController(controllerType: KotlinType, name: Name): SimpleFunctionDescriptor? =
|
||||
controllerType.memberScope.getContributedFunctions(name, NoLookupLocation.FROM_BACKEND).singleOrNull { it.isOperator }
|
||||
fun KotlinType.hasNoinlineInterceptResume() = findInterceptResume()?.isInline == false
|
||||
|
||||
fun FunctionDescriptor.isBuiltInSuspendWithCurrentContinuation(): Boolean {
|
||||
if (name != SUSPEND_WITH_CURRENT_CONTINUATION_NAME) return false
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
// NO_INTERCEPT_RESUME_TESTS
|
||||
|
||||
class Controller {
|
||||
var log = ""
|
||||
var resumeIndex = 0
|
||||
|
||||
suspend fun <T> suspendWithValue(value: T): T = suspendWithCurrentContinuation { continuation ->
|
||||
log += "suspend($value);"
|
||||
continuation.resume(value)
|
||||
Suspend
|
||||
}
|
||||
|
||||
suspend fun suspendWithException(value: String): Unit = suspendWithCurrentContinuation { continuation ->
|
||||
log += "error($value);"
|
||||
continuation.resumeWithException(RuntimeException(value))
|
||||
Suspend
|
||||
}
|
||||
|
||||
operator fun interceptResume(block: () -> Unit) {
|
||||
var id = resumeIndex++
|
||||
log += "before $id;"
|
||||
block()
|
||||
log += "after $id;"
|
||||
}
|
||||
}
|
||||
|
||||
fun test(coroutine c: Controller.() -> Continuation<Unit>): String {
|
||||
val controller = Controller()
|
||||
c(controller).resume(Unit)
|
||||
return controller.log
|
||||
}
|
||||
|
||||
fun box(): String {
|
||||
var result = test {
|
||||
val o = suspendWithValue("O")
|
||||
val k = suspendWithValue("K")
|
||||
log += "$o$k;"
|
||||
}
|
||||
if (result != "before 0;suspend(O);before 1;suspend(K);before 2;OK;after 2;after 1;after 0;") return "fail1: $result"
|
||||
|
||||
result = test {
|
||||
try {
|
||||
suspendWithException("OK")
|
||||
log += "ignore;"
|
||||
}
|
||||
catch (e: RuntimeException) {
|
||||
log += "${e.message};"
|
||||
}
|
||||
}
|
||||
if (result != "before 0;error(OK);before 1;OK;after 1;after 0;") return "fail2: $result"
|
||||
|
||||
return "OK"
|
||||
}
|
||||
+6
@@ -4595,6 +4595,12 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("interceptResume.kt")
|
||||
public void testInterceptResume() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/interceptResume.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("iterateOverArray.kt")
|
||||
public void testIterateOverArray() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/iterateOverArray.kt");
|
||||
|
||||
@@ -4595,6 +4595,12 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("interceptResume.kt")
|
||||
public void testInterceptResume() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/interceptResume.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("iterateOverArray.kt")
|
||||
public void testIterateOverArray() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/iterateOverArray.kt");
|
||||
|
||||
+2
@@ -109,6 +109,8 @@ var JsFunction.continuationInterfaceRef: JsExpression? by MetadataProperty(defau
|
||||
|
||||
var JsName.imported by MetadataProperty(default = false)
|
||||
|
||||
var JsFunction.interceptResumeRef: JsExpression? by MetadataProperty(default = null)
|
||||
|
||||
enum class TypeCheck {
|
||||
TYPEOF,
|
||||
INSTANCEOF,
|
||||
|
||||
+21
-5
@@ -61,7 +61,7 @@ class CoroutineFunctionTransformer(
|
||||
val additionalStatements = mutableListOf<JsStatement>()
|
||||
val resumeName = generateDoResume(coroutineBlocks, context, additionalStatements, throwName)
|
||||
generateContinuationConstructor(context, additionalStatements, bodyTransformer.hasFinallyBlocks, globalCatchBlockIndex)
|
||||
generateContinuationMethods(resumeName, additionalStatements)
|
||||
generateContinuationMethods(context, resumeName, additionalStatements)
|
||||
|
||||
generateCoroutineInstantiation()
|
||||
|
||||
@@ -116,12 +116,18 @@ class CoroutineFunctionTransformer(
|
||||
return JsAstUtils.assignment(JsNameRef(Namer.METADATA, constructorName.makeRef()), metadataObject).makeStmt()
|
||||
}
|
||||
|
||||
private fun generateContinuationMethods(doResumeName: JsName, statements: MutableList<JsStatement>) {
|
||||
generateResumeFunction(doResumeName, statements, "resume", "data", listOf())
|
||||
generateResumeFunction(doResumeName, statements, "resumeWithException", "exception", listOf(Namer.getUndefinedExpression()))
|
||||
private fun generateContinuationMethods(
|
||||
context: CoroutineTransformationContext,
|
||||
doResumeName: JsName,
|
||||
statements: MutableList<JsStatement>
|
||||
) {
|
||||
generateResumeFunction(context, doResumeName, statements, "resume", "data", listOf())
|
||||
generateResumeFunction(context, doResumeName, statements, "resumeWithException", "exception",
|
||||
listOf(Namer.getUndefinedExpression()))
|
||||
}
|
||||
|
||||
private fun generateResumeFunction(
|
||||
context: CoroutineTransformationContext,
|
||||
doResumeName: JsName,
|
||||
statements: MutableList<JsStatement>,
|
||||
name: String,
|
||||
@@ -137,9 +143,19 @@ class CoroutineFunctionTransformer(
|
||||
resumeFunction.parameters += JsParameter(resumeParameter)
|
||||
|
||||
resumeFunction.body.statements.apply {
|
||||
val interceptResumeRef = function.interceptResumeRef
|
||||
val invocation = JsInvocation(JsNameRef(doResumeName, JsLiteral.THIS), additionalArgs + resumeParameter.makeRef())
|
||||
this += JsReturn(invocation)
|
||||
if (interceptResumeRef == null) {
|
||||
this += JsReturn(invocation)
|
||||
}
|
||||
else {
|
||||
val interceptLambda = JsFunction(resumeFunction.scope, JsBlock(JsReturn(invocation)), "")
|
||||
val interceptParameter = JsInvocation(JsNameRef("bind", interceptLambda), JsLiteral.THIS)
|
||||
val interceptInvocation = JsInvocation(interceptResumeRef.deepCopy(), interceptParameter)
|
||||
this += JsReturn(interceptInvocation)
|
||||
}
|
||||
}
|
||||
resumeFunction.body.replaceSpecialReferences(context)
|
||||
|
||||
statements.apply {
|
||||
assignToPrototype(resumeName, resumeFunction)
|
||||
|
||||
@@ -198,7 +198,7 @@ private fun CoroutineBlock.collectFinallyPaths(): List<List<CoroutineBlock>> {
|
||||
return finallyPaths
|
||||
}
|
||||
|
||||
fun JsBlock.replaceLocalVariables(scope: JsScope, context: CoroutineTransformationContext, localVariables: Set<JsName>) {
|
||||
fun JsBlock.replaceSpecialReferences(context: CoroutineTransformationContext) {
|
||||
val visitor = object : JsVisitorWithContextImpl() {
|
||||
override fun endVisit(x: JsNameRef, ctx: JsContext<in JsNode>) {
|
||||
when {
|
||||
@@ -213,11 +213,19 @@ fun JsBlock.replaceLocalVariables(scope: JsScope, context: CoroutineTransformati
|
||||
sideEffects = SideEffectKind.DEPENDS_ON_STATE
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
visitor.accept(this)
|
||||
}
|
||||
|
||||
x.qualifier == null && x.name in localVariables -> {
|
||||
val fieldName = scope.getFieldName(x.name!!)
|
||||
ctx.replaceMe(JsNameRef(fieldName, JsLiteral.THIS))
|
||||
}
|
||||
fun JsBlock.replaceLocalVariables(scope: JsScope, context: CoroutineTransformationContext, localVariables: Set<JsName>) {
|
||||
replaceSpecialReferences(context)
|
||||
val visitor = object : JsVisitorWithContextImpl() {
|
||||
override fun endVisit(x: JsNameRef, ctx: JsContext<in JsNode>) {
|
||||
if (x.qualifier == null && x.name in localVariables) {
|
||||
val fieldName = scope.getFieldName(x.name!!)
|
||||
ctx.replaceMe(JsNameRef(fieldName, JsLiteral.THIS))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5406,6 +5406,12 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest {
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("interceptResume.kt")
|
||||
public void testInterceptResume() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/interceptResume.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("iterateOverArray.kt")
|
||||
public void testIterateOverArray() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/iterateOverArray.kt");
|
||||
|
||||
+16
@@ -18,6 +18,7 @@ package org.jetbrains.kotlin.js.translate.expression
|
||||
|
||||
import com.google.dart.compiler.backend.js.ast.*
|
||||
import com.google.dart.compiler.backend.js.ast.metadata.*
|
||||
import org.jetbrains.kotlin.backend.common.findInterceptResume
|
||||
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
|
||||
import org.jetbrains.kotlin.descriptors.*
|
||||
import org.jetbrains.kotlin.descriptors.impl.ValueParameterDescriptorImpl
|
||||
@@ -96,6 +97,8 @@ class LiteralFunctionTranslator(context: TranslationContext) : AbstractTranslato
|
||||
null
|
||||
}
|
||||
|
||||
val interceptResumeRef = controllerType?.let { context().generateInterceptResumeRef(it) }
|
||||
|
||||
if (tracker.hasCapturedExceptContaining()) {
|
||||
val lambdaCreator = simpleReturnFunction(invokingContext.scope(), lambda)
|
||||
lambdaCreator.name = invokingContext.getInnerNameForDescriptor(descriptor)
|
||||
@@ -108,6 +111,7 @@ class LiteralFunctionTranslator(context: TranslationContext) : AbstractTranslato
|
||||
lambdaCreator.name.staticRef = lambdaCreator
|
||||
lambdaCreator.continuationInterfaceRef = invokingContext.getContinuationInterfaceReference()
|
||||
lambdaCreator.suspendObjectRef = suspendObjectRef
|
||||
lambdaCreator.interceptResumeRef = interceptResumeRef
|
||||
return lambdaCreator.withCapturedParameters(descriptor, descriptor.wrapContextForCoroutineIfNecessary(functionContext),
|
||||
invokingContext)
|
||||
}
|
||||
@@ -120,6 +124,7 @@ class LiteralFunctionTranslator(context: TranslationContext) : AbstractTranslato
|
||||
lambda.name.staticRef = lambda
|
||||
lambda.continuationInterfaceRef = invokingContext.getContinuationInterfaceReference()
|
||||
lambda.suspendObjectRef = suspendObjectRef
|
||||
lambda.interceptResumeRef = interceptResumeRef
|
||||
return getReferenceToLambda(invokingContext, descriptor, lambda.name)
|
||||
}
|
||||
|
||||
@@ -201,6 +206,17 @@ private fun getReferenceToLambda(context: TranslationContext, descriptor: Callab
|
||||
}
|
||||
}
|
||||
|
||||
private fun TranslationContext.generateInterceptResumeRef(classDescriptor: ClassDescriptor): JsExpression? {
|
||||
val interceptResumeFunction = classDescriptor.defaultType.findInterceptResume()
|
||||
return if (interceptResumeFunction != null) {
|
||||
val controllerRef = JsNameRef("\$\$controller\$\$", JsLiteral.THIS).apply { coroutineController = true }
|
||||
JsNameRef(getNameForDescriptor(interceptResumeFunction), controllerRef)
|
||||
}
|
||||
else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private data class CapturedArgsParams(val arguments: List<JsExpression> = listOf(), val parameters: List<JsParameter> = listOf())
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user