diff --git a/compiler/testData/codegen/box/coroutines/controlFlow/returnFromFinally.kt b/compiler/testData/codegen/box/coroutines/controlFlow/returnFromFinally.kt index 668bcc75e1f..dfff28e80b5 100644 --- a/compiler/testData/codegen/box/coroutines/controlFlow/returnFromFinally.kt +++ b/compiler/testData/codegen/box/coroutines/controlFlow/returnFromFinally.kt @@ -17,10 +17,10 @@ class Controller { } } -fun builder(coroutine c: () -> String): String { +fun builder(c: suspend Controller.() -> String): String { val controller = Controller() - c.startCoroutine(handleResult { - controller.result += "return($value);" + c.startCoroutine(controller, handleResultContinuation { + controller.result += "return($it);" }) return controller.result } diff --git a/compiler/testData/codegen/box/coroutines/multiModule/simple.kt b/compiler/testData/codegen/box/coroutines/multiModule/simple.kt index 5bd5192e380..7bcbacfb037 100644 --- a/compiler/testData/codegen/box/coroutines/multiModule/simple.kt +++ b/compiler/testData/codegen/box/coroutines/multiModule/simple.kt @@ -13,7 +13,7 @@ class Controller { } } -// MODULE: main(controller) +// MODULE: main(controller, support) // FILE: main.kt import lib.* import kotlin.coroutines.* diff --git a/compiler/testData/codegen/box/coroutines/simple.kt b/compiler/testData/codegen/box/coroutines/simple.kt index 164d856e610..4f68d5edb92 100644 --- a/compiler/testData/codegen/box/coroutines/simple.kt +++ b/compiler/testData/codegen/box/coroutines/simple.kt @@ -1,9 +1,7 @@ -// IGNORE_BACKEND: JS // WITH_RUNTIME // WITH_COROUTINES import kotlin.coroutines.* - suspend fun suspendHere(): String = suspendWithCurrentContinuation { x -> x.resume("OK") SUSPENDED diff --git a/compiler/tests-common/org/jetbrains/kotlin/test/KotlinTestUtils.java b/compiler/tests-common/org/jetbrains/kotlin/test/KotlinTestUtils.java index 5e2291826a8..cc5eb0fa14c 100644 --- a/compiler/tests-common/org/jetbrains/kotlin/test/KotlinTestUtils.java +++ b/compiler/tests-common/org/jetbrains/kotlin/test/KotlinTestUtils.java @@ -602,6 +602,7 @@ public class KotlinTestUtils { List testFiles = Lists.newArrayList(); Matcher matcher = FILE_OR_MODULE_PATTERN.matcher(expectedText); + boolean hasModules = false; if (!matcher.find()) { // One file testFiles.add(factory.createFile(null, testFileName, expectedText, directives)); @@ -614,6 +615,7 @@ public class KotlinTestUtils { String moduleName = matcher.group(1); String moduleDependencies = matcher.group(2); if (moduleName != null) { + hasModules = true; module = factory.createModule(moduleName, parseDependencies(moduleDependencies)); } @@ -642,7 +644,8 @@ public class KotlinTestUtils { } if (isDirectiveDefined(expectedText, "WITH_COROUTINES")) { - testFiles.add(factory.createFile(null, + M supportModule = hasModules ? factory.createModule("support", Collections.emptyList()) : null; + testFiles.add(factory.createFile(supportModule, "CoroutineUtil.kt", "import kotlin.coroutines.*\n" + "fun handleResultContinuation(x: (T) -> Unit): Continuation = object: Continuation {\n" + diff --git a/js/js.dart-ast/src/com/google/dart/compiler/backend/js/ast/metadata/metadataProperties.kt b/js/js.dart-ast/src/com/google/dart/compiler/backend/js/ast/metadata/metadataProperties.kt index 12d2587d57f..dd7e46d5ab9 100644 --- a/js/js.dart-ast/src/com/google/dart/compiler/backend/js/ast/metadata/metadataProperties.kt +++ b/js/js.dart-ast/src/com/google/dart/compiler/backend/js/ast/metadata/metadataProperties.kt @@ -20,9 +20,9 @@ package com.google.dart.compiler.backend.js.ast.metadata import com.google.dart.compiler.backend.js.ast.* import com.intellij.psi.PsiElement import org.jetbrains.kotlin.descriptors.CallableDescriptor -import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.descriptors.FunctionDescriptor import org.jetbrains.kotlin.resolve.inline.InlineStrategy +import org.jetbrains.kotlin.types.KotlinType var JsName.staticRef: JsNode? by MetadataProperty(default = null) @@ -64,9 +64,7 @@ var HasMetadata.synthetic: Boolean by MetadataProperty(default = false) var HasMetadata.sideEffects: SideEffectKind by MetadataProperty(default = SideEffectKind.AFFECTS_STATE) -var JsFunction.coroutineType: ClassDescriptor? by MetadataProperty(default = null) - -var JsFunction.controllerType: ClassDescriptor? by MetadataProperty(default = null) +var JsFunction.coroutineType: KotlinType? by MetadataProperty(default = null) /** * Denotes a suspension call-site that is to be processed by coroutine transformer. @@ -86,12 +84,6 @@ var JsInvocation.isPreSuspend: Boolean by MetadataProperty(default = false) */ var JsInvocation.isFakeSuspend: Boolean by MetadataProperty(default = false) -/** - * Denotes a call to coroutine's controller `handleResult` function. - * See coroutine spec for explanation. - */ -var JsInvocation.isHandleResult: Boolean by MetadataProperty(default = false) - /** * Denotes a reference to coroutine's `result` field that contains result of * last suspended invocation. @@ -99,17 +91,25 @@ var JsInvocation.isHandleResult: Boolean by MetadataProperty(default = false) var JsNameRef.coroutineResult by MetadataProperty(default = false) /** - * Denotes a reference to coroutine's `controller` field that contains coroutines's controller + * Denotes a reference to coroutine's `interceptor` field that contains coroutines's interceptor */ var JsNameRef.coroutineController by MetadataProperty(default = false) -var JsFunction.suspendObjectRef: JsExpression? by MetadataProperty(default = null) - -var JsFunction.continuationInterfaceRef: JsExpression? by MetadataProperty(default = null) - var JsName.imported by MetadataProperty(default = false) -var JsFunction.interceptResumeRef: JsExpression? by MetadataProperty(default = null) +var JsFunction.coroutineMetadata: CoroutineMetadata? by MetadataProperty(default = null) + +class CoroutineMetadata( + val doResumeName: JsName, + val stateName: JsName, + val exceptionStateName: JsName, + val finallyPathName: JsName, + val resultName: JsName, + val exceptionName: JsName, + val baseClassRef: JsExpression, + val suspendObjectRef: JsExpression, + val hasController: Boolean +) enum class TypeCheck { TYPEOF, diff --git a/js/js.inliner/src/org/jetbrains/kotlin/js/coroutine/CoroutineBodyTransformer.kt b/js/js.inliner/src/org/jetbrains/kotlin/js/coroutine/CoroutineBodyTransformer.kt index 197f5e23e07..295ba832802 100644 --- a/js/js.inliner/src/org/jetbrains/kotlin/js/coroutine/CoroutineBodyTransformer.kt +++ b/js/js.inliner/src/org/jetbrains/kotlin/js/coroutine/CoroutineBodyTransformer.kt @@ -21,13 +21,8 @@ import com.google.dart.compiler.backend.js.ast.metadata.* import org.jetbrains.kotlin.js.inline.util.collectBreakContinueTargets import org.jetbrains.kotlin.js.translate.utils.JsAstUtils import org.jetbrains.kotlin.utils.DFS -import org.jetbrains.kotlin.utils.singletonOrEmptyList -class CoroutineBodyTransformer( - private val program: JsProgram, - private val context: CoroutineTransformationContext, - private val throwFunctionName: JsName? -) : RecursiveJsVisitor() { +class CoroutineBodyTransformer(private val program: JsProgram,private val context: CoroutineTransformationContext) : RecursiveJsVisitor() { private val entryBlock = context.entryBlock private val globalCatchBlock = context.globalCatchBlock private var currentBlock = entryBlock @@ -254,7 +249,7 @@ class CoroutineBodyTransformer( } if (catchNode != null) { - currentStatements += JsAstUtils.newVar(catchNode.parameter.name, JsNameRef(context.exceptionFieldName, JsLiteral.THIS)) + currentStatements += JsAstUtils.newVar(catchNode.parameter.name, JsNameRef(context.metadata.exceptionName, JsLiteral.THIS)) catchNode.body.statements.forEach { it.accept(this) } if (finallyNode == null) { @@ -283,8 +278,8 @@ class CoroutineBodyTransformer( // for simple `when` statement, we will need to support JsSwitch here private fun generateFinallyExit() { - val finallyPathRef = JsNameRef(context.finallyPathFieldName, JsLiteral.THIS) - val stateRef = JsNameRef(context.stateFieldName, JsLiteral.THIS) + val finallyPathRef = JsNameRef(context.metadata.finallyPathName, JsLiteral.THIS) + val stateRef = JsNameRef(context.metadata.stateName, JsLiteral.THIS) val nextState = JsInvocation(JsNameRef("shift", finallyPathRef)) currentStatements += JsAstUtils.assignment(stateRef, nextState).makeStmt() currentStatements += jump() @@ -310,11 +305,12 @@ class CoroutineBodyTransformer( if (isInFinally) { val returnBlock = CoroutineBlock() jumpWithFinally(0, returnBlock) + val returnFieldRef = JsNameRef(context.returnValueFieldName, JsLiteral.THIS) + currentStatements += JsAstUtils.assignment(returnFieldRef, x.expression).makeStmt() currentStatements += jump() currentBlock = returnBlock - currentStatements += x.expression?.makeStmt().singletonOrEmptyList() - currentStatements += JsReturn() + currentStatements += JsReturn(returnFieldRef.deepCopy()) } else { currentStatements += x @@ -322,16 +318,7 @@ class CoroutineBodyTransformer( } override fun visitThrow(x: JsThrow) { - if (throwFunctionName != null && tryStack.isEmpty()) { - val methodRef = JsNameRef(throwFunctionName, JsNameRef(context.controllerFieldName, JsLiteral.THIS)) - val invocation = JsInvocation(methodRef, x.expression).apply { - source = x.source - } - currentStatements += JsReturn(invocation) - } - else { - currentStatements += x - } + currentStatements += x } private fun handleExpression(expression: JsExpression): JsExpression? { @@ -361,15 +348,14 @@ class CoroutineBodyTransformer( private fun handleSuspend(invocation: JsInvocation) { val invokeExpression = if (invocation.isFakeSuspend) invocation.arguments.getOrNull(0) else invocation - val suspendObjectVar = context.suspendObjectVar - val statements = if (invokeExpression == null || suspendObjectVar == null) { - listOf(JsReturn(invokeExpression)) + val statements = if (invokeExpression == null) { + listOf(JsReturn(context.metadata.suspendObjectRef.deepCopy())) } else { - val resultRef = JsNameRef(context.resultFieldName, JsLiteral.THIS).apply { sideEffects = SideEffectKind.DEPENDS_ON_STATE } + val resultRef = JsNameRef(context.metadata.resultName, JsLiteral.THIS).apply { sideEffects = SideEffectKind.DEPENDS_ON_STATE } val invocationStatement = JsAstUtils.assignment(resultRef, invokeExpression).makeStmt() - val suspendCondition = JsAstUtils.equality(resultRef.deepCopy(), JsAstUtils.pureFqn(suspendObjectVar, null)) - val suspendIfNeeded = JsIf(suspendCondition, JsReturn()) + val suspendCondition = JsAstUtils.equality(resultRef.deepCopy(), context.metadata.suspendObjectRef.deepCopy()) + val suspendIfNeeded = JsIf(suspendCondition, JsReturn(context.metadata.suspendObjectRef.deepCopy())) listOf(invocationStatement, suspendIfNeeded, JsBreak()) } currentStatements += statements diff --git a/js/js.inliner/src/org/jetbrains/kotlin/js/coroutine/CoroutineFunctionTransformer.kt b/js/js.inliner/src/org/jetbrains/kotlin/js/coroutine/CoroutineFunctionTransformer.kt index 68a70547b85..161f52f4784 100644 --- a/js/js.inliner/src/org/jetbrains/kotlin/js/coroutine/CoroutineFunctionTransformer.kt +++ b/js/js.inliner/src/org/jetbrains/kotlin/js/coroutine/CoroutineFunctionTransformer.kt @@ -17,39 +17,23 @@ package org.jetbrains.kotlin.js.coroutine import com.google.dart.compiler.backend.js.ast.* -import com.google.dart.compiler.backend.js.ast.metadata.* -import org.jetbrains.kotlin.descriptors.ClassDescriptor -import org.jetbrains.kotlin.descriptors.FunctionDescriptor +import com.google.dart.compiler.backend.js.ast.metadata.coroutineMetadata import org.jetbrains.kotlin.js.inline.clean.FunctionPostProcessor import org.jetbrains.kotlin.js.inline.util.collectLocalVariables import org.jetbrains.kotlin.js.inline.util.getInnerFunction -import org.jetbrains.kotlin.js.naming.NameSuggestion import org.jetbrains.kotlin.js.translate.context.Namer import org.jetbrains.kotlin.js.translate.utils.JsAstUtils -import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter -class CoroutineFunctionTransformer( - private val program: JsProgram, - private val function: JsFunction, - private val coroutineType: ClassDescriptor, - private val controllerType: ClassDescriptor -) { +class CoroutineFunctionTransformer(private val program: JsProgram, private val function: JsFunction) { private val innerFunction = function.getInnerFunction() private val functionWithBody = innerFunction ?: function private val body = functionWithBody.body private val localVariables = (function.collectLocalVariables() + functionWithBody.collectLocalVariables()).toMutableSet() - private val nameSuggestion = NameSuggestion() private val className = function.scope.parent.declareFreshName("Coroutine\$${function.name}") fun transform(): List { - val throwFunction = controllerType.findFunction("handleException") - val throwName = throwFunction?.let { - val throwId = nameSuggestion.suggest(it)!!.names.last() - function.scope.declareName(throwId) - } - - val context = CoroutineTransformationContext(function.scope, function.suspendObjectRef != null) - val bodyTransformer = CoroutineBodyTransformer(program, context, throwName) + val context = CoroutineTransformationContext(function.scope, function) + val bodyTransformer = CoroutineBodyTransformer(program, context) bodyTransformer.preProcess(body) body.statements.forEach { it.accept(bodyTransformer) } val coroutineBlocks = bodyTransformer.postProcess() @@ -59,9 +43,8 @@ class CoroutineFunctionTransformer( coroutineBlocks.forEach { it.jsBlock.replaceLocalVariables(function.scope, context, localVariables) } val additionalStatements = mutableListOf() - val resumeName = generateDoResume(coroutineBlocks, context, additionalStatements, throwName) - generateContinuationConstructor(context, additionalStatements, bodyTransformer.hasFinallyBlocks, globalCatchBlockIndex) - generateContinuationMethods(context, resumeName, additionalStatements) + generateDoResume(coroutineBlocks, context, additionalStatements) + generateContinuationConstructor(context, additionalStatements, globalCatchBlockIndex) generateCoroutineInstantiation() @@ -71,7 +54,6 @@ class CoroutineFunctionTransformer( private fun generateContinuationConstructor( context: CoroutineTransformationContext, statements: MutableList, - hasFinallyBlocks: Boolean, globalCatchBlockIndex: Int ) { val constructor = JsFunction(function.scope.parent, JsBlock(), "Continuation") @@ -81,29 +63,46 @@ class CoroutineFunctionTransformer( constructor.parameters += innerFunction.parameters.map { JsParameter(it.name) } } - val controllerName = function.scope.declareFreshName("controller") - constructor.parameters += JsParameter(controllerName) + val controllerName = if (context.metadata.hasController) { + function.scope.declareFreshName("controller").apply { constructor.parameters += JsParameter(this) } + } + else { + null + } + + val interceptorName = function.scope.declareFreshName("interceptor") + constructor.parameters += JsParameter(interceptorName) val parameterNames = (function.parameters.map { it.name } + innerFunction?.parameters?.map { it.name }.orEmpty()).toSet() constructor.body.statements.run { - assignToField(context.stateFieldName, program.getNumberLiteral(0)) - assignToField(context.exceptionStateName, program.getNumberLiteral(globalCatchBlockIndex)) - if (hasFinallyBlocks) { - assignToField(context.finallyPathFieldName, JsLiteral.NULL) + val baseClass = context.metadata.baseClassRef.deepCopy() + this += JsInvocation(Namer.getFunctionCallRef(baseClass), JsLiteral.THIS, interceptorName.makeRef()).makeStmt() + if (controllerName != null) { + assignToField(context.controllerFieldName, controllerName.makeRef()) } - assignToField(context.controllerFieldName, controllerName.makeRef()) + assignToField(context.metadata.exceptionStateName, program.getNumberLiteral(globalCatchBlockIndex)) for (localVariable in localVariables) { val value = if (localVariable !in parameterNames) JsLiteral.NULL else localVariable.makeRef() assignToField(function.scope.getFieldName(localVariable), value) } } - statements.addAll(0, listOf(constructor.makeStmt(), generateCoroutineMetadata(constructor.name))) + statements.addAll(0, listOf(constructor.makeStmt(), generateCoroutineMetadata(constructor.name)) + + generateCoroutinePrototype(constructor.name)) + } + + private fun generateCoroutinePrototype(constructorName: JsName): List { + val prototype = JsAstUtils.prototypeOf(JsNameRef(constructorName)) + + val baseClass = Namer.createObjectWithPrototypeFrom(function.coroutineMetadata!!.baseClassRef.deepCopy()) + val assignPrototype = JsAstUtils.assignment(prototype, baseClass) + val assignConstructor = JsAstUtils.assignment(JsNameRef("constructor", prototype.deepCopy()), JsNameRef(constructorName)) + return listOf(assignPrototype.makeStmt(), assignConstructor.makeStmt()) } private fun generateCoroutineMetadata(constructorName: JsName): JsStatement { - val interfaceRef = function.continuationInterfaceRef!!.deepCopy() + val baseClassRefRef = function.coroutineMetadata!!.baseClassRef.deepCopy() val metadataObject = JsObjectLiteral(true) metadataObject.propertyInitializers += JsPropertyInitializer( @@ -111,89 +110,31 @@ class CoroutineFunctionTransformer( metadataObject.propertyInitializers += JsPropertyInitializer( JsNameRef("classIndex"), JsInvocation(JsNameRef("newClassIndex", Namer.KOTLIN_NAME))) metadataObject.propertyInitializers += JsPropertyInitializer(JsNameRef("simpleName"), JsLiteral.NULL) - metadataObject.propertyInitializers += JsPropertyInitializer(JsNameRef("baseClasses"), JsArrayLiteral(listOf(interfaceRef))) + metadataObject.propertyInitializers += JsPropertyInitializer(JsNameRef("baseClasses"), JsArrayLiteral(listOf(baseClassRefRef))) return JsAstUtils.assignment(JsNameRef(Namer.METADATA, constructorName.makeRef()), metadataObject).makeStmt() } - private fun generateContinuationMethods( - context: CoroutineTransformationContext, - doResumeName: JsName, - statements: MutableList - ) { - 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, - name: String, - parameterName: String, - additionalArgs: List - ) { - val resumeDescriptor = coroutineType.findFunction(name)!! - val resumeId = nameSuggestion.suggest(resumeDescriptor)!!.names.last() - val resumeName = function.scope.declareName(resumeId) - - val resumeFunction = JsFunction(function.scope.parent, JsBlock(), resumeDescriptor.toString()) - val resumeParameter = resumeFunction.scope.declareFreshName(parameterName) - resumeFunction.parameters += JsParameter(resumeParameter) - - resumeFunction.body.statements.apply { - val interceptResumeRef = function.interceptResumeRef - val invocation = JsInvocation(JsNameRef(doResumeName, JsLiteral.THIS), additionalArgs + resumeParameter.makeRef()) - 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) - } - } - private fun generateDoResume( coroutineBlocks: List, context: CoroutineTransformationContext, - statements: MutableList, - throwName: JsName? - ): JsName { + statements: MutableList + ) { val resumeFunction = JsFunction(function.scope.parent, JsBlock(), "resume function") - val resumeParameter = resumeFunction.scope.declareFreshName("data") - val resumeException = resumeFunction.scope.declareFreshName("exception") - resumeFunction.parameters += listOf(JsParameter(resumeParameter), JsParameter(resumeException)) - val coroutineBody = generateCoroutineBody(context, coroutineBlocks, throwName, resumeException) + val coroutineBody = generateCoroutineBody(context, coroutineBlocks) functionWithBody.body.statements.clear() resumeFunction.body.statements.apply { - assignToField(context.resultFieldName, resumeParameter.makeRef()) - if (context.suspendObjectVar != null) { - add(JsAstUtils.newVar(context.suspendObjectVar!!, function.suspendObjectRef!!.deepCopy()).apply { - synthetic = true - }) - } this += coroutineBody } - val resumeName = function.scope.parent.declareFreshName("doResume") + val resumeName = function.coroutineMetadata!!.doResumeName statements.apply { assignToPrototype(resumeName, resumeFunction) } FunctionPostProcessor(resumeFunction).apply() - - return resumeName } private fun generateCoroutineInstantiation() { @@ -203,36 +144,36 @@ class CoroutineFunctionTransformer( instantiation.arguments += innerFunction.parameters.map { it.name.makeRef() } } - instantiation.arguments += JsLiteral.THIS + if (function.coroutineMetadata!!.hasController) { + instantiation.arguments += JsLiteral.THIS + } + + val interceptorParamName = functionWithBody.scope.declareFreshName("interceptor") + functionWithBody.parameters += JsParameter(interceptorParamName) + + instantiation.arguments += interceptorParamName.makeRef() functionWithBody.body.statements += JsReturn(instantiation) } private fun generateCoroutineBody( context: CoroutineTransformationContext, - blocks: List, - throwName: JsName?, - exceptionName: JsName + blocks: List ): List { val indexOfGlobalCatch = blocks.indexOf(context.globalCatchBlock) - val stateRef = JsNameRef(context.stateFieldName, JsLiteral.THIS) + val stateRef = JsNameRef(context.metadata.stateName, JsLiteral.THIS) val isFromGlobalCatch = JsAstUtils.equality(stateRef, program.getNumberLiteral(indexOfGlobalCatch)) val catch = JsCatch(functionWithBody.scope, "e") val continueWithException = JsBlock( - JsAstUtils.assignment(stateRef.deepCopy(), JsNameRef(context.exceptionStateName, JsLiteral.THIS)).makeStmt(), - JsAstUtils.assignment(JsNameRef(context.exceptionFieldName, JsLiteral.THIS), catch.parameter.name.makeRef()).makeStmt() + JsAstUtils.assignment(stateRef.deepCopy(), JsNameRef(context.metadata.exceptionStateName, JsLiteral.THIS)).makeStmt(), + JsAstUtils.assignment(JsNameRef(context.metadata.exceptionName, JsLiteral.THIS), + catch.parameter.name.makeRef()).makeStmt() ) catch.body = JsBlock(JsIf(isFromGlobalCatch, JsThrow(catch.parameter.name.makeRef()), continueWithException)) - val throwResultRef = JsNameRef(context.exceptionFieldName, JsLiteral.THIS) - if (throwName != null) { - val throwMethodRef = JsNameRef(throwName, JsNameRef(context.controllerFieldName, JsLiteral.THIS)) - context.globalCatchBlock.statements += JsReturn(JsInvocation(throwMethodRef.deepCopy(), throwResultRef)) - } - else { - context.globalCatchBlock.statements += JsThrow(throwResultRef) - } + val throwResultRef = JsNameRef(context.metadata.exceptionName, JsLiteral.THIS) + context.globalCatchBlock.statements += JsThrow(throwResultRef) val cases = blocks.withIndex().map { (index, block) -> JsCase().apply { @@ -243,15 +184,7 @@ class CoroutineFunctionTransformer( val switchStatement = JsSwitch(stateRef.deepCopy(), cases) val loop = JsDoWhile(JsLiteral.TRUE, JsTry(JsBlock(switchStatement), catch, null)) - val testExceptionPassed = JsAstUtils.notOptimized( - JsAstUtils.typeOfIs(exceptionName.makeRef(), program.getStringLiteral("undefined"))) - val stateToException = JsAstUtils.assignment( - JsNameRef(context.stateFieldName, JsLiteral.THIS), - JsNameRef(context.exceptionStateName, JsLiteral.THIS)) - val exceptionToResult = JsAstUtils.assignment(JsNameRef(context.exceptionFieldName, JsLiteral.THIS), exceptionName.makeRef()) - val throwExceptionIfNeeded = JsIf(testExceptionPassed, JsBlock(stateToException.makeStmt(), exceptionToResult.makeStmt())) - - return listOf(throwExceptionIfNeeded, loop) + return listOf(loop) } private fun JsBlock.collectAdditionalLocalVariables() { @@ -263,13 +196,6 @@ class CoroutineFunctionTransformer( }) } - - private fun ClassDescriptor.findFunction(name: String): FunctionDescriptor? { - val functions = unsubstitutedMemberScope.getContributedDescriptors(DescriptorKindFilter.FUNCTIONS) - .filter { it.name.asString() == name } - return functions.mapNotNull { it as? FunctionDescriptor }.firstOrNull { it.kind.isReal } - } - private fun MutableList.assignToField(fieldName: JsName, value: JsExpression) { this += JsAstUtils.assignment(JsNameRef(fieldName, JsLiteral.THIS), value).makeStmt() } diff --git a/js/js.inliner/src/org/jetbrains/kotlin/js/coroutine/CoroutinePasses.kt b/js/js.inliner/src/org/jetbrains/kotlin/js/coroutine/CoroutinePasses.kt index 4bc6c2fe379..9043c778700 100644 --- a/js/js.inliner/src/org/jetbrains/kotlin/js/coroutine/CoroutinePasses.kt +++ b/js/js.inliner/src/org/jetbrains/kotlin/js/coroutine/CoroutinePasses.kt @@ -112,7 +112,7 @@ fun List.replaceCoroutineFlowStatements(context: CoroutineTransf override fun endVisit(x: JsDebugger, ctx: JsContext) { val target = x.targetBlock if (target != null) { - val lhs = JsNameRef(context.stateFieldName, JsLiteral.THIS) + val lhs = JsNameRef(context.metadata.stateName, JsLiteral.THIS) val rhs = program.getNumberLiteral(blockIndexes[target]!!) ctx.replaceMe(JsExpressionStatement(JsAstUtils.assignment(lhs, rhs)).apply { targetBlock = true @@ -121,7 +121,7 @@ fun List.replaceCoroutineFlowStatements(context: CoroutineTransf val exceptionTarget = x.targetExceptionBlock if (exceptionTarget != null) { - val lhs = JsNameRef(context.exceptionStateName, JsLiteral.THIS) + val lhs = JsNameRef(context.metadata.exceptionStateName, JsLiteral.THIS) val rhs = program.getNumberLiteral(blockIndexes[exceptionTarget]!!) ctx.replaceMe(JsExpressionStatement(JsAstUtils.assignment(lhs, rhs)).apply { targetExceptionBlock = true @@ -131,7 +131,7 @@ fun List.replaceCoroutineFlowStatements(context: CoroutineTransf val finallyPath = x.finallyPath if (finallyPath != null) { if (finallyPath.isNotEmpty()) { - val lhs = JsNameRef(context.finallyPathFieldName, JsLiteral.THIS) + val lhs = JsNameRef(context.metadata.finallyPathName, JsLiteral.THIS) val rhs = JsArrayLiteral(finallyPath.map { program.getNumberLiteral(blockIndexes[it]!!) }) ctx.replaceMe(JsExpressionStatement(JsAstUtils.assignment(lhs, rhs)).apply { this.finallyPath = true @@ -209,7 +209,7 @@ fun JsBlock.replaceSpecialReferences(context: CoroutineTransformationContext) { } x.coroutineResult -> { - ctx.replaceMe(JsNameRef(context.resultFieldName, x.qualifier).apply { + ctx.replaceMe(JsNameRef(context.metadata.resultName, x.qualifier).apply { sideEffects = SideEffectKind.DEPENDS_ON_STATE }) } diff --git a/js/js.inliner/src/org/jetbrains/kotlin/js/coroutine/CoroutineTransformationContext.kt b/js/js.inliner/src/org/jetbrains/kotlin/js/coroutine/CoroutineTransformationContext.kt index f6c9bd0390f..2490780e151 100644 --- a/js/js.inliner/src/org/jetbrains/kotlin/js/coroutine/CoroutineTransformationContext.kt +++ b/js/js.inliner/src/org/jetbrains/kotlin/js/coroutine/CoroutineTransformationContext.kt @@ -16,16 +16,14 @@ package org.jetbrains.kotlin.js.coroutine +import com.google.dart.compiler.backend.js.ast.JsFunction import com.google.dart.compiler.backend.js.ast.JsScope +import com.google.dart.compiler.backend.js.ast.metadata.coroutineMetadata -class CoroutineTransformationContext(private val scope: JsScope, private val stackUnwinding: Boolean) { +class CoroutineTransformationContext(private val scope: JsScope, function: JsFunction) { val entryBlock = CoroutineBlock() val globalCatchBlock = CoroutineBlock() - val resultFieldName by lazy { scope.declareFreshName("\$result") } - val exceptionFieldName by lazy { scope.declareFreshName("\$exception") } - val stateFieldName by lazy { scope.declareFreshName("\$state") } + val metadata = function.coroutineMetadata!! val controllerFieldName by lazy { scope.declareFreshName("\$controller") } - val exceptionStateName by lazy { scope.declareFreshName("\$exceptionState") } - val finallyPathFieldName by lazy { scope.declareFreshName("\$finallyPath") } - val suspendObjectVar by lazy { if (stackUnwinding) scope.declareFreshName("\$suspendObject") else null } + val returnValueFieldName by lazy { scope.declareFreshName("\$returnValue") } } \ No newline at end of file diff --git a/js/js.inliner/src/org/jetbrains/kotlin/js/coroutine/CoroutineTransformer.kt b/js/js.inliner/src/org/jetbrains/kotlin/js/coroutine/CoroutineTransformer.kt index 056f9cfd808..0ea978c8120 100644 --- a/js/js.inliner/src/org/jetbrains/kotlin/js/coroutine/CoroutineTransformer.kt +++ b/js/js.inliner/src/org/jetbrains/kotlin/js/coroutine/CoroutineTransformer.kt @@ -17,7 +17,6 @@ package org.jetbrains.kotlin.js.coroutine import com.google.dart.compiler.backend.js.ast.* -import com.google.dart.compiler.backend.js.ast.metadata.controllerType import com.google.dart.compiler.backend.js.ast.metadata.coroutineType class CoroutineTransformer(private val program: JsProgram) : JsVisitorWithContextImpl() { @@ -32,7 +31,7 @@ class CoroutineTransformer(private val program: JsProgram) : JsVisitorWithContex override fun endVisit(x: JsFunction, ctx: JsContext) { val coroutineType = x.coroutineType if (coroutineType != null) { - additionalStatements += CoroutineFunctionTransformer(program, x, coroutineType, x.controllerType!!).transform() + additionalStatements += CoroutineFunctionTransformer(program, x).transform() } } } diff --git a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/clean/resolveTemporaryNames.kt b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/clean/resolveTemporaryNames.kt index 4a2640444a3..d691b6f89bc 100644 --- a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/clean/resolveTemporaryNames.kt +++ b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/clean/resolveTemporaryNames.kt @@ -33,9 +33,7 @@ package org.jetbrains.kotlin.js.inline.clean import com.google.dart.compiler.backend.js.ast.* -import com.google.dart.compiler.backend.js.ast.metadata.continuationInterfaceRef -import com.google.dart.compiler.backend.js.ast.metadata.interceptResumeRef -import com.google.dart.compiler.backend.js.ast.metadata.suspendObjectRef +import com.google.dart.compiler.backend.js.ast.metadata.coroutineMetadata import org.jetbrains.kotlin.js.inline.util.collectReferencedTemporaryNames fun JsNode.resolveTemporaryNames() { @@ -59,9 +57,10 @@ fun JsNode.resolveTemporaryNames() { } override fun visitFunction(x: JsFunction) { - x.continuationInterfaceRef?.let { accept(it) } - x.interceptResumeRef?.let { accept(it) } - x.suspendObjectRef?.let { accept(it) } + x.coroutineMetadata?.apply { + accept(suspendObjectRef) + accept(baseClassRef) + } super.visitFunction(x) } }) @@ -86,7 +85,7 @@ private fun Map>.resolveNames(knownNames: Set): Ma return replacements } -private class ScopeCollector() : RecursiveJsVisitor() { +private class ScopeCollector : RecursiveJsVisitor() { val scopeTree = mutableMapOf>() override fun visitCatch(x: JsCatch) { diff --git a/js/js.libraries/src/core/coroutines.kt b/js/js.libraries/src/core/coroutines.kt new file mode 100644 index 00000000000..bfc481a75f6 --- /dev/null +++ b/js/js.libraries/src/core/coroutines.kt @@ -0,0 +1,162 @@ +/* + * Copyright 2010-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package kotlin.coroutines + +/** + * A strategy to intercept resumptions inside coroutine. + * Interceptor may shift resumption into another execution frame by scheduling asynchronous execution + * in this or another thread. + */ +@SinceKotlin("1.1") +public interface ResumeInterceptor { + /** + * Intercepts [Continuation.resume] invocation. + * This function must either return `false` or return `true` and invoke `continuation.resume(data)` asynchronously. + */ + public fun

interceptResume(data: P, continuation: Continuation

): Boolean = false + + /** + * Intercepts [Continuation.resumeWithException] invocation. + * This function must either return `false` or return `true` and invoke `continuation.resumeWithException(exception)` asynchronously. + */ + public fun interceptResumeWithException(exception: Throwable, continuation: Continuation<*>): Boolean = false +} + +/** + * Creates coroutine with receiver type [R] and result type [T]. + * This function creates a new, fresh instance of suspendable computation every time it is invoked. + * To start executing the created coroutine, invoke `resume(Unit)` on the returned [Continuation] instance. + * The result of the coroutine's execution is provided via invocation of [resultHandler]. + */ +@SinceKotlin("1.1") +public fun (suspend R.() -> T).createCoroutine( + receiver: R, + resultHandler: Continuation, + resumeInterceptor: ResumeInterceptor? = null +): Continuation = this.asDynamic().call(receiver, withInterceptor(resultHandler, resumeInterceptor)) + +/** + * Starts coroutine with receiver type [R] and result type [T]. + * This function creates and start a new, fresh instance of suspendable computation every time it is invoked. + * The result of the coroutine's execution is provided via invocation of [resultHandler]. + */ +@SinceKotlin("1.1") +@Suppress("UNCHECKED_CAST") +public fun (suspend R.() -> T).startCoroutine( + receiver: R, + resultHandler: Continuation, + resumeInterceptor: ResumeInterceptor? = null +) { + createCoroutine(receiver, resultHandler, resumeInterceptor).resume(Unit) +} + +/** + * Creates coroutine without receiver and with result type [T]. + * This function creates a new, fresh instance of suspendable computation every time it is invoked. + * To start executing the created coroutine, invoke `resume(Unit)` on the returned [Continuation] instance. + * The result of the coroutine's execution is provided via invocation of [resultHandler]. + * An optional [resumeInterceptor] may be specified to intercept resumes at suspension points inside the coroutine. + */ +@SinceKotlin("1.1") +public fun (suspend () -> T).createCoroutine( + resultHandler: Continuation, + resumeInterceptor: ResumeInterceptor? = null +): Continuation = this.asDynamic()(withInterceptor(resultHandler, resumeInterceptor)) + +/** + * Starts coroutine without receiver and with result type [T]. + * This function creates and start a new, fresh instance of suspendable computation every time it is invoked. + * The result of the coroutine's execution is provided via invocation of [resultHandler]. + * An optional [resumeInterceptor] may be specified to intercept resumes at suspension points inside the coroutine. + */ +@SinceKotlin("1.1") +@Suppress("UNCHECKED_CAST") +public fun (suspend () -> T).startCoroutine( + resultHandler: Continuation, + resumeInterceptor: ResumeInterceptor? = null +) { + createCoroutine(resultHandler, resumeInterceptor).resume(Unit) +} + +// ------- internal stuff ------- + +private fun withInterceptor(resultHandler: Continuation, resumeInterceptor: ResumeInterceptor?): Continuation { + val finalResumeInterceptor = resumeInterceptor ?: object : ResumeInterceptor { + override fun

interceptResume(data: P, continuation: Continuation

) = false + + override fun interceptResumeWithException(exception: Throwable, continuation: Continuation<*>) = false + } + return object : Continuation by resultHandler, ResumeInterceptor by finalResumeInterceptor {} +} + +@JsName("CoroutineImpl") +internal abstract class CoroutineImpl(private val resultContinuation: Continuation) : Continuation { + protected var state = 0 + protected var exceptionState = 0 + protected var result: Any? = null + protected var exception: Throwable? = null + protected var finallyPath: Array? = null + private val resumeInterceptor = resultContinuation as? ResumeInterceptor + + override fun resume(data: Any?) { + if (resumeInterceptor != null && (state and INTERCEPTING) == 0) { + state = state or INTERCEPTING + if (resumeInterceptor.interceptResume(data, this)) { + state = state and INTERCEPTING.inv() + return + } + } + + state = state and INTERCEPTING.inv() + this.result = data + try { + val result = doResume() + if (result != SUSPENDED) { + resultContinuation.resume(result) + } + } + catch (e: Throwable) { + resultContinuation.resumeWithException(e) + } + } + + override fun resumeWithException(exception: Throwable) { + if (resumeInterceptor != null && (state and INTERCEPTING) == 0) { + state = state or INTERCEPTING + if (resumeInterceptor.interceptResumeWithException(exception, this)) { + state = state and INTERCEPTING.inv() + return + } + } + + state = exceptionState + this.exception = exception + try { + val result = doResume() + if (result != SUSPENDED) { + resultContinuation.resume(result) + } + } + catch (e: Throwable) { + resultContinuation.resumeWithException(e) + } + } + + protected abstract fun doResume(): Any? +} + +private const val INTERCEPTING = 1 shl 31 \ No newline at end of file diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/JsCodegenBoxTestGenerated.java b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/JsCodegenBoxTestGenerated.java index 0afe6ef1df0..df927cff29a 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/JsCodegenBoxTestGenerated.java +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/JsCodegenBoxTestGenerated.java @@ -5505,13 +5505,7 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest { @TestMetadata("simple.kt") public void testSimple() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/simple.kt"); - try { - doTest(fileName); - } - catch (Throwable ignore) { - return; - } - throw new AssertionError("Looks like this test can be unmuted. Remove IGNORE_BACKEND directive for that."); + doTest(fileName); } @TestMetadata("simpleException.kt") diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/Namer.java b/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/Namer.java index ff4e6338c2d..e025607666e 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/Namer.java +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/Namer.java @@ -174,8 +174,8 @@ public final class Namer { } @NotNull - public static JsInvocation createObjectWithPrototypeFrom(JsNameRef referenceToClass) { - return new JsInvocation(JS_OBJECT_CREATE_FUNCTION, JsAstUtils.prototypeOf(referenceToClass)); + public static JsInvocation createObjectWithPrototypeFrom(@NotNull JsExpression referenceToClass) { + return new JsInvocation(JS_OBJECT_CREATE_FUNCTION.deepCopy(), JsAstUtils.prototypeOf(referenceToClass)); } @NotNull diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/TranslationContext.java b/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/TranslationContext.java index 2dcbefd803a..470623da10e 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/TranslationContext.java +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/TranslationContext.java @@ -21,6 +21,7 @@ import com.google.dart.compiler.backend.js.ast.metadata.MetadataProperties; import com.intellij.psi.PsiElement; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.jetbrains.kotlin.coroutines.CoroutineUtilKt; import org.jetbrains.kotlin.descriptors.*; import org.jetbrains.kotlin.descriptors.annotations.Annotations; import org.jetbrains.kotlin.descriptors.impl.LocalVariableDescriptor; diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/ExpressionVisitor.java b/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/ExpressionVisitor.java index 7bb21af4924..10ee06235b8 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/ExpressionVisitor.java +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/ExpressionVisitor.java @@ -37,7 +37,6 @@ import org.jetbrains.kotlin.js.translate.operation.UnaryOperationTranslator; import org.jetbrains.kotlin.js.translate.reference.*; import org.jetbrains.kotlin.js.translate.utils.BindingUtils; import org.jetbrains.kotlin.js.translate.utils.JsAstUtils; -import org.jetbrains.kotlin.js.translate.utils.TranslationUtils; import org.jetbrains.kotlin.js.translate.utils.UtilsKt; import org.jetbrains.kotlin.psi.*; import org.jetbrains.kotlin.psi.psiUtil.PsiUtilsKt; @@ -128,11 +127,7 @@ public final class ExpressionVisitor extends TranslatorVisitor { } JsReturn jsReturn; - JsExpression handleResultExpression = TranslationUtils.tryTranslateHandleResult(context, jetReturnExpression, returned); - if (handleResultExpression != null) { - jsReturn = new JsReturn(handleResultExpression); - } - else if (returned == null) { + if (returned == null) { jsReturn = new JsReturn(null); } else { @@ -451,13 +446,13 @@ public final class ExpressionVisitor extends TranslatorVisitor { @Override @NotNull public JsNode visitLambdaExpression(@NotNull KtLambdaExpression expression, @NotNull TranslationContext context) { - return new LiteralFunctionTranslator(context).translate(expression.getFunctionLiteral(), null, null); + return new LiteralFunctionTranslator(context).translate(expression.getFunctionLiteral(), null); } @Override @NotNull public JsNode visitNamedFunction(@NotNull KtNamedFunction expression, @NotNull TranslationContext context) { - JsExpression alias = new LiteralFunctionTranslator(context).translate(expression, null, null); + JsExpression alias = new LiteralFunctionTranslator(context).translate(expression, null); FunctionDescriptor descriptor = getFunctionDescriptor(context.bindingContext(), expression); JsNameRef nameRef = (JsNameRef) ReferenceTranslator.translateAsValueReference(descriptor, context); diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/LiteralFunctionTranslator.kt b/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/LiteralFunctionTranslator.kt index 67478558bb0..e20bbbfc76f 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/LiteralFunctionTranslator.kt +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/LiteralFunctionTranslator.kt @@ -18,7 +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.builtins.KotlinBuiltIns +import org.jetbrains.kotlin.builtins.isBuiltinExtensionFunctionalType import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.descriptors.impl.ValueParameterDescriptorImpl import org.jetbrains.kotlin.js.descriptorUtils.isCoroutineLambda @@ -32,25 +32,24 @@ import org.jetbrains.kotlin.js.translate.reference.ReferenceTranslator import org.jetbrains.kotlin.js.translate.utils.BindingUtils.getFunctionDescriptor import org.jetbrains.kotlin.js.translate.utils.FunctionBodyTranslator.translateFunctionBody import org.jetbrains.kotlin.js.translate.utils.JsAstUtils +import org.jetbrains.kotlin.js.translate.utils.TranslationUtils import org.jetbrains.kotlin.js.translate.utils.TranslationUtils.simpleReturnFunction -import org.jetbrains.kotlin.name.ClassId import org.jetbrains.kotlin.name.FqName -import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.psi.KtDeclarationWithBody import org.jetbrains.kotlin.psi.KtParameter import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils import org.jetbrains.kotlin.resolve.inline.InlineUtil -import org.jetbrains.kotlin.serialization.deserialization.findClassAcrossModuleDependencies +import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter +import org.jetbrains.kotlin.types.KotlinType class LiteralFunctionTranslator(context: TranslationContext) : AbstractTranslator(context) { companion object { - private val SUSPEND_FQ_NAME = "kotlin.coroutines.Suspend" + private val SUSPEND_FQ_NAME = FqName("kotlin.coroutines.SUSPENDED") } fun translate( declaration: KtDeclarationWithBody, - continuationType: ClassDescriptor? = null, - controllerType: ClassDescriptor? = null + continuationType: KotlinType? = null ): JsExpression { val invokingContext = context() val descriptor = getFunctionDescriptor(invokingContext.bindingContext(), declaration) @@ -88,41 +87,50 @@ class LiteralFunctionTranslator(context: TranslationContext) : AbstractTranslato invokingContext.getInnerNameForDescriptor(descriptor) } - val suspendObjectRef = if (descriptor.isCoroutineLambda && KotlinBuiltIns.isUnit(descriptor.returnType!!)) { - val suspendObjectDescriptor = context().currentModule.builtIns.getBuiltInClassByFqName(FqName(SUSPEND_FQ_NAME)) - ReferenceTranslator.translateAsValueReference(suspendObjectDescriptor, context()) - } - else { - null - } - if (tracker.hasCapturedExceptContaining()) { val lambdaCreator = simpleReturnFunction(invokingContext.scope(), lambda) lambdaCreator.name = invokingContext.getInnerNameForDescriptor(descriptor) lambdaCreator.isLocal = true - lambdaCreator.coroutineType = continuationType - lambdaCreator.controllerType = controllerType if (!isRecursive || descriptor.isCoroutineLambda) { lambda.name = null } lambdaCreator.name.staticRef = lambdaCreator - lambdaCreator.continuationInterfaceRef = invokingContext.getContinuationInterfaceReference() - lambdaCreator.suspendObjectRef = suspendObjectRef + lambdaCreator.fillCoroutineMetadata(invokingContext, continuationType) return lambdaCreator.withCapturedParameters(descriptor, descriptor.wrapContextForCoroutineIfNecessary(functionContext), invokingContext) } lambda.isLocal = true - lambda.coroutineType = continuationType - lambda.controllerType = controllerType invokingContext.addDeclarationStatement(lambda.makeStmt()) + lambda.fillCoroutineMetadata(invokingContext, continuationType) lambda.name.staticRef = lambda - lambda.continuationInterfaceRef = invokingContext.getContinuationInterfaceReference() - lambda.suspendObjectRef = suspendObjectRef return getReferenceToLambda(invokingContext, descriptor, lambda.name) } + fun JsFunction.fillCoroutineMetadata(context: TranslationContext, continuationType: KotlinType?) { + if (continuationType == null) return + + val suspendObjectDescriptor = context().currentModule.getPackage(SUSPEND_FQ_NAME.parent()) + .memberScope.getContributedDescriptors(DescriptorKindFilter.CALLABLES) + .first { it is PropertyDescriptor && it.name == SUSPEND_FQ_NAME.shortName() } + + val coroutineBaseClassRef = ReferenceTranslator.translateAsTypeReference(TranslationUtils.getCoroutineBaseClass(context), context) + + coroutineMetadata = CoroutineMetadata( + doResumeName = context.getNameForDescriptor(TranslationUtils.getCoroutineDoResumeFunction(context)), + suspendObjectRef = ReferenceTranslator.translateAsValueReference(suspendObjectDescriptor, context()), + baseClassRef = coroutineBaseClassRef, + stateName = context.getNameForDescriptor(TranslationUtils.getCoroutineProperty(context, "state")), + exceptionStateName = context.getNameForDescriptor(TranslationUtils.getCoroutineProperty(context, "exceptionState")), + finallyPathName = context.getNameForDescriptor(TranslationUtils.getCoroutineProperty(context, "finallyPath")), + resultName = context.getNameForDescriptor(TranslationUtils.getCoroutineProperty(context, "result")), + exceptionName = context.getNameForDescriptor(TranslationUtils.getCoroutineProperty(context, "exception")), + hasController = continuationType.isBuiltinExtensionFunctionalType + ) + coroutineType = continuationType + } + fun ValueParameterDescriptorImpl.WithDestructuringDeclaration.translate(context: TranslationContext): JsVars { val destructuringDeclaration = (DescriptorToSourceUtils.descriptorToDeclaration(this) as? KtParameter)?.destructuringDeclaration @@ -133,12 +141,6 @@ class LiteralFunctionTranslator(context: TranslationContext) : AbstractTranslato } } -private fun TranslationContext.getContinuationInterfaceReference(): JsExpression { - val coroutineClassId = ClassId.topLevel(KotlinBuiltIns.COROUTINES_PACKAGE_FQ_NAME.child(Name.identifier("Continuation"))) - val classDescriptor = currentModule.findClassAcrossModuleDependencies(coroutineClassId)!! - return ReferenceTranslator.translateAsTypeReference(classDescriptor, this) -} - private fun CallableMemberDescriptor.wrapContextForCoroutineIfNecessary(context: TranslationContext): TranslationContext { return if (isCoroutineLambda) context.innerContextWithDescriptorsAliased(mapOf(this to JsLiteral.THIS)) else context } @@ -254,7 +256,7 @@ private fun moveCapturedLocalInside(capturingFunction: JsFunction, capturedName: val alias = JsInvocation(localFunAlias.qualifier, aliasCallArguments) declareAliasInsideFunction(capturingFunction, capturedName, alias) - val capturedParameters = freshNames.map {JsParameter(it)} + val capturedParameters = freshNames.map(::JsParameter) return CapturedArgsParams(capturedArgs, capturedParameters) } diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/reference/CallArgumentTranslator.kt b/js/js.translator/src/org/jetbrains/kotlin/js/translate/reference/CallArgumentTranslator.kt index f2469d75111..d0333f0b8e0 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/reference/CallArgumentTranslator.kt +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/reference/CallArgumentTranslator.kt @@ -20,7 +20,8 @@ import com.google.dart.compiler.backend.js.ast.* import com.google.dart.compiler.backend.js.ast.metadata.SideEffectKind import com.google.dart.compiler.backend.js.ast.metadata.sideEffects import com.intellij.util.SmartList -import org.jetbrains.kotlin.descriptors.ClassDescriptor +import org.jetbrains.kotlin.coroutines.isSuspendLambda +import org.jetbrains.kotlin.descriptors.CallableDescriptor import org.jetbrains.kotlin.descriptors.FunctionDescriptor import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor @@ -31,10 +32,8 @@ import org.jetbrains.kotlin.js.translate.expression.LiteralFunctionTranslator import org.jetbrains.kotlin.js.translate.expression.PatternTranslator import org.jetbrains.kotlin.js.translate.general.AbstractTranslator import org.jetbrains.kotlin.js.translate.general.Translation -import org.jetbrains.kotlin.js.translate.utils.AnnotationsUtils -import org.jetbrains.kotlin.js.translate.utils.JsAstUtils -import org.jetbrains.kotlin.js.translate.utils.TranslationUtils -import org.jetbrains.kotlin.js.translate.utils.getReferenceToJsClass +import org.jetbrains.kotlin.js.translate.utils.* +import org.jetbrains.kotlin.psi.KtExpression import org.jetbrains.kotlin.psi.KtLambdaExpression import org.jetbrains.kotlin.psi.KtPsiUtil import org.jetbrains.kotlin.psi.ValueArgument @@ -206,19 +205,21 @@ class CallArgumentTranslator private constructor( val parenthesizedArgumentExpression = valueArguments[0].getArgumentExpression()!! val argumentExpression = KtPsiUtil.deparenthesize(parenthesizedArgumentExpression) - result += if (/*parameterDescriptor.isCoroutine=*/false && argumentExpression is KtLambdaExpression) { - val continuationType = parameterDescriptor.type.arguments.last().type - val continuationDescriptor = continuationType.constructor.declarationDescriptor as ClassDescriptor - val controllerType = parameterDescriptor.type.arguments[0].type - val controllerDescriptor = controllerType.constructor.declarationDescriptor as ClassDescriptor - LiteralFunctionTranslator(context).translate( - argumentExpression.functionLiteral, continuationDescriptor, controllerDescriptor) + val lambdaDescriptor = argumentExpression?.let { context.extractLambda(it) } + if (lambdaDescriptor?.isSuspendLambda == true) { + val lambdaExpression = (argumentExpression as KtLambdaExpression).functionLiteral + result += LiteralFunctionTranslator(context).translate(lambdaExpression, parameterDescriptor.type) } else { - Translation.translateAsExpression(parenthesizedArgumentExpression, context) + result += Translation.translateAsExpression(parenthesizedArgumentExpression, context) } } + private fun TranslationContext.extractLambda(expression: KtExpression): CallableDescriptor? { + val lambdaExpression = expression as? KtLambdaExpression ?: return null + return BindingUtils.getFunctionDescriptor(bindingContext(), lambdaExpression.functionLiteral) + } + private fun translateVarargArgument(arguments: List, result: MutableList, context: TranslationContext, shouldWrapVarargInArray: Boolean) { if (arguments.isEmpty()) { diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/utils/FunctionBodyTranslator.java b/js/js.translator/src/org/jetbrains/kotlin/js/translate/utils/FunctionBodyTranslator.java index 52da5ec5039..ee8c85bd89d 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/utils/FunctionBodyTranslator.java +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/utils/FunctionBodyTranslator.java @@ -118,16 +118,8 @@ public final class FunctionBodyTranslator extends AbstractTranslator { KotlinType returnType = descriptor.getReturnType(); assert returnType != null; - - TranslationContext handleResultContext = context().innerBlock(jsBlock); - JsExpression handleResultExpr = TranslationUtils.tryTranslateHandleResult(handleResultContext, declaration, jetBodyExpression); - if (handleResultExpr != null) { - jsBlock.getStatements().add(new JsReturn(handleResultExpr)); - } - else { - JsNode jsBody = Translation.translateExpression(jetBodyExpression, context(), jsBlock); - jsBlock.getStatements().addAll(mayBeWrapWithReturn(jsBody).getStatements()); - } + JsNode jsBody = Translation.translateExpression(jetBodyExpression, context(), jsBlock); + jsBlock.getStatements().addAll(mayBeWrapWithReturn(jsBody).getStatements()); return jsBlock; } diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/utils/TranslationUtils.java b/js/js.translator/src/org/jetbrains/kotlin/js/translate/utils/TranslationUtils.java index d689ba4d01a..304b99c6a8a 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/utils/TranslationUtils.java +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/utils/TranslationUtils.java @@ -18,34 +18,34 @@ package org.jetbrains.kotlin.js.translate.utils; import com.google.dart.compiler.backend.js.ast.*; import com.google.dart.compiler.backend.js.ast.JsBinaryOperator; -import com.google.dart.compiler.backend.js.ast.metadata.MetadataProperties; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.kotlin.builtins.KotlinBuiltIns; import org.jetbrains.kotlin.descriptors.*; import org.jetbrains.kotlin.descriptors.impl.LocalVariableAccessorDescriptor; import org.jetbrains.kotlin.descriptors.impl.LocalVariableDescriptor; -import org.jetbrains.kotlin.js.translate.callTranslator.CallTranslator; +import org.jetbrains.kotlin.incremental.components.NoLookupLocation; import org.jetbrains.kotlin.js.translate.context.Namer; import org.jetbrains.kotlin.js.translate.context.TemporaryConstVariable; import org.jetbrains.kotlin.js.translate.context.TranslationContext; import org.jetbrains.kotlin.js.translate.expression.InlineMetadata; import org.jetbrains.kotlin.js.translate.general.Translation; import org.jetbrains.kotlin.js.translate.reference.ReferenceTranslator; +import org.jetbrains.kotlin.name.ClassId; +import org.jetbrains.kotlin.name.FqName; +import org.jetbrains.kotlin.name.Name; import org.jetbrains.kotlin.psi.*; -import org.jetbrains.kotlin.psi.psiUtil.PsiUtilsKt; import org.jetbrains.kotlin.resolve.BindingContext; import org.jetbrains.kotlin.resolve.DescriptorUtils; import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall; import org.jetbrains.kotlin.resolve.calls.model.ResolvedValueArgument; import org.jetbrains.kotlin.resolve.inline.InlineUtil; +import org.jetbrains.kotlin.serialization.deserialization.FindClassInModuleKt; import org.jetbrains.kotlin.resolve.scopes.receivers.ImplicitReceiver; import org.jetbrains.kotlin.types.KotlinType; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; -import java.util.Map; import static com.google.dart.compiler.backend.js.ast.JsBinaryOperator.*; import static org.jetbrains.kotlin.js.translate.utils.BindingUtils.getCallableDescriptorForOperationExpression; @@ -130,7 +130,7 @@ public final class TranslationUtils { } @NotNull - public static JsBinaryOperation isNotNullCheck(@NotNull JsExpression expressionToCheck) { + private static JsBinaryOperation isNotNullCheck(@NotNull JsExpression expressionToCheck) { return nullCheck(expressionToCheck, true); } @@ -312,48 +312,6 @@ public final class TranslationUtils { return false; } - @Nullable - public static JsExpression tryTranslateHandleResult( - @NotNull TranslationContext context, - @NotNull KtExpression expression, - @Nullable KtExpression returnExpression - ) { - ResolvedCall returnCall = context.bindingContext().get( - BindingContext.RETURN_HANDLE_RESULT_RESOLVED_CALL, expression); - if (returnCall == null) return null; - - Map aliases = new HashMap(); - List arguments = returnCall.getValueArgumentsByIndex(); - assert arguments != null : "Arguments should be defined here: " + PsiUtilsKt.getTextWithLocation(expression); - - KotlinType returnType = returnCall.getResultingDescriptor().getValueParameters().get(0).getType(); - - ValueArgument returnValueArgument = arguments.get(0).getArguments().get(0); - if (returnExpression != null) { - aliases.put(returnValueArgument.getArgumentExpression(), Translation.translateAsExpression(returnExpression, context)); - } - else if (KotlinBuiltIns.isUnit(returnType)) { - aliases.put(returnValueArgument.getArgumentExpression(), JsLiteral.NULL); - } - - ValueArgument continuationArgument = arguments.get(1).getArguments().get(0); - aliases.put(continuationArgument.getArgumentExpression(), JsLiteral.THIS); - - TranslationContext returnContext = context.innerContextWithAliasesForExpressions(aliases); - - ImplicitReceiver receiver = (ImplicitReceiver) returnCall.getDispatchReceiver(); - assert receiver != null; - FunctionDescriptor lambdaDescriptor = (FunctionDescriptor) receiver.getDeclarationDescriptor(); - ReceiverParameterDescriptor receiverParameter = lambdaDescriptor.getExtensionReceiverParameter(); - assert receiverParameter != null; - JsExpression jsReceiver = returnContext.getDispatchReceiver(receiverParameter); - - JsInvocation handleResultInvocation = (JsInvocation) CallTranslator.translate(returnContext, returnCall, jsReceiver); - MetadataProperties.setHandleResult(handleResultInvocation, true); - - return handleResultInvocation; - } - @NotNull public static JsExpression translateContinuationArgument(@NotNull TranslationContext context, @NotNull ResolvedCall resolvedCall) { CallableDescriptor continuationDescriptor = @@ -375,4 +333,28 @@ public final class TranslationUtils { } return result; } + + @NotNull + public static ClassDescriptor getCoroutineBaseClass(@NotNull TranslationContext context) { + FqName className = KotlinBuiltIns.COROUTINES_PACKAGE_FQ_NAME.child(Name.identifier("CoroutineImpl")); + ClassDescriptor descriptor = FindClassInModuleKt.findClassAcrossModuleDependencies( + context.getCurrentModule(), ClassId.topLevel(className)); + assert descriptor != null; + return descriptor; + } + + @NotNull + public static PropertyDescriptor getCoroutineProperty(@NotNull TranslationContext context, @NotNull String name) { + return getCoroutineBaseClass(context).getUnsubstitutedMemberScope() + .getContributedVariables(Name.identifier(name), NoLookupLocation.FROM_DESERIALIZATION) + .iterator().next(); + } + + + @NotNull + public static FunctionDescriptor getCoroutineDoResumeFunction(@NotNull TranslationContext context) { + return getCoroutineBaseClass(context).getUnsubstitutedMemberScope() + .getContributedFunctions(Name.identifier("doResume"), NoLookupLocation.FROM_DESERIALIZATION) + .iterator().next(); + } }