JS: implement new coroutine convention
This commit is contained in:
committed by
Stanislav Erokhin
parent
9fd1ac72a9
commit
e2d969d8b0
@@ -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
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ class Controller {
|
||||
}
|
||||
}
|
||||
|
||||
// MODULE: main(controller)
|
||||
// MODULE: main(controller, support)
|
||||
// FILE: main.kt
|
||||
import lib.*
|
||||
import kotlin.coroutines.*
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
// IGNORE_BACKEND: JS
|
||||
// WITH_RUNTIME
|
||||
// WITH_COROUTINES
|
||||
import kotlin.coroutines.*
|
||||
|
||||
|
||||
suspend fun suspendHere(): String = suspendWithCurrentContinuation { x ->
|
||||
x.resume("OK")
|
||||
SUSPENDED
|
||||
|
||||
@@ -602,6 +602,7 @@ public class KotlinTestUtils {
|
||||
|
||||
List<F> 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.<String>emptyList()) : null;
|
||||
testFiles.add(factory.createFile(supportModule,
|
||||
"CoroutineUtil.kt",
|
||||
"import kotlin.coroutines.*\n" +
|
||||
"fun <T> handleResultContinuation(x: (T) -> Unit): Continuation<T> = object: Continuation<T> {\n" +
|
||||
|
||||
+16
-16
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
+53
-127
@@ -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<JsStatement> {
|
||||
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<JsStatement>()
|
||||
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<JsStatement>,
|
||||
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<JsStatement> {
|
||||
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<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,
|
||||
parameterName: String,
|
||||
additionalArgs: List<JsExpression>
|
||||
) {
|
||||
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<CoroutineBlock>,
|
||||
context: CoroutineTransformationContext,
|
||||
statements: MutableList<JsStatement>,
|
||||
throwName: JsName?
|
||||
): JsName {
|
||||
statements: MutableList<JsStatement>
|
||||
) {
|
||||
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<CoroutineBlock>,
|
||||
throwName: JsName?,
|
||||
exceptionName: JsName
|
||||
blocks: List<CoroutineBlock>
|
||||
): List<JsStatement> {
|
||||
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<JsStatement>.assignToField(fieldName: JsName, value: JsExpression) {
|
||||
this += JsAstUtils.assignment(JsNameRef(fieldName, JsLiteral.THIS), value).makeStmt()
|
||||
}
|
||||
|
||||
@@ -112,7 +112,7 @@ fun List<CoroutineBlock>.replaceCoroutineFlowStatements(context: CoroutineTransf
|
||||
override fun endVisit(x: JsDebugger, ctx: JsContext<in JsStatement>) {
|
||||
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<CoroutineBlock>.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<CoroutineBlock>.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
|
||||
})
|
||||
}
|
||||
|
||||
+5
-7
@@ -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") }
|
||||
}
|
||||
@@ -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<in JsStatement>) {
|
||||
val coroutineType = x.coroutineType
|
||||
if (coroutineType != null) {
|
||||
additionalStatements += CoroutineFunctionTransformer(program, x, coroutineType, x.controllerType!!).transform()
|
||||
additionalStatements += CoroutineFunctionTransformer(program, x).transform()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<JsScope, Set<JsScope>>.resolveNames(knownNames: Set<JsName>): Ma
|
||||
return replacements
|
||||
}
|
||||
|
||||
private class ScopeCollector() : RecursiveJsVisitor() {
|
||||
private class ScopeCollector : RecursiveJsVisitor() {
|
||||
val scopeTree = mutableMapOf<JsScope, MutableSet<JsScope>>()
|
||||
|
||||
override fun visitCatch(x: JsCatch) {
|
||||
|
||||
@@ -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 <P> interceptResume(data: P, continuation: Continuation<P>): 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 <R, T> (suspend R.() -> T).createCoroutine(
|
||||
receiver: R,
|
||||
resultHandler: Continuation<T>,
|
||||
resumeInterceptor: ResumeInterceptor? = null
|
||||
): Continuation<Unit> = 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 <R, T> (suspend R.() -> T).startCoroutine(
|
||||
receiver: R,
|
||||
resultHandler: Continuation<T>,
|
||||
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 <T> (suspend () -> T).createCoroutine(
|
||||
resultHandler: Continuation<T>,
|
||||
resumeInterceptor: ResumeInterceptor? = null
|
||||
): Continuation<Unit> = 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 <T> (suspend () -> T).startCoroutine(
|
||||
resultHandler: Continuation<T>,
|
||||
resumeInterceptor: ResumeInterceptor? = null
|
||||
) {
|
||||
createCoroutine(resultHandler, resumeInterceptor).resume(Unit)
|
||||
}
|
||||
|
||||
// ------- internal stuff -------
|
||||
|
||||
private fun <T> withInterceptor(resultHandler: Continuation<T>, resumeInterceptor: ResumeInterceptor?): Continuation<T> {
|
||||
val finalResumeInterceptor = resumeInterceptor ?: object : ResumeInterceptor {
|
||||
override fun <P> interceptResume(data: P, continuation: Continuation<P>) = false
|
||||
|
||||
override fun interceptResumeWithException(exception: Throwable, continuation: Continuation<*>) = false
|
||||
}
|
||||
return object : Continuation<T> by resultHandler, ResumeInterceptor by finalResumeInterceptor {}
|
||||
}
|
||||
|
||||
@JsName("CoroutineImpl")
|
||||
internal abstract class CoroutineImpl(private val resultContinuation: Continuation<Any?>) : Continuation<Any?> {
|
||||
protected var state = 0
|
||||
protected var exceptionState = 0
|
||||
protected var result: Any? = null
|
||||
protected var exception: Throwable? = null
|
||||
protected var finallyPath: Array<Int>? = 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
|
||||
+1
-7
@@ -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")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
+3
-8
@@ -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<JsNode> {
|
||||
}
|
||||
|
||||
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<JsNode> {
|
||||
@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);
|
||||
|
||||
+32
-30
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
+14
-13
@@ -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<ValueArgument>, result: MutableList<JsExpression>,
|
||||
context: TranslationContext, shouldWrapVarargInArray: Boolean) {
|
||||
if (arguments.isEmpty()) {
|
||||
|
||||
+2
-10
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
+30
-48
@@ -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<? extends FunctionDescriptor> returnCall = context.bindingContext().get(
|
||||
BindingContext.RETURN_HANDLE_RESULT_RESOLVED_CALL, expression);
|
||||
if (returnCall == null) return null;
|
||||
|
||||
Map<KtExpression, JsExpression> aliases = new HashMap<KtExpression, JsExpression>();
|
||||
List<ResolvedValueArgument> 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();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user