JS: allow to call suspend function from any position within another suspend function

This commit is contained in:
Denis Zharkov
2017-01-13 19:13:34 +03:00
parent 1f0d0028b2
commit 2fcc2bbae0
13 changed files with 202 additions and 87 deletions
@@ -24,7 +24,6 @@ import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.descriptors.FunctionDescriptor import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.js.backend.ast.* import org.jetbrains.kotlin.js.backend.ast.*
import org.jetbrains.kotlin.resolve.inline.InlineStrategy import org.jetbrains.kotlin.resolve.inline.InlineStrategy
import org.jetbrains.kotlin.types.KotlinType
var JsName.staticRef: JsNode? by MetadataProperty(default = null) var JsName.staticRef: JsNode? by MetadataProperty(default = null)
@@ -68,8 +67,6 @@ var HasMetadata.synthetic: Boolean by MetadataProperty(default = false)
var HasMetadata.sideEffects: SideEffectKind by MetadataProperty(default = SideEffectKind.AFFECTS_STATE) var HasMetadata.sideEffects: SideEffectKind by MetadataProperty(default = SideEffectKind.AFFECTS_STATE)
var JsFunction.coroutineType: KotlinType? by MetadataProperty(default = null)
/** /**
* Denotes a suspension call-site that is to be processed by coroutine transformer. * Denotes a suspension call-site that is to be processed by coroutine transformer.
* More clearly, denotes invocation that should immediately return from coroutine state machine * More clearly, denotes invocation that should immediately return from coroutine state machine
@@ -99,6 +96,12 @@ var JsNameRef.coroutineResult by MetadataProperty(default = false)
*/ */
var JsNameRef.coroutineController by MetadataProperty(default = false) var JsNameRef.coroutineController by MetadataProperty(default = false)
/**
* Denotes a reference to coroutine's receiver. It's later rewritten to `this`. Required to distinguish between `this` for
* function's dispatch receiver and coroutine's state receiver.
*/
var JsNameRef.coroutineReceiver by MetadataProperty(default = false)
var JsName.imported by MetadataProperty(default = false) var JsName.imported by MetadataProperty(default = false)
var JsFunction.coroutineMetadata: CoroutineMetadata? by MetadataProperty(default = null) var JsFunction.coroutineMetadata: CoroutineMetadata? by MetadataProperty(default = null)
@@ -114,7 +117,9 @@ class CoroutineMetadata(
val facadeName: JsName, val facadeName: JsName,
val baseClassRef: JsExpression, val baseClassRef: JsExpression,
val suspendObjectRef: JsExpression, val suspendObjectRef: JsExpression,
val hasController: Boolean val isLambda: Boolean,
val hasController: Boolean,
val hasReceiver: Boolean
) )
enum class TypeCheck { enum class TypeCheck {
@@ -249,7 +249,8 @@ class CoroutineBodyTransformer(private val program: JsProgram, private val conte
} }
if (catchNode != null) { if (catchNode != null) {
currentStatements += JsAstUtils.newVar(catchNode.parameter.name, JsNameRef(context.metadata.exceptionName, JsLiteral.THIS)) currentStatements += JsAstUtils.newVar(catchNode.parameter.name, JsNameRef(
context.metadata.exceptionName, JsAstUtils.stateMachineReceiver()))
catchNode.body.statements.forEach { it.accept(this) } catchNode.body.statements.forEach { it.accept(this) }
if (finallyNode == null) { if (finallyNode == null) {
@@ -278,8 +279,8 @@ class CoroutineBodyTransformer(private val program: JsProgram, private val conte
// for simple `when` statement, we will need to support JsSwitch here // for simple `when` statement, we will need to support JsSwitch here
private fun generateFinallyExit() { private fun generateFinallyExit() {
val finallyPathRef = JsNameRef(context.metadata.finallyPathName, JsLiteral.THIS) val finallyPathRef = JsNameRef(context.metadata.finallyPathName, JsAstUtils.stateMachineReceiver())
val stateRef = JsNameRef(context.metadata.stateName, JsLiteral.THIS) val stateRef = JsNameRef(context.metadata.stateName, JsAstUtils.stateMachineReceiver())
val nextState = JsInvocation(JsNameRef("shift", finallyPathRef)) val nextState = JsInvocation(JsNameRef("shift", finallyPathRef))
currentStatements += JsAstUtils.assignment(stateRef, nextState).makeStmt() currentStatements += JsAstUtils.assignment(stateRef, nextState).makeStmt()
currentStatements += jump() currentStatements += jump()
@@ -307,7 +308,7 @@ class CoroutineBodyTransformer(private val program: JsProgram, private val conte
jumpWithFinally(0, returnBlock) jumpWithFinally(0, returnBlock)
val returnExpression = x.expression val returnExpression = x.expression
val returnFieldRef = if (returnExpression != null) { val returnFieldRef = if (returnExpression != null) {
val ref = JsNameRef(context.returnValueFieldName, JsLiteral.THIS) val ref = JsNameRef(context.returnValueFieldName, JsAstUtils.stateMachineReceiver())
currentStatements += JsAstUtils.assignment(ref, x.expression).makeStmt() currentStatements += JsAstUtils.assignment(ref, x.expression).makeStmt()
ref ref
} }
@@ -359,7 +360,9 @@ class CoroutineBodyTransformer(private val program: JsProgram, private val conte
listOf(JsReturn(context.metadata.suspendObjectRef.deepCopy())) listOf(JsReturn(context.metadata.suspendObjectRef.deepCopy()))
} }
else { else {
val resultRef = JsNameRef(context.metadata.resultName, JsLiteral.THIS).apply { sideEffects = SideEffectKind.DEPENDS_ON_STATE } val resultRef = JsNameRef(context.metadata.resultName, JsAstUtils.stateMachineReceiver()).apply {
sideEffects = SideEffectKind.DEPENDS_ON_STATE
}
val invocationStatement = JsAstUtils.assignment(resultRef, invokeExpression).makeStmt() val invocationStatement = JsAstUtils.assignment(resultRef, invokeExpression).makeStmt()
val suspendCondition = JsAstUtils.equality(resultRef.deepCopy(), context.metadata.suspendObjectRef.deepCopy()) val suspendCondition = JsAstUtils.equality(resultRef.deepCopy(), context.metadata.suspendObjectRef.deepCopy())
val suspendIfNeeded = JsIf(suspendCondition, JsReturn(context.metadata.suspendObjectRef.deepCopy())) val suspendIfNeeded = JsIf(suspendCondition, JsReturn(context.metadata.suspendObjectRef.deepCopy()))
@@ -24,12 +24,12 @@ import org.jetbrains.kotlin.js.inline.util.getInnerFunction
import org.jetbrains.kotlin.js.translate.context.Namer import org.jetbrains.kotlin.js.translate.context.Namer
import org.jetbrains.kotlin.js.translate.utils.JsAstUtils import org.jetbrains.kotlin.js.translate.utils.JsAstUtils
class CoroutineFunctionTransformer(private val program: JsProgram, private val function: JsFunction) { class CoroutineFunctionTransformer(private val program: JsProgram, private val function: JsFunction, name: String?) {
private val innerFunction = function.getInnerFunction() private val innerFunction = function.getInnerFunction()
private val functionWithBody = innerFunction ?: function private val functionWithBody = innerFunction ?: function
private val body = functionWithBody.body private val body = functionWithBody.body
private val localVariables = (function.collectLocalVariables() + functionWithBody.collectLocalVariables()).toMutableSet() private val localVariables = (function.collectLocalVariables() + functionWithBody.collectLocalVariables()).toMutableSet()
private val className = function.scope.parent.declareFreshName("Coroutine\$${function.name}") private val className = function.scope.parent.declareFreshName("Coroutine\$${name ?: "anonymous"}")
fun transform(): List<JsStatement> { fun transform(): List<JsStatement> {
val context = CoroutineTransformationContext(function.scope, function) val context = CoroutineTransformationContext(function.scope, function)
@@ -58,11 +58,16 @@ class CoroutineFunctionTransformer(private val program: JsProgram, private val f
) { ) {
val constructor = JsFunction(function.scope.parent, JsBlock(), "Continuation") val constructor = JsFunction(function.scope.parent, JsBlock(), "Continuation")
constructor.name = className constructor.name = className
if (context.metadata.hasReceiver) {
constructor.parameters += JsParameter(context.receiverFieldName)
}
constructor.parameters += function.parameters.map { JsParameter(it.name) } constructor.parameters += function.parameters.map { JsParameter(it.name) }
if (innerFunction != null) { if (innerFunction != null) {
constructor.parameters += innerFunction.parameters.map { JsParameter(it.name) } constructor.parameters += innerFunction.parameters.map { JsParameter(it.name) }
} }
val lastParameter = function.parameters.lastOrNull()?.name
val controllerName = if (context.metadata.hasController) { val controllerName = if (context.metadata.hasController) {
function.scope.declareFreshName("controller").apply { constructor.parameters += JsParameter(this) } function.scope.declareFreshName("controller").apply { constructor.parameters += JsParameter(this) }
} }
@@ -70,18 +75,27 @@ class CoroutineFunctionTransformer(private val program: JsProgram, private val f
null null
} }
val interceptorName = function.scope.declareFreshName("interceptor") val interceptorRef = if (context.metadata.isLambda) {
constructor.parameters += JsParameter(interceptorName) val interceptorName = function.scope.declareFreshName("interceptor")
constructor.parameters += JsParameter(interceptorName)
interceptorName.makeRef()
}
else {
lastParameter!!.makeRef()
}
val parameterNames = (function.parameters.map { it.name } + innerFunction?.parameters?.map { it.name }.orEmpty()).toSet() val parameterNames = (function.parameters.map { it.name } + innerFunction?.parameters?.map { it.name }.orEmpty()).toSet()
constructor.body.statements.run { constructor.body.statements.run {
val baseClass = context.metadata.baseClassRef.deepCopy() val baseClass = context.metadata.baseClassRef.deepCopy()
this += JsInvocation(Namer.getFunctionCallRef(baseClass), JsLiteral.THIS, interceptorName.makeRef()).makeStmt() this += JsInvocation(Namer.getFunctionCallRef(baseClass), JsLiteral.THIS, interceptorRef).makeStmt()
if (controllerName != null) { if (controllerName != null) {
assignToField(context.controllerFieldName, controllerName.makeRef()) assignToField(context.controllerFieldName, controllerName.makeRef())
} }
assignToField(context.metadata.exceptionStateName, program.getNumberLiteral(globalCatchBlockIndex)) assignToField(context.metadata.exceptionStateName, program.getNumberLiteral(globalCatchBlockIndex))
if (context.metadata.hasReceiver) {
assignToField(context.receiverFieldName, context.receiverFieldName.makeRef())
}
for (localVariable in localVariables) { for (localVariable in localVariables) {
val value = if (localVariable !in parameterNames) Namer.getUndefinedExpression() else localVariable.makeRef() val value = if (localVariable !in parameterNames) Namer.getUndefinedExpression() else localVariable.makeRef()
assignToField(function.scope.getFieldName(localVariable), value) assignToField(function.scope.getFieldName(localVariable), value)
@@ -139,6 +153,9 @@ class CoroutineFunctionTransformer(private val program: JsProgram, private val f
private fun generateCoroutineInstantiation(context: CoroutineTransformationContext) { private fun generateCoroutineInstantiation(context: CoroutineTransformationContext) {
val instantiation = JsNew(className.makeRef()) val instantiation = JsNew(className.makeRef())
if (context.metadata.hasReceiver) {
instantiation.arguments += JsLiteral.THIS
}
instantiation.arguments += function.parameters.map { it.name.makeRef() } instantiation.arguments += function.parameters.map { it.name.makeRef() }
if (innerFunction != null) { if (innerFunction != null) {
instantiation.arguments += innerFunction.parameters.map { it.name.makeRef() } instantiation.arguments += innerFunction.parameters.map { it.name.makeRef() }
@@ -148,14 +165,15 @@ class CoroutineFunctionTransformer(private val program: JsProgram, private val f
instantiation.arguments += JsLiteral.THIS instantiation.arguments += JsLiteral.THIS
} }
val interceptorParamName = functionWithBody.scope.declareFreshName("interceptor") if (context.metadata.isLambda) {
functionWithBody.parameters += JsParameter(interceptorParamName) val interceptorParamName = functionWithBody.scope.declareFreshName("interceptor")
functionWithBody.parameters += JsParameter(interceptorParamName)
instantiation.arguments += interceptorParamName.makeRef()
}
val suspendedName = functionWithBody.scope.declareFreshName("suspended") val suspendedName = functionWithBody.scope.declareFreshName("suspended")
functionWithBody.parameters += JsParameter(suspendedName) functionWithBody.parameters += JsParameter(suspendedName)
instantiation.arguments += interceptorParamName.makeRef()
val instanceName = functionWithBody.scope.declareFreshName("instance") val instanceName = functionWithBody.scope.declareFreshName("instance")
functionWithBody.body.statements += JsAstUtils.newVar(instanceName, JsNameRef(context.metadata.facadeName, instantiation)) functionWithBody.body.statements += JsAstUtils.newVar(instanceName, JsNameRef(context.metadata.facadeName, instantiation))
@@ -112,7 +112,7 @@ fun List<CoroutineBlock>.replaceCoroutineFlowStatements(context: CoroutineTransf
override fun endVisit(x: JsDebugger, ctx: JsContext<in JsStatement>) { override fun endVisit(x: JsDebugger, ctx: JsContext<in JsStatement>) {
val target = x.targetBlock val target = x.targetBlock
if (target != null) { if (target != null) {
val lhs = JsNameRef(context.metadata.stateName, JsLiteral.THIS) val lhs = JsNameRef(context.metadata.stateName, JsAstUtils.stateMachineReceiver())
val rhs = program.getNumberLiteral(blockIndexes[target]!!) val rhs = program.getNumberLiteral(blockIndexes[target]!!)
ctx.replaceMe(JsExpressionStatement(JsAstUtils.assignment(lhs, rhs)).apply { ctx.replaceMe(JsExpressionStatement(JsAstUtils.assignment(lhs, rhs)).apply {
targetBlock = true targetBlock = true
@@ -121,7 +121,7 @@ fun List<CoroutineBlock>.replaceCoroutineFlowStatements(context: CoroutineTransf
val exceptionTarget = x.targetExceptionBlock val exceptionTarget = x.targetExceptionBlock
if (exceptionTarget != null) { if (exceptionTarget != null) {
val lhs = JsNameRef(context.metadata.exceptionStateName, JsLiteral.THIS) val lhs = JsNameRef(context.metadata.exceptionStateName, JsAstUtils.stateMachineReceiver())
val rhs = program.getNumberLiteral(blockIndexes[exceptionTarget]!!) val rhs = program.getNumberLiteral(blockIndexes[exceptionTarget]!!)
ctx.replaceMe(JsExpressionStatement(JsAstUtils.assignment(lhs, rhs)).apply { ctx.replaceMe(JsExpressionStatement(JsAstUtils.assignment(lhs, rhs)).apply {
targetExceptionBlock = true targetExceptionBlock = true
@@ -131,7 +131,7 @@ fun List<CoroutineBlock>.replaceCoroutineFlowStatements(context: CoroutineTransf
val finallyPath = x.finallyPath val finallyPath = x.finallyPath
if (finallyPath != null) { if (finallyPath != null) {
if (finallyPath.isNotEmpty()) { if (finallyPath.isNotEmpty()) {
val lhs = JsNameRef(context.metadata.finallyPathName, JsLiteral.THIS) val lhs = JsNameRef(context.metadata.finallyPathName, JsAstUtils.stateMachineReceiver())
val rhs = JsArrayLiteral(finallyPath.map { program.getNumberLiteral(blockIndexes[it]!!) }) val rhs = JsArrayLiteral(finallyPath.map { program.getNumberLiteral(blockIndexes[it]!!) })
ctx.replaceMe(JsExpressionStatement(JsAstUtils.assignment(lhs, rhs)).apply { ctx.replaceMe(JsExpressionStatement(JsAstUtils.assignment(lhs, rhs)).apply {
this.finallyPath = true this.finallyPath = true
@@ -200,8 +200,16 @@ private fun CoroutineBlock.collectFinallyPaths(): List<List<CoroutineBlock>> {
fun JsBlock.replaceSpecialReferences(context: CoroutineTransformationContext) { fun JsBlock.replaceSpecialReferences(context: CoroutineTransformationContext) {
val visitor = object : JsVisitorWithContextImpl() { val visitor = object : JsVisitorWithContextImpl() {
override fun endVisit(x: JsLiteral.JsThisRef, ctx: JsContext<in JsNode>) {
ctx.replaceMe(JsNameRef(context.receiverFieldName, JsLiteral.THIS))
}
override fun endVisit(x: JsNameRef, ctx: JsContext<in JsNode>) { override fun endVisit(x: JsNameRef, ctx: JsContext<in JsNode>) {
when { when {
x.coroutineReceiver -> {
ctx.replaceMe(JsLiteral.THIS)
}
x.coroutineController -> { x.coroutineController -> {
ctx.replaceMe(JsNameRef(context.controllerFieldName, x.qualifier).apply { ctx.replaceMe(JsNameRef(context.controllerFieldName, x.qualifier).apply {
sideEffects = SideEffectKind.PURE sideEffects = SideEffectKind.PURE
@@ -26,4 +26,5 @@ class CoroutineTransformationContext(private val scope: JsScope, function: JsFun
val metadata = function.coroutineMetadata!! val metadata = function.coroutineMetadata!!
val controllerFieldName by lazy { scope.declareFreshName("\$controller") } val controllerFieldName by lazy { scope.declareFreshName("\$controller") }
val returnValueFieldName by lazy { scope.declareFreshName("\$returnValue") } val returnValueFieldName by lazy { scope.declareFreshName("\$returnValue") }
val receiverFieldName by lazy { scope.declareFreshName("\$this") }
} }
@@ -17,21 +17,56 @@
package org.jetbrains.kotlin.js.coroutine package org.jetbrains.kotlin.js.coroutine
import org.jetbrains.kotlin.js.backend.ast.* import org.jetbrains.kotlin.js.backend.ast.*
import org.jetbrains.kotlin.js.backend.ast.metadata.coroutineType import org.jetbrains.kotlin.js.backend.ast.metadata.coroutineMetadata
import org.jetbrains.kotlin.js.translate.expression.InlineMetadata
import org.jetbrains.kotlin.js.translate.utils.JsAstUtils
class CoroutineTransformer(private val program: JsProgram) : JsVisitorWithContextImpl() { class CoroutineTransformer(private val program: JsProgram) : JsVisitorWithContextImpl() {
private val additionalStatements = mutableListOf<JsStatement>() private val additionalStatementsByNode = mutableMapOf<JsNode, List<JsStatement>>()
override fun endVisit(x: JsExpressionStatement, ctx: JsContext<in JsStatement>) { override fun endVisit(x: JsExpressionStatement, ctx: JsContext<in JsStatement>) {
additionalStatements.forEach { ctx.addNext(it) } additionalStatementsByNode.remove(x)?.forEach { ctx.addNext(it) }
additionalStatements.clear()
super.endVisit(x, ctx) super.endVisit(x, ctx)
} }
override fun endVisit(x: JsFunction, ctx: JsContext<in JsStatement>) { override fun endVisit(x: JsVars, ctx: JsContext<in JsStatement>) {
val coroutineType = x.coroutineType for (v in x.vars) {
if (coroutineType != null) { additionalStatementsByNode.remove(v)?.forEach { ctx.addNext(it) }
additionalStatements += CoroutineFunctionTransformer(program, x).transform()
} }
super.endVisit(x, ctx)
}
override fun visit(x: JsExpressionStatement, ctx: JsContext<*>): Boolean {
val expression = x.expression
val assignment = JsAstUtils.decomposeAssignment(expression)
if (assignment != null) {
val (lhs, rhs) = assignment
val function = rhs as? JsFunction ?: InlineMetadata.decompose(rhs)?.function
if (function?.coroutineMetadata != null) {
val name = ((lhs as? JsNameRef)?.name ?: function.name)?.ident
additionalStatementsByNode[x] = CoroutineFunctionTransformer(program, function, name).transform()
return false
}
}
else if (expression is JsFunction) {
if (expression.coroutineMetadata != null) {
additionalStatementsByNode[x] = CoroutineFunctionTransformer(program, expression, expression.name?.ident).transform()
return false
}
}
return super.visit(x, ctx)
}
override fun visit(x: JsVars.JsVar, ctx: JsContext<*>): Boolean {
val initExpression = x.initExpression
if (initExpression != null) {
val function = initExpression as? JsFunction ?: InlineMetadata.decompose(initExpression)?.function
if (function?.coroutineMetadata != null) {
val name = x.name.ident
additionalStatementsByNode[x] = CoroutineFunctionTransformer(program, function, name).transform()
return false
}
}
return super.visit(x, ctx)
} }
} }
+9 -5
View File
@@ -126,11 +126,13 @@ internal abstract class CoroutineImpl(private val resultContinuation: Continuati
} }
override fun resume(data: Any?) { override fun resume(data: Any?) {
this.result = data result = data
try { try {
val result = doResume() result = doResume()
if (result != SUSPENDED_MARKER) { if (result != SUSPENDED_MARKER) {
resultContinuation.resume(result) val data = result
result = SUSPENDED_MARKER
resultContinuation.resume(data)
} }
} }
catch (e: Throwable) { catch (e: Throwable) {
@@ -142,9 +144,11 @@ internal abstract class CoroutineImpl(private val resultContinuation: Continuati
state = exceptionState state = exceptionState
this.exception = exception this.exception = exception
try { try {
val result = doResume() result = doResume()
if (result != SUSPENDED_MARKER) { if (result != SUSPENDED_MARKER) {
resultContinuation.resume(result) val data = result
result = SUSPENDED_MARKER
resultContinuation.resume(data)
} }
} }
catch (e: Throwable) { catch (e: Throwable) {
@@ -28,10 +28,7 @@ import org.jetbrains.kotlin.js.translate.general.Translation
import org.jetbrains.kotlin.js.translate.reference.CallArgumentTranslator import org.jetbrains.kotlin.js.translate.reference.CallArgumentTranslator
import org.jetbrains.kotlin.js.translate.reference.CallExpressionTranslator import org.jetbrains.kotlin.js.translate.reference.CallExpressionTranslator
import org.jetbrains.kotlin.js.translate.reference.ReferenceTranslator import org.jetbrains.kotlin.js.translate.reference.ReferenceTranslator
import org.jetbrains.kotlin.js.translate.utils.AnnotationsUtils import org.jetbrains.kotlin.js.translate.utils.*
import org.jetbrains.kotlin.js.translate.utils.JsAstUtils
import org.jetbrains.kotlin.js.translate.utils.TranslationUtils
import org.jetbrains.kotlin.js.translate.utils.setInlineCallMetadata
import org.jetbrains.kotlin.psi.Call.CallType import org.jetbrains.kotlin.psi.Call.CallType
import org.jetbrains.kotlin.psi.KtExpression import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.resolve.calls.callResolverUtil.isInvokeCallOnVariable import org.jetbrains.kotlin.resolve.calls.callResolverUtil.isInvokeCallOnVariable
@@ -143,7 +140,7 @@ private fun translateFunctionCall(
inlineResolvedCall.resultingDescriptor, context) inlineResolvedCall.resultingDescriptor, context)
} }
if (resolvedCall.resultingDescriptor.isSuspend && !context.isInSuspendFunction) { if (resolvedCall.resultingDescriptor.isSuspend && context.isInStateMachine) {
context.currentBlock.statements += JsAstUtils.asSyntheticStatement((callExpression as JsInvocation).apply { context.currentBlock.statements += JsAstUtils.asSyntheticStatement((callExpression as JsInvocation).apply {
isSuspend = true isSuspend = true
isPreSuspend = true isPreSuspend = true
@@ -157,6 +154,9 @@ private fun translateFunctionCall(
return callExpression return callExpression
} }
private val TranslationContext.isInStateMachine
get() = (declarationDescriptor as? FunctionDescriptor)?.requiresStateMachineTransformation(this) == true
private fun translateCallWithContinuation(context: TranslationContext, resolvedCall: ResolvedCall<out FunctionDescriptor>): JsExpression { private fun translateCallWithContinuation(context: TranslationContext, resolvedCall: ResolvedCall<out FunctionDescriptor>): JsExpression {
val arguments = CallArgumentTranslator.translate(resolvedCall, null, context) val arguments = CallArgumentTranslator.translate(resolvedCall, null, context)
val coroutineArgument = TranslationUtils.getEnclosingContinuationParameter(context) val coroutineArgument = TranslationUtils.getEnclosingContinuationParameter(context)
@@ -21,6 +21,7 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.descriptors.*; import org.jetbrains.kotlin.descriptors.*;
import org.jetbrains.kotlin.descriptors.annotations.Annotations; import org.jetbrains.kotlin.descriptors.annotations.Annotations;
import org.jetbrains.kotlin.descriptors.impl.AnonymousFunctionDescriptor;
import org.jetbrains.kotlin.descriptors.impl.LocalVariableDescriptor; import org.jetbrains.kotlin.descriptors.impl.LocalVariableDescriptor;
import org.jetbrains.kotlin.descriptors.impl.TypeAliasConstructorDescriptor; import org.jetbrains.kotlin.descriptors.impl.TypeAliasConstructorDescriptor;
import org.jetbrains.kotlin.js.backend.ast.*; import org.jetbrains.kotlin.js.backend.ast.*;
@@ -106,7 +107,7 @@ public class TranslationContext {
} }
if (declarationDescriptor instanceof FunctionDescriptor) { if (declarationDescriptor instanceof FunctionDescriptor) {
FunctionDescriptor function = (FunctionDescriptor) declarationDescriptor; FunctionDescriptor function = (FunctionDescriptor) declarationDescriptor;
if (function.isSuspend()) { if (function.isSuspend() && !(function instanceof AnonymousFunctionDescriptor)) {
ClassDescriptor continuationDescriptor = getCurrentModule().getBuiltIns().getBuiltInClassByFqName( ClassDescriptor continuationDescriptor = getCurrentModule().getBuiltIns().getBuiltInClassByFqName(
DescriptorUtils.CONTINUATION_INTERFACE_FQ_NAME); DescriptorUtils.CONTINUATION_INTERFACE_FQ_NAME);
return new LocalVariableDescriptor( return new LocalVariableDescriptor(
@@ -403,7 +404,7 @@ public class TranslationContext {
return alias; return alias;
} }
if (isCoroutineLambda(descriptor.getContainingDeclaration())) { if (isCoroutineLambda(descriptor.getContainingDeclaration())) {
JsNameRef result = new JsNameRef("$$controller$$", JsLiteral.THIS); JsNameRef result = new JsNameRef("$$controller$$", JsAstUtils.stateMachineReceiver());
MetadataProperties.setCoroutineController(result, true); MetadataProperties.setCoroutineController(result, true);
return result; return result;
} }
@@ -680,10 +681,6 @@ public class TranslationContext {
return false; return false;
} }
public boolean isInSuspendFunction() {
return declarationDescriptor instanceof FunctionDescriptor && ((FunctionDescriptor) declarationDescriptor).isSuspend();
}
@Nullable @Nullable
public VariableDescriptor getContinuationParameterDescriptor() { public VariableDescriptor getContinuationParameterDescriptor() {
return continuationParameterDescriptor; return continuationParameterDescriptor;
@@ -16,16 +16,18 @@
package org.jetbrains.kotlin.js.translate.declaration package org.jetbrains.kotlin.js.translate.declaration
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
import org.jetbrains.kotlin.descriptors.isOverridable
import org.jetbrains.kotlin.js.backend.ast.JsExpression import org.jetbrains.kotlin.js.backend.ast.JsExpression
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.js.translate.context.TranslationContext import org.jetbrains.kotlin.js.translate.context.TranslationContext
import org.jetbrains.kotlin.js.translate.expression.translateAndAliasParameters import org.jetbrains.kotlin.js.translate.expression.translateAndAliasParameters
import org.jetbrains.kotlin.js.translate.expression.translateFunction import org.jetbrains.kotlin.js.translate.expression.translateFunction
import org.jetbrains.kotlin.js.translate.expression.wrapWithInlineMetadata import org.jetbrains.kotlin.js.translate.expression.wrapWithInlineMetadata
import org.jetbrains.kotlin.js.translate.general.TranslatorVisitor import org.jetbrains.kotlin.js.translate.general.TranslatorVisitor
import org.jetbrains.kotlin.js.translate.utils.BindingUtils import org.jetbrains.kotlin.js.translate.reference.ReferenceTranslator
import org.jetbrains.kotlin.js.translate.utils.FunctionBodyTranslator import org.jetbrains.kotlin.js.translate.utils.*
import org.jetbrains.kotlin.js.translate.utils.TranslationUtils
import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.descriptorUtil.isExtensionProperty import org.jetbrains.kotlin.resolve.descriptorUtil.isExtensionProperty
@@ -93,7 +95,20 @@ abstract class AbstractDeclarationVisitor : TranslatorVisitor<Unit>() {
context: TranslationContext context: TranslationContext
): JsExpression { ): JsExpression {
val function = context.getFunctionObject(descriptor) val function = context.getFunctionObject(descriptor)
val innerContext = context.newDeclaration(descriptor).translateAndAliasParameters(descriptor, function.parameters) var innerContext = context.newDeclaration(descriptor).translateAndAliasParameters(descriptor, function.parameters)
if (descriptor.isSuspend) {
if (descriptor.requiresStateMachineTransformation(context)) {
function.fillCoroutineMetadata(context, descriptor, hasController = false, isLambda = false)
innerContext = innerContext.innerContextWithAliased(descriptor, JsAstUtils.stateMachineReceiver())
}
else {
val continuationRef = ReferenceTranslator.translateAsValueReference(
innerContext.continuationParameterDescriptor!!, innerContext)
innerContext = innerContext.innerContextWithAliased(descriptor, continuationRef)
}
}
innerContext.getInnerNameForDescriptor(descriptor) innerContext.getInnerNameForDescriptor(descriptor)
if (!descriptor.isOverridable) { if (!descriptor.isOverridable) {
function.body.statements += FunctionBodyTranslator.setDefaultValueForArguments(descriptor, innerContext) function.body.statements += FunctionBodyTranslator.setDefaultValueForArguments(descriptor, innerContext)
@@ -20,7 +20,6 @@ import org.jetbrains.kotlin.backend.common.SUSPENDED_MARKER_NAME
import org.jetbrains.kotlin.builtins.isBuiltinExtensionFunctionalType import org.jetbrains.kotlin.builtins.isBuiltinExtensionFunctionalType
import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.descriptors.impl.ValueParameterDescriptorImpl import org.jetbrains.kotlin.descriptors.impl.ValueParameterDescriptorImpl
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
import org.jetbrains.kotlin.js.backend.ast.* import org.jetbrains.kotlin.js.backend.ast.*
import org.jetbrains.kotlin.js.backend.ast.metadata.* import org.jetbrains.kotlin.js.backend.ast.metadata.*
import org.jetbrains.kotlin.js.descriptorUtils.isCoroutineLambda import org.jetbrains.kotlin.js.descriptorUtils.isCoroutineLambda
@@ -30,15 +29,12 @@ import org.jetbrains.kotlin.js.translate.context.getNameForCapturedDescriptor
import org.jetbrains.kotlin.js.translate.context.hasCapturedExceptContaining import org.jetbrains.kotlin.js.translate.context.hasCapturedExceptContaining
import org.jetbrains.kotlin.js.translate.context.isCaptured import org.jetbrains.kotlin.js.translate.context.isCaptured
import org.jetbrains.kotlin.js.translate.general.AbstractTranslator import org.jetbrains.kotlin.js.translate.general.AbstractTranslator
import org.jetbrains.kotlin.js.translate.reference.ReferenceTranslator
import org.jetbrains.kotlin.js.translate.utils.BindingUtils.getFunctionDescriptor import org.jetbrains.kotlin.js.translate.utils.BindingUtils.getFunctionDescriptor
import org.jetbrains.kotlin.js.translate.utils.FunctionBodyTranslator.setDefaultValueForArguments import org.jetbrains.kotlin.js.translate.utils.FunctionBodyTranslator.setDefaultValueForArguments
import org.jetbrains.kotlin.js.translate.utils.FunctionBodyTranslator.translateFunctionBody import org.jetbrains.kotlin.js.translate.utils.FunctionBodyTranslator.translateFunctionBody
import org.jetbrains.kotlin.js.translate.utils.JsAstUtils 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.js.translate.utils.TranslationUtils.simpleReturnFunction
import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.js.translate.utils.fillCoroutineMetadata
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.KtDeclarationWithBody import org.jetbrains.kotlin.psi.KtDeclarationWithBody
import org.jetbrains.kotlin.psi.KtParameter import org.jetbrains.kotlin.psi.KtParameter
import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils
@@ -46,10 +42,6 @@ import org.jetbrains.kotlin.resolve.inline.InlineUtil
import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.KotlinType
class LiteralFunctionTranslator(context: TranslationContext) : AbstractTranslator(context) { class LiteralFunctionTranslator(context: TranslationContext) : AbstractTranslator(context) {
companion object {
private val COROUTINE_INTRINSICS_PACKAGE_FQ_NAME = FqName("kotlin.coroutines.intrinsics")
}
fun translate( fun translate(
declaration: KtDeclarationWithBody, declaration: KtDeclarationWithBody,
continuationType: KotlinType? = null continuationType: KotlinType? = null
@@ -61,7 +53,7 @@ class LiteralFunctionTranslator(context: TranslationContext) : AbstractTranslato
val aliases = mutableMapOf<DeclarationDescriptor, JsExpression>() val aliases = mutableMapOf<DeclarationDescriptor, JsExpression>()
if (descriptor.isCoroutineLambda) { if (descriptor.isCoroutineLambda) {
aliases.put(descriptor, JsLiteral.THIS) aliases.put(descriptor, JsAstUtils.stateMachineReceiver())
} }
val functionContext = invokingContext val functionContext = invokingContext
@@ -98,7 +90,7 @@ class LiteralFunctionTranslator(context: TranslationContext) : AbstractTranslato
lambda.name = null lambda.name = null
} }
lambdaCreator.name.staticRef = lambdaCreator lambdaCreator.name.staticRef = lambdaCreator
lambdaCreator.fillCoroutineMetadata(invokingContext, continuationType) lambdaCreator.fillCoroutineMetadata(invokingContext, descriptor, continuationType)
return lambdaCreator.withCapturedParameters(descriptor, descriptor.wrapContextForCoroutineIfNecessary(functionContext), return lambdaCreator.withCapturedParameters(descriptor, descriptor.wrapContextForCoroutineIfNecessary(functionContext),
invokingContext) invokingContext)
} }
@@ -106,36 +98,15 @@ class LiteralFunctionTranslator(context: TranslationContext) : AbstractTranslato
lambda.isLocal = true lambda.isLocal = true
invokingContext.addDeclarationStatement(lambda.makeStmt()) invokingContext.addDeclarationStatement(lambda.makeStmt())
lambda.fillCoroutineMetadata(invokingContext, continuationType) lambda.fillCoroutineMetadata(invokingContext, descriptor, continuationType)
lambda.name.staticRef = lambda lambda.name.staticRef = lambda
return getReferenceToLambda(invokingContext, descriptor, lambda.name) return getReferenceToLambda(invokingContext, descriptor, lambda.name)
} }
fun JsFunction.fillCoroutineMetadata(context: TranslationContext, continuationType: KotlinType?) { fun JsFunction.fillCoroutineMetadata(context: TranslationContext, descriptor: FunctionDescriptor, continuationType: KotlinType?) {
if (continuationType == null) return if (continuationType == null) return
val suspendPropertyDescriptor = context().currentModule.getPackage(COROUTINE_INTRINSICS_PACKAGE_FQ_NAME) fillCoroutineMetadata(context, descriptor, hasController = continuationType.isBuiltinExtensionFunctionalType, isLambda = true)
.memberScope.getContributedVariables(SUSPENDED_MARKER_NAME, NoLookupLocation.FROM_BACKEND).first()
val coroutineBaseClassRef = ReferenceTranslator.translateAsTypeReference(TranslationUtils.getCoroutineBaseClass(context), context)
fun getCoroutinePropertyName(id: String) =
context.getNameForDescriptor(TranslationUtils.getCoroutineProperty(context, id))
coroutineMetadata = CoroutineMetadata(
doResumeName = context.getNameForDescriptor(TranslationUtils.getCoroutineDoResumeFunction(context)),
resumeName = context.getNameForDescriptor(TranslationUtils.getCoroutineResumeFunction(context)),
suspendObjectRef = ReferenceTranslator.translateAsValueReference(suspendPropertyDescriptor, context()),
baseClassRef = coroutineBaseClassRef,
stateName = getCoroutinePropertyName("state"),
exceptionStateName = getCoroutinePropertyName("exceptionState"),
finallyPathName = getCoroutinePropertyName("finallyPath"),
resultName = getCoroutinePropertyName("result"),
exceptionName = getCoroutinePropertyName("exception"),
facadeName = getCoroutinePropertyName("facade"),
hasController = continuationType.isBuiltinExtensionFunctionalType
)
coroutineType = continuationType
} }
fun ValueParameterDescriptorImpl.WithDestructuringDeclaration.translate(context: TranslationContext): JsVars { fun ValueParameterDescriptorImpl.WithDestructuringDeclaration.translate(context: TranslationContext): JsVars {
@@ -149,7 +120,12 @@ class LiteralFunctionTranslator(context: TranslationContext) : AbstractTranslato
} }
private fun CallableMemberDescriptor.wrapContextForCoroutineIfNecessary(context: TranslationContext): TranslationContext { private fun CallableMemberDescriptor.wrapContextForCoroutineIfNecessary(context: TranslationContext): TranslationContext {
return if (isCoroutineLambda) context.innerContextWithDescriptorsAliased(mapOf(this to JsLiteral.THIS)) else context return if (isCoroutineLambda) {
context.innerContextWithDescriptorsAliased(mapOf(this to JsAstUtils.stateMachineReceiver()))
}
else {
context
}
} }
fun JsFunction.withCapturedParameters( fun JsFunction.withCapturedParameters(
@@ -539,4 +539,11 @@ public final class JsAstUtils {
public static JsExpression prototypeOf(@NotNull JsExpression expression) { public static JsExpression prototypeOf(@NotNull JsExpression expression) {
return pureFqn("prototype", expression); return pureFqn("prototype", expression);
} }
@NotNull
public static JsExpression stateMachineReceiver() {
JsNameRef result = new JsNameRef("$this$");
MetadataProperties.setCoroutineReceiver(result, true);
return result;
}
} }
@@ -17,15 +17,22 @@
package org.jetbrains.kotlin.js.translate.utils package org.jetbrains.kotlin.js.translate.utils
import com.intellij.util.SmartList import com.intellij.util.SmartList
import org.jetbrains.kotlin.backend.common.SUSPENDED_MARKER_NAME
import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.FunctionDescriptor import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.descriptors.PropertyDescriptor import org.jetbrains.kotlin.descriptors.PropertyDescriptor
import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor
import org.jetbrains.kotlin.descriptors.impl.AnonymousFunctionDescriptor
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
import org.jetbrains.kotlin.js.backend.ast.* import org.jetbrains.kotlin.js.backend.ast.*
import org.jetbrains.kotlin.js.backend.ast.metadata.CoroutineMetadata
import org.jetbrains.kotlin.js.backend.ast.metadata.coroutineMetadata
import org.jetbrains.kotlin.js.translate.context.Namer import org.jetbrains.kotlin.js.translate.context.Namer
import org.jetbrains.kotlin.js.translate.context.TranslationContext import org.jetbrains.kotlin.js.translate.context.TranslationContext
import org.jetbrains.kotlin.js.translate.reference.ReferenceTranslator import org.jetbrains.kotlin.js.translate.reference.ReferenceTranslator
import org.jetbrains.kotlin.js.translate.utils.TranslationUtils.simpleReturnFunction import org.jetbrains.kotlin.js.translate.utils.TranslationUtils.simpleReturnFunction
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.DescriptorUtils import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.KotlinType
@@ -119,4 +126,43 @@ fun TranslationContext.addAccessorsToPrototype(
val propertyName = getNameForDescriptor(propertyDescriptor) val propertyName = getNameForDescriptor(propertyDescriptor)
val defineProperty = JsAstUtils.defineProperty(prototypeRef, propertyName.ident, literal, program()) val defineProperty = JsAstUtils.defineProperty(prototypeRef, propertyName.ident, literal, program())
addDeclarationStatement(defineProperty.makeStmt()) addDeclarationStatement(defineProperty.makeStmt())
} }
fun FunctionDescriptor.requiresStateMachineTransformation(context: TranslationContext): Boolean =
this is AnonymousFunctionDescriptor ||
context.bindingContext()[BindingContext.CONTAINS_NON_TAIL_SUSPEND_CALLS, this] == true
fun JsFunction.fillCoroutineMetadata(
context: TranslationContext,
descriptor: FunctionDescriptor,
hasController: Boolean, isLambda: Boolean
) {
if (!descriptor.requiresStateMachineTransformation(context)) return
val suspendPropertyDescriptor = context.currentModule.getPackage(COROUTINES_INTRINSICS_PACKAGE_FQ_NAME)
.memberScope
.getContributedVariables(SUSPENDED_MARKER_NAME, NoLookupLocation.FROM_BACKEND).first()
val coroutineBaseClassRef = ReferenceTranslator.translateAsTypeReference(TranslationUtils.getCoroutineBaseClass(context), context)
fun getCoroutinePropertyName(id: String) =
context.getNameForDescriptor(TranslationUtils.getCoroutineProperty(context, id))
coroutineMetadata = CoroutineMetadata(
doResumeName = context.getNameForDescriptor(TranslationUtils.getCoroutineDoResumeFunction(context)),
resumeName = context.getNameForDescriptor(TranslationUtils.getCoroutineResumeFunction(context)),
suspendObjectRef = ReferenceTranslator.translateAsValueReference(suspendPropertyDescriptor, context),
baseClassRef = coroutineBaseClassRef,
stateName = getCoroutinePropertyName("state"),
exceptionStateName = getCoroutinePropertyName("exceptionState"),
finallyPathName = getCoroutinePropertyName("finallyPath"),
resultName = getCoroutinePropertyName("result"),
exceptionName = getCoroutinePropertyName("exception"),
facadeName = getCoroutinePropertyName("facade"),
hasController = hasController,
isLambda = isLambda,
hasReceiver = descriptor.dispatchReceiverParameter != null
)
}
private val COROUTINES_INTRINSICS_PACKAGE_FQ_NAME = FqName("kotlin.coroutines.intrinsics")