Fix generation of JS source maps for suspend functions

This commit is contained in:
Alexey Andreev
2017-05-04 18:45:51 +03:00
parent 28140b0883
commit 3077a0f640
8 changed files with 105 additions and 61 deletions
@@ -114,7 +114,8 @@ data class CoroutineMetadata(
val baseClassRef: JsExpression,
val suspendObjectRef: JsExpression,
val hasController: Boolean,
val hasReceiver: Boolean
val hasReceiver: Boolean,
val psiElement: PsiElement?
)
enum class TypeCheck {
@@ -1,5 +1,5 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
* Copyright 2010-2017 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.
@@ -83,8 +83,8 @@ class CoroutineBodyTransformer(private val program: JsProgram, private val conte
ifBlock.statements += x
val jointBlock = CoroutineBlock()
thenExitBlock.statements += stateAndJump(jointBlock)
elseExitBlock.statements += stateAndJump(jointBlock)
thenExitBlock.statements += stateAndJump(jointBlock, x)
elseExitBlock.statements += stateAndJump(jointBlock, x)
currentBlock = jointBlock
}
@@ -108,7 +108,7 @@ class CoroutineBodyTransformer(private val program: JsProgram, private val conte
accept(inner)
}
if (successor in referencedBlocks) {
currentBlock.statements += stateAndJump(successor)
currentBlock.statements += stateAndJump(successor, x)
currentBlock = successor
}
}
@@ -118,25 +118,25 @@ class CoroutineBodyTransformer(private val program: JsProgram, private val conte
override fun visitWhile(x: JsWhile) = splitIfNecessary(x) {
val successor = CoroutineBlock()
val bodyEntryBlock = CoroutineBlock()
currentStatements += stateAndJump(bodyEntryBlock)
currentStatements += stateAndJump(bodyEntryBlock, x)
currentBlock = bodyEntryBlock
if (x.condition != JsLiteral.TRUE) {
currentStatements += JsIf(JsAstUtils.notOptimized(x.condition), JsBlock(stateAndJump(successor))).apply { source = x.source }
currentStatements += JsIf(JsAstUtils.notOptimized(x.condition), JsBlock(stateAndJump(successor, x))).apply { source = x.source }
}
withBreakAndContinue(x, successor, bodyEntryBlock) {
x.body.accept(this)
}
currentStatements += stateAndJump(bodyEntryBlock)
currentStatements += stateAndJump(bodyEntryBlock, x)
currentBlock = successor
}
override fun visitDoWhile(x: JsDoWhile) = splitIfNecessary(x) {
val successor = CoroutineBlock()
val bodyEntryBlock = CoroutineBlock()
currentStatements += stateAndJump(bodyEntryBlock)
currentStatements += stateAndJump(bodyEntryBlock, x)
currentBlock = bodyEntryBlock
withBreakAndContinue(x, successor, bodyEntryBlock) {
@@ -144,10 +144,10 @@ class CoroutineBodyTransformer(private val program: JsProgram, private val conte
}
if (x.condition != JsLiteral.TRUE) {
val jsIf = JsIf(JsAstUtils.notOptimized(x.condition), JsBlock(stateAndJump(successor))).apply { source = x.source }
val jsIf = JsIf(JsAstUtils.notOptimized(x.condition), JsBlock(stateAndJump(successor, x))).apply { source = x.source }
currentStatements.add(jsIf)
}
currentBlock.statements += stateAndJump(bodyEntryBlock)
currentBlock.statements += stateAndJump(bodyEntryBlock, x)
currentBlock = successor
}
@@ -165,22 +165,22 @@ class CoroutineBodyTransformer(private val program: JsProgram, private val conte
val increment = CoroutineBlock()
val successor = CoroutineBlock()
val bodyEntryBlock = CoroutineBlock()
currentStatements += stateAndJump(bodyEntryBlock)
currentStatements += stateAndJump(bodyEntryBlock, x)
currentBlock = bodyEntryBlock
if (x.condition != null && x.condition != JsLiteral.TRUE) {
currentStatements += JsIf(JsAstUtils.notOptimized(x.condition), JsBlock(stateAndJump(successor))).apply { source = x.source }
currentStatements += JsIf(JsAstUtils.notOptimized(x.condition), JsBlock(stateAndJump(successor, x))).apply { source = x.source }
}
withBreakAndContinue(x, successor, increment) {
x.body.accept(this)
}
currentStatements += stateAndJump(increment)
currentStatements += stateAndJump(increment, x)
currentBlock = increment
x.incrementExpression?.let { JsExpressionStatement(it).accept(this) }
currentStatements += stateAndJump(bodyEntryBlock)
currentStatements += stateAndJump(bodyEntryBlock, x)
currentBlock = successor
}
@@ -189,7 +189,7 @@ class CoroutineBodyTransformer(private val program: JsProgram, private val conte
val targetStatement = breakContinueTargetStatements[x]!!
val (targetBlock, targetTryDepth) = breakTargets[targetStatement]!!
referencedBlocks += targetBlock
jumpWithFinally(targetTryDepth + 1, targetBlock)
jumpWithFinally(targetTryDepth + 1, targetBlock, x)
currentStatements += jump()
}
@@ -197,7 +197,7 @@ class CoroutineBodyTransformer(private val program: JsProgram, private val conte
val targetStatement = breakContinueTargetStatements[x]!!
val (targetBlock, targetTryDepth) = continueTargets[targetStatement]!!
referencedBlocks += targetBlock
jumpWithFinally(targetTryDepth + 1, targetBlock)
jumpWithFinally(targetTryDepth + 1, targetBlock, x)
currentStatements += jump()
}
@@ -205,10 +205,10 @@ class CoroutineBodyTransformer(private val program: JsProgram, private val conte
* When we perform break, continue or return, we can leave try blocks, so we should update $exceptionHandler correspondingly.
* Also, these try blocks can contain finally clauses, therefore we need to update $finallyPath as well.
*/
private fun jumpWithFinally(targetTryDepth: Int, successor: CoroutineBlock) {
private fun jumpWithFinally(targetTryDepth: Int, successor: CoroutineBlock, fromNode: JsNode) {
if (targetTryDepth < tryStack.size) {
val tryBlock = tryStack[targetTryDepth]
currentStatements += exceptionState(tryBlock.catchBlock)
currentStatements += exceptionState(tryBlock.catchBlock, fromNode)
}
val relativeFinallyPath = relativeFinallyPath(targetTryDepth)
@@ -216,7 +216,7 @@ class CoroutineBodyTransformer(private val program: JsProgram, private val conte
if (fullPath.size > 1) {
currentStatements += updateFinallyPath(fullPath.drop(1))
}
currentStatements += state(fullPath[0])
currentStatements += state(fullPath[0], fromNode)
}
override fun visitTry(x: JsTry) = splitIfNecessary(x) {
@@ -231,19 +231,19 @@ class CoroutineBodyTransformer(private val program: JsProgram, private val conte
val oldCatchBlock = currentCatchBlock
currentCatchBlock = catchBlock
currentStatements += exceptionState(catchBlock)
currentStatements += exceptionState(catchBlock, x)
x.tryBlock.statements.forEach { it.accept(this) }
currentStatements += exceptionState(oldCatchBlock)
currentStatements += exceptionState(oldCatchBlock, x)
currentCatchBlock = oldCatchBlock
if (finallyNode != null) {
currentStatements += updateFinallyPath(listOf(successor))
currentStatements += stateAndJump(finallyBlock)
currentStatements += stateAndJump(finallyBlock, x)
}
else {
currentStatements += stateAndJump(successor)
currentStatements += stateAndJump(successor, x)
}
// Handle catch node
@@ -251,10 +251,10 @@ class CoroutineBodyTransformer(private val program: JsProgram, private val conte
if (finallyNode != null) {
currentStatements += updateFinallyPath(listOf(oldCatchBlock))
currentStatements += if (catchNode != null) exceptionState(finallyBlock) else stateAndJump(finallyBlock)
currentStatements += if (catchNode != null) exceptionState(finallyBlock, x) else stateAndJump(finallyBlock, x)
}
else {
currentStatements += if (catchNode != null) exceptionState(oldCatchBlock) else stateAndJump(oldCatchBlock)
currentStatements += if (catchNode != null) exceptionState(oldCatchBlock, x) else stateAndJump(oldCatchBlock, x)
}
if (catchNode != null) {
@@ -263,11 +263,11 @@ class CoroutineBodyTransformer(private val program: JsProgram, private val conte
catchNode.body.statements.forEach { it.accept(this) }
if (finallyNode == null) {
currentStatements += stateAndJump(successor)
currentStatements += stateAndJump(successor, x)
}
else {
currentStatements += updateFinallyPath(listOf(successor))
currentStatements += stateAndJump(finallyBlock)
currentStatements += stateAndJump(finallyBlock, x)
}
}
@@ -314,7 +314,7 @@ class CoroutineBodyTransformer(private val program: JsProgram, private val conte
val isInFinally = hasEnclosingFinallyBlock()
if (isInFinally) {
val returnBlock = CoroutineBlock()
jumpWithFinally(0, returnBlock)
jumpWithFinally(0, returnBlock, x)
val returnExpression = x.expression
val returnFieldRef = if (returnExpression != null) {
val ref = JsNameRef(context.returnValueFieldName, JsAstUtils.stateMachineReceiver())
@@ -343,47 +343,51 @@ class CoroutineBodyTransformer(private val program: JsProgram, private val conte
if (assignment != null) {
val rhs = assignment.second
if (rhs.isSuspend) {
handleSuspend(expression)
handleSuspend(expression, rhs)
return null
}
}
else if (expression.isSuspend) {
handleSuspend(expression)
handleSuspend(expression, expression)
return null
}
return expression
}
private fun handleSuspend(invocation: JsExpression) {
private fun handleSuspend(invocation: JsExpression, sourceNode: JsExpression) {
val psi = sourceNode.source ?: invocation.source
val nextBlock = CoroutineBlock()
currentStatements += state(nextBlock)
currentStatements += state(nextBlock, invocation)
val resultRef = JsNameRef(context.metadata.resultName, JsAstUtils.stateMachineReceiver()).apply {
sideEffects = SideEffectKind.DEPENDS_ON_STATE
}
val invocationStatement = JsAstUtils.assignment(resultRef, invocation).makeStmt()
val suspendCondition = JsAstUtils.equality(resultRef.deepCopy(), context.metadata.suspendObjectRef.deepCopy())
val suspendIfNeeded = JsIf(suspendCondition, JsReturn(context.metadata.suspendObjectRef.deepCopy()))
currentStatements += listOf(invocationStatement, suspendIfNeeded, JsBreak())
val suspendCondition = JsAstUtils.equality(resultRef.deepCopy(), context.metadata.suspendObjectRef.deepCopy()).source(psi)
val suspendIfNeeded = JsIf(suspendCondition, JsReturn(context.metadata.suspendObjectRef.deepCopy().source(psi)))
currentStatements += listOf(invocationStatement, suspendIfNeeded, JsBreak().apply { source = psi })
currentBlock = nextBlock
}
private fun state(target: CoroutineBlock): List<JsStatement> {
private fun state(target: CoroutineBlock, fromExpression: JsNode): List<JsStatement> {
val placeholder = JsDebugger()
placeholder.targetBlock = target
placeholder.source = fromExpression.source
return listOf(placeholder)
}
private fun jump() = JsContinue()
private fun stateAndJump(target: CoroutineBlock): List<JsStatement> {
return state(target) + jump()
private fun stateAndJump(target: CoroutineBlock, fromNode: JsNode): List<JsStatement> {
return state(target, fromNode) + jump()
}
private fun exceptionState(target: CoroutineBlock): List<JsStatement> {
private fun exceptionState(target: CoroutineBlock, fromNode: JsNode): List<JsStatement> {
val placeholder = JsDebugger()
placeholder.targetExceptionBlock = target
placeholder.source = fromNode
return listOf(placeholder)
}
@@ -1,5 +1,5 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
* Copyright 2010-2017 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.
@@ -16,6 +16,7 @@
package org.jetbrains.kotlin.js.coroutine
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.js.backend.ast.*
import org.jetbrains.kotlin.js.backend.ast.metadata.coroutineMetadata
import org.jetbrains.kotlin.js.inline.clean.FunctionPostProcessor
@@ -57,6 +58,8 @@ class CoroutineFunctionTransformer(private val program: JsProgram, private val f
statements: MutableList<JsStatement>,
globalCatchBlockIndex: Int
) {
val psiElement = context.metadata.psiElement
val constructor = JsFunction(function.scope.parent, JsBlock(), "Continuation")
constructor.name = className
if (context.metadata.hasReceiver) {
@@ -80,17 +83,17 @@ class CoroutineFunctionTransformer(private val program: JsProgram, private val f
constructor.body.statements.run {
val baseClass = context.metadata.baseClassRef.deepCopy()
this += JsInvocation(Namer.getFunctionCallRef(baseClass), JsLiteral.THIS, interceptorRef).makeStmt()
this += JsInvocation(Namer.getFunctionCallRef(baseClass), JsLiteral.THIS, interceptorRef).source(psiElement).makeStmt()
if (controllerName != null) {
assignToField(context.controllerFieldName, controllerName.makeRef())
assignToField(context.controllerFieldName, controllerName.makeRef(), psiElement)
}
assignToField(context.metadata.exceptionStateName, program.getNumberLiteral(globalCatchBlockIndex))
assignToField(context.metadata.exceptionStateName, program.getNumberLiteral(globalCatchBlockIndex), psiElement)
if (context.metadata.hasReceiver) {
assignToField(context.receiverFieldName, context.receiverFieldName.makeRef())
assignToField(context.receiverFieldName, context.receiverFieldName.makeRef(), psiElement)
}
for (localVariable in localVariables) {
val value = if (localVariable !in parameterNames) Namer.getUndefinedExpression() else localVariable.makeRef()
assignToField(context.getFieldName(localVariable), value)
assignToField(context.getFieldName(localVariable), value, psiElement)
}
}
@@ -144,7 +147,8 @@ class CoroutineFunctionTransformer(private val program: JsProgram, private val f
}
private fun generateCoroutineInstantiation(context: CoroutineTransformationContext) {
val instantiation = JsNew(className.makeRef())
val psiElement = context.metadata.psiElement
val instantiation = JsNew(className.makeRef()).apply { source = psiElement }
if (context.metadata.hasReceiver) {
instantiation.arguments += JsLiteral.THIS
}
@@ -163,9 +167,13 @@ class CoroutineFunctionTransformer(private val program: JsProgram, private val f
val instanceName = JsScope.declareTemporaryName("instance")
functionWithBody.body.statements += JsAstUtils.newVar(instanceName, instantiation)
val invokeResume = JsReturn(JsInvocation(JsNameRef(context.metadata.doResumeName, instanceName.makeRef()), JsLiteral.NULL))
val invokeResume = JsReturn(JsInvocation(JsNameRef(context.metadata.doResumeName, instanceName.makeRef()), JsLiteral.NULL)
.source(psiElement))
functionWithBody.body.statements += JsIf(suspendedName.makeRef(), JsReturn(instanceName.makeRef()), invokeResume)
functionWithBody.body.statements += JsIf(
suspendedName.makeRef().source(psiElement),
JsReturn(instanceName.makeRef().source(psiElement)),
invokeResume)
}
private fun generateCoroutineBody(
@@ -208,8 +216,8 @@ class CoroutineFunctionTransformer(private val program: JsProgram, private val f
})
}
private fun MutableList<JsStatement>.assignToField(fieldName: JsName, value: JsExpression) {
this += JsAstUtils.assignment(JsNameRef(fieldName, JsLiteral.THIS), value).makeStmt()
private fun MutableList<JsStatement>.assignToField(fieldName: JsName, value: JsExpression, psiElement: PsiElement?) {
this += JsAstUtils.assignment(JsNameRef(fieldName, JsLiteral.THIS), value).source(psiElement).makeStmt()
}
private fun MutableList<JsStatement>.assignToPrototype(fieldName: JsName, value: JsExpression) {
@@ -1,5 +1,5 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
* Copyright 2010-2017 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.
@@ -122,7 +122,7 @@ fun List<CoroutineBlock>.replaceCoroutineFlowStatements(context: CoroutineTransf
if (target != null) {
val lhs = JsNameRef(context.metadata.stateName, JsAstUtils.stateMachineReceiver())
val rhs = program.getNumberLiteral(blockIndexes[target]!!)
ctx.replaceMe(JsExpressionStatement(JsAstUtils.assignment(lhs, rhs)).apply {
ctx.replaceMe(JsExpressionStatement(JsAstUtils.assignment(lhs, rhs).source(x.source)).apply {
targetBlock = true
})
}
@@ -131,7 +131,7 @@ fun List<CoroutineBlock>.replaceCoroutineFlowStatements(context: CoroutineTransf
if (exceptionTarget != null) {
val lhs = JsNameRef(context.metadata.exceptionStateName, JsAstUtils.stateMachineReceiver())
val rhs = program.getNumberLiteral(blockIndexes[exceptionTarget]!!)
ctx.replaceMe(JsExpressionStatement(JsAstUtils.assignment(lhs, rhs)).apply {
ctx.replaceMe(JsExpressionStatement(JsAstUtils.assignment(lhs, rhs).source(x.source)).apply {
targetExceptionBlock = true
})
}
@@ -141,7 +141,7 @@ fun List<CoroutineBlock>.replaceCoroutineFlowStatements(context: CoroutineTransf
if (finallyPath.isNotEmpty()) {
val lhs = JsNameRef(context.metadata.finallyPathName, JsAstUtils.stateMachineReceiver())
val rhs = JsArrayLiteral(finallyPath.map { program.getNumberLiteral(blockIndexes[it]!!) })
ctx.replaceMe(JsExpressionStatement(JsAstUtils.assignment(lhs, rhs)).apply {
ctx.replaceMe(JsExpressionStatement(JsAstUtils.assignment(lhs, rhs).source(x.source)).apply {
this.finallyPath = true
})
}
@@ -222,12 +222,14 @@ fun JsBlock.replaceSpecialReferences(context: CoroutineTransformationContext) {
x.coroutineController -> {
ctx.replaceMe(JsNameRef(context.controllerFieldName, x.qualifier).apply {
source = x.source
sideEffects = SideEffectKind.PURE
})
}
x.coroutineResult -> {
ctx.replaceMe(JsNameRef(context.metadata.resultName, x.qualifier).apply {
source = x.source
sideEffects = SideEffectKind.DEPENDS_ON_STATE
})
}
@@ -262,7 +264,7 @@ fun JsBlock.replaceLocalVariables(context: CoroutineTransformationContext, local
override fun endVisit(x: JsNameRef, ctx: JsContext<in JsNode>) {
if (x.qualifier == null && x.name in localVariables) {
val fieldName = context.getFieldName(x.name!!)
ctx.replaceMe(JsNameRef(fieldName, JsLiteral.THIS))
ctx.replaceMe(JsNameRef(fieldName, JsLiteral.THIS).source(x.source))
}
}
@@ -42,6 +42,12 @@ public class JsLineNumberTestGenerated extends AbstractJsLineNumberTest {
doTest(fileName);
}
@TestMetadata("coroutine.kt")
public void testCoroutine() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/lineNumbers/coroutine.kt");
doTest(fileName);
}
@TestMetadata("inlining.kt")
public void testInlining() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/lineNumbers/inlining.kt");
@@ -1,5 +1,5 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
* Copyright 2010-2017 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.
@@ -136,10 +136,14 @@ private fun translateFunctionCall(
if (resolvedCall.resultingDescriptor.isSuspend) {
if (context.isInStateMachine) {
context.currentBlock.statements += JsAstUtils.asSyntheticStatement(callExpression.apply { isSuspend = true })
val coroutineRef = TranslationUtils.translateContinuationArgument(context)
context.currentBlock.statements += JsAstUtils.asSyntheticStatement(callExpression.apply {
isSuspend = true
source = resolvedCall.call.callElement
})
val coroutineRef = TranslationUtils.translateContinuationArgument(context).apply { source = resolvedCall.call.callElement }
return context.defineTemporary(JsNameRef("\$\$coroutineResult\$\$", coroutineRef).apply {
sideEffects = SideEffectKind.DEPENDS_ON_STATE
source = resolvedCall.call.callElement
coroutineResult = true
})
}
@@ -1,5 +1,5 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
* Copyright 2010-2017 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.
@@ -36,6 +36,7 @@ import org.jetbrains.kotlin.js.translate.utils.TranslationUtils.simpleReturnFunc
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.descriptorUtil.hasOrInheritsParametersWithDefaultValue
import org.jetbrains.kotlin.resolve.source.getPsi
import org.jetbrains.kotlin.types.KotlinType
fun generateDelegateCall(
@@ -184,6 +185,7 @@ fun JsFunction.fillCoroutineMetadata(
resultName = getCoroutinePropertyName("result"),
exceptionName = getCoroutinePropertyName("exception"),
hasController = hasController,
hasReceiver = descriptor.dispatchReceiverParameter != null
hasReceiver = descriptor.dispatchReceiverParameter != null,
psiElement = descriptor.source.getPsi()
)
}
+17
View File
@@ -0,0 +1,17 @@
import kotlin.coroutines.experimental.*
import kotlin.coroutines.experimental.intrinsics.*
suspend fun foo(value: Int): Int = suspendCoroutineOrReturn { c ->
c.resume(value)
COROUTINE_SUSPENDED
}
suspend fun bar(): Unit {
println("!")
val a = foo(2)
println("!")
val b = foo(3)
println(a + b)
}
// LINES: 4 5 6 * 9 9 9 9 9 9 9 9 * 10 11 11 11 11 11 * 11 12 13 13 13 13 13 13 14