JS: add support of interceptResume function in coroutine controller

This commit is contained in:
Alexey Andreev
2016-12-05 15:30:04 +03:00
committed by Alexey Andreev
parent 9eff193ea4
commit 060f08a8dd
11 changed files with 132 additions and 18 deletions
@@ -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
@@ -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"
}
@@ -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");
@@ -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,
@@ -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");
@@ -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())
/**