JS: refactor coroutine transformer
This commit is contained in:
@@ -18,29 +18,25 @@ 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.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(val program: JsProgram, val scope: JsScope, val throwFunctionName: JsName?) : RecursiveJsVisitor() {
|
||||
val entryBlock = CoroutineBlock()
|
||||
val globalCatchBlock = CoroutineBlock()
|
||||
class CoroutineBodyTransformer(
|
||||
private val program: JsProgram,
|
||||
private val context: CoroutineTransformationContext,
|
||||
private val throwFunctionName: JsName?
|
||||
) : RecursiveJsVisitor() {
|
||||
private val entryBlock = context.entryBlock
|
||||
private val globalCatchBlock = context.globalCatchBlock
|
||||
private var currentBlock = entryBlock
|
||||
private val currentStatements: MutableList<JsStatement>
|
||||
get() = currentBlock.statements
|
||||
val resultFieldName by lazy { scope.declareFreshName("\$result") }
|
||||
val exceptionFieldName by lazy { scope.declareFreshName("\$exception") }
|
||||
val stateFieldName by lazy { scope.declareFreshName("\$state") }
|
||||
val controllerFieldName by lazy { scope.declareFreshName("\$controller") }
|
||||
val exceptionStateName by lazy { scope.declareFreshName("\$exceptionState") }
|
||||
val finallyPathFieldName by lazy { scope.declareFreshName("\$finallyPath") }
|
||||
private val breakTargets = mutableMapOf<JsName, JumpTarget>()
|
||||
private val continueTargets = mutableMapOf<JsName, JumpTarget>()
|
||||
private var defaultBreakTarget: JumpTarget = JumpTarget(JsEmpty, 0)
|
||||
private var defaultContinueTarget: JumpTarget = JumpTarget(JsEmpty, 0)
|
||||
private val breakContinueTargetStatements = mutableMapOf<JsContinue, JsStatement>()
|
||||
private val breakTargets = mutableMapOf<JsStatement, JumpTarget>()
|
||||
private val continueTargets = mutableMapOf<JsStatement, JumpTarget>()
|
||||
private val referencedBlocks = mutableSetOf<CoroutineBlock>()
|
||||
private val breakBlocks = mutableMapOf<JsStatement, CoroutineBlock>()
|
||||
private val continueBlocks = mutableMapOf<JsStatement, CoroutineBlock>()
|
||||
private lateinit var nodesToSplit: Set<JsNode>
|
||||
private var currentCatchBlock = globalCatchBlock
|
||||
private val tryStack = mutableListOf(TryBlock(globalCatchBlock, null))
|
||||
@@ -54,140 +50,19 @@ class CoroutineBodyTransformer(val program: JsProgram, val scope: JsScope, val t
|
||||
get() = tryStack.lastIndex
|
||||
|
||||
fun preProcess(node: JsNode) {
|
||||
val nodes = mutableSetOf<JsNode>()
|
||||
|
||||
val visitor = object : RecursiveJsVisitor() {
|
||||
var childrenInSet = false
|
||||
|
||||
override fun visitInvocation(invocation: JsInvocation) {
|
||||
super.visitInvocation(invocation)
|
||||
if (invocation.isSuspend) {
|
||||
nodes += invocation
|
||||
childrenInSet = true
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitReturn(x: JsReturn) {
|
||||
super.visitReturn(x)
|
||||
nodes += x
|
||||
childrenInSet = true
|
||||
}
|
||||
|
||||
override fun visitBreak(x: JsBreak) {
|
||||
super.visitBreak(x)
|
||||
|
||||
// It's a simplification
|
||||
// TODO: don't split break and continue statements when possible
|
||||
nodes += x
|
||||
childrenInSet = true
|
||||
}
|
||||
|
||||
override fun visitContinue(x: JsContinue) {
|
||||
super.visitContinue(x)
|
||||
nodes += x
|
||||
childrenInSet = true
|
||||
}
|
||||
|
||||
override fun visitElement(node: JsNode) {
|
||||
val oldChildrenInSet = childrenInSet
|
||||
childrenInSet = false
|
||||
|
||||
node.acceptChildren(this)
|
||||
|
||||
if (childrenInSet) {
|
||||
nodes += node
|
||||
}
|
||||
else {
|
||||
childrenInSet = oldChildrenInSet
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
visitor.accept(node)
|
||||
visitor.accept(node)
|
||||
|
||||
nodesToSplit = nodes
|
||||
|
||||
breakContinueTargetStatements += node.collectBreakContinueTargets()
|
||||
nodesToSplit = node.collectNodesToSplit()
|
||||
currentStatements += exceptionState(currentCatchBlock)
|
||||
}
|
||||
|
||||
fun postProcess(): List<CoroutineBlock> {
|
||||
currentBlock.statements += JsReturn()
|
||||
val graph = buildBlockGraph()
|
||||
val graph = entryBlock.buildGraph()
|
||||
val orderedBlocks = DFS.topologicalOrder(listOf(entryBlock)) { graph[it].orEmpty() }
|
||||
val blockIndexes = orderedBlocks.withIndex().associate { (index, block) -> Pair(block, index) }
|
||||
|
||||
val blockReplacementVisitor = object : JsVisitorWithContextImpl() {
|
||||
override fun endVisit(x: JsDebugger, ctx: JsContext<in JsStatement>) {
|
||||
val target = x.targetBlock
|
||||
if (target != null) {
|
||||
val lhs = JsNameRef(stateFieldName, JsLiteral.THIS)
|
||||
val rhs = program.getNumberLiteral(blockIndexes[target]!!)
|
||||
ctx.replaceMe(JsExpressionStatement(JsAstUtils.assignment(lhs, rhs)).apply {
|
||||
targetBlock = true
|
||||
})
|
||||
}
|
||||
|
||||
val exceptionTarget = x.targetExceptionBlock
|
||||
if (exceptionTarget != null) {
|
||||
val lhs = JsNameRef(exceptionStateName, JsLiteral.THIS)
|
||||
val rhs = program.getNumberLiteral(blockIndexes[exceptionTarget]!!)
|
||||
ctx.replaceMe(JsExpressionStatement(JsAstUtils.assignment(lhs, rhs)).apply {
|
||||
targetExceptionBlock = true
|
||||
})
|
||||
}
|
||||
|
||||
val finallyPath = x.finallyPath
|
||||
if (finallyPath != null) {
|
||||
if (finallyPath.isNotEmpty()) {
|
||||
val lhs = JsNameRef(finallyPathFieldName, JsLiteral.THIS)
|
||||
val rhs = JsArrayLiteral(finallyPath.map { program.getNumberLiteral(blockIndexes[it]!!) })
|
||||
ctx.replaceMe(JsExpressionStatement(JsAstUtils.assignment(lhs, rhs)).apply {
|
||||
this.finallyPath = true
|
||||
})
|
||||
}
|
||||
else {
|
||||
ctx.removeMe()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (block in orderedBlocks) {
|
||||
blockReplacementVisitor.accept(block.jsBlock)
|
||||
}
|
||||
|
||||
orderedBlocks.replaceCoroutineFlowStatements(context, program)
|
||||
return orderedBlocks
|
||||
}
|
||||
|
||||
private fun buildBlockGraph(): Map<CoroutineBlock, Set<CoroutineBlock>> {
|
||||
// That's a little more than DFS due to need of tracking finally paths
|
||||
|
||||
val visitedBlocks = mutableSetOf<CoroutineBlock>()
|
||||
val graph = mutableMapOf<CoroutineBlock, MutableSet<CoroutineBlock>>()
|
||||
|
||||
fun visitBlock(block: CoroutineBlock) {
|
||||
if (block in visitedBlocks) return
|
||||
|
||||
for (finallyPath in block.collectFinallyPaths()) {
|
||||
for ((finallySource, finallyTarget) in (listOf(block) + finallyPath).zip(finallyPath)) {
|
||||
if (graph.getOrPut(finallySource) { mutableSetOf() }.add(finallyTarget)) {
|
||||
visitedBlocks -= finallySource
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
visitedBlocks += block
|
||||
|
||||
val successors = graph.getOrPut(block) { mutableSetOf() }
|
||||
successors += block.collectTargetBlocks()
|
||||
successors.forEach(::visitBlock)
|
||||
}
|
||||
|
||||
visitBlock(entryBlock)
|
||||
|
||||
return graph
|
||||
}
|
||||
|
||||
override fun visitBlock(x: JsBlock) = splitIfNecessary(x) {
|
||||
for (statement in x.statements) {
|
||||
statement.accept(this)
|
||||
@@ -220,15 +95,13 @@ class CoroutineBodyTransformer(val program: JsProgram, val scope: JsScope, val t
|
||||
override fun visitLabel(x: JsLabel) {
|
||||
val inner = x.statement
|
||||
when (inner) {
|
||||
is JsDoWhile -> handleDoWhile(inner, x.name)
|
||||
|
||||
is JsWhile -> handleWhile(inner, x.name)
|
||||
|
||||
is JsFor -> handleFor(inner, x.name)
|
||||
is JsWhile,
|
||||
is JsDoWhile,
|
||||
is JsFor -> inner.accept(this)
|
||||
|
||||
else -> splitIfNecessary(x) {
|
||||
val successor = CoroutineBlock()
|
||||
withBreakAndContinue(x.name, x.statement, successor, null) {
|
||||
withBreakAndContinue(x.statement, successor, null) {
|
||||
accept(inner)
|
||||
}
|
||||
if (successor in referencedBlocks) {
|
||||
@@ -239,15 +112,13 @@ class CoroutineBodyTransformer(val program: JsProgram, val scope: JsScope, val t
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitWhile(x: JsWhile) = handleWhile(x, null)
|
||||
|
||||
private fun handleWhile(x: JsWhile, label: JsName?) = splitIfNecessary(x) {
|
||||
override fun visitWhile(x: JsWhile) = splitIfNecessary(x) {
|
||||
val predecessor = currentBlock
|
||||
val successor = CoroutineBlock()
|
||||
|
||||
val bodyEntryBlock = CoroutineBlock()
|
||||
currentBlock = bodyEntryBlock
|
||||
withBreakAndContinue(label, x, successor, bodyEntryBlock) {
|
||||
withBreakAndContinue(x, successor, bodyEntryBlock) {
|
||||
x.body.accept(this)
|
||||
}
|
||||
|
||||
@@ -260,15 +131,13 @@ class CoroutineBodyTransformer(val program: JsProgram, val scope: JsScope, val t
|
||||
currentBlock = successor
|
||||
}
|
||||
|
||||
override fun visitDoWhile(x: JsDoWhile) = handleDoWhile(x, null)
|
||||
|
||||
private fun handleDoWhile(x: JsDoWhile, label: JsName?) = splitIfNecessary(x) {
|
||||
override fun visitDoWhile(x: JsDoWhile) = splitIfNecessary(x) {
|
||||
val predecessor = currentBlock
|
||||
val successor = CoroutineBlock()
|
||||
|
||||
val bodyEntryBlock = CoroutineBlock()
|
||||
currentBlock = bodyEntryBlock
|
||||
withBreakAndContinue(label, x, successor, bodyEntryBlock) {
|
||||
withBreakAndContinue(x, successor, bodyEntryBlock) {
|
||||
x.body.accept(this)
|
||||
}
|
||||
|
||||
@@ -281,9 +150,7 @@ class CoroutineBodyTransformer(val program: JsProgram, val scope: JsScope, val t
|
||||
currentBlock = successor
|
||||
}
|
||||
|
||||
override fun visitFor(x: JsFor) = handleFor(x, null)
|
||||
|
||||
private fun handleFor(x: JsFor, label: JsName?) = splitIfNecessary(x) {
|
||||
override fun visitFor(x: JsFor) = splitIfNecessary(x) {
|
||||
x.initExpression?.let {
|
||||
JsExpressionStatement(it).accept(this)
|
||||
}
|
||||
@@ -299,7 +166,7 @@ class CoroutineBodyTransformer(val program: JsProgram, val scope: JsScope, val t
|
||||
|
||||
val bodyEntryBlock = CoroutineBlock()
|
||||
currentBlock = bodyEntryBlock
|
||||
withBreakAndContinue(label, x, successor, predecessor) {
|
||||
withBreakAndContinue(x, successor, predecessor) {
|
||||
x.body.accept(this)
|
||||
}
|
||||
val bodyExitBlock = currentBlock
|
||||
@@ -320,32 +187,17 @@ class CoroutineBodyTransformer(val program: JsProgram, val scope: JsScope, val t
|
||||
}
|
||||
|
||||
override fun visitBreak(x: JsBreak) {
|
||||
val targetLabel = x.label?.name
|
||||
val (targetStatement, targetTryDepth) = if (targetLabel == null) {
|
||||
defaultBreakTarget
|
||||
}
|
||||
else {
|
||||
breakTargets[targetLabel]!!
|
||||
}
|
||||
|
||||
val targetBlock = breakBlocks[targetStatement]!!
|
||||
val targetStatement = breakContinueTargetStatements[x]!!
|
||||
val (targetBlock, targetTryDepth) = breakTargets[targetStatement]!!
|
||||
referencedBlocks += targetBlock
|
||||
jumpWithFinally(targetTryDepth + 1, targetBlock)
|
||||
currentStatements += jump()
|
||||
}
|
||||
|
||||
override fun visitContinue(x: JsContinue) {
|
||||
val targetLabel = x.label?.name
|
||||
val (targetStatement, targetTryDepth) = if (targetLabel == null) {
|
||||
defaultContinueTarget
|
||||
}
|
||||
else {
|
||||
continueTargets[targetLabel]!!
|
||||
}
|
||||
|
||||
val targetBlock = continueBlocks[targetStatement]!!
|
||||
val targetStatement = breakContinueTargetStatements[x]!!
|
||||
val (targetBlock, targetTryDepth) = continueTargets[targetStatement]!!
|
||||
referencedBlocks += targetBlock
|
||||
|
||||
jumpWithFinally(targetTryDepth + 1, targetBlock)
|
||||
currentStatements += jump()
|
||||
}
|
||||
@@ -403,7 +255,7 @@ class CoroutineBodyTransformer(val program: JsProgram, val scope: JsScope, val t
|
||||
}
|
||||
|
||||
if (catchNode != null) {
|
||||
currentStatements += JsAstUtils.newVar(catchNode.parameter.name, JsNameRef(exceptionFieldName, JsLiteral.THIS))
|
||||
currentStatements += JsAstUtils.newVar(catchNode.parameter.name, JsNameRef(context.exceptionFieldName, JsLiteral.THIS))
|
||||
catchNode.body.statements.forEach { it.accept(this) }
|
||||
|
||||
if (finallyNode == null) {
|
||||
@@ -429,8 +281,8 @@ class CoroutineBodyTransformer(val program: JsProgram, val scope: JsScope, val t
|
||||
}
|
||||
|
||||
private fun generateFinallyExit() {
|
||||
val finallyPathRef = JsNameRef(finallyPathFieldName, JsLiteral.THIS)
|
||||
val stateRef = JsNameRef(stateFieldName, JsLiteral.THIS)
|
||||
val finallyPathRef = JsNameRef(context.finallyPathFieldName, JsLiteral.THIS)
|
||||
val stateRef = JsNameRef(context.stateFieldName, JsLiteral.THIS)
|
||||
val nextState = JsInvocation(JsNameRef("shift", finallyPathRef))
|
||||
currentStatements += JsAstUtils.assignment(stateRef, nextState).makeStmt()
|
||||
currentStatements += jump()
|
||||
@@ -469,7 +321,7 @@ class CoroutineBodyTransformer(val program: JsProgram, val scope: JsScope, val t
|
||||
|
||||
override fun visitThrow(x: JsThrow) {
|
||||
if (throwFunctionName != null) {
|
||||
val methodRef = JsNameRef(throwFunctionName, JsNameRef(controllerFieldName, JsLiteral.THIS))
|
||||
val methodRef = JsNameRef(throwFunctionName, JsNameRef(context.controllerFieldName, JsLiteral.THIS))
|
||||
val invocation = JsInvocation(methodRef, x.expression).apply {
|
||||
source = x.source
|
||||
}
|
||||
@@ -547,55 +399,20 @@ class CoroutineBodyTransformer(val program: JsProgram, val scope: JsScope, val t
|
||||
}
|
||||
|
||||
private fun withBreakAndContinue(
|
||||
label: JsName?,
|
||||
statement: JsStatement,
|
||||
breakBlock: CoroutineBlock,
|
||||
continueBlock: CoroutineBlock? = null,
|
||||
action: () -> Unit
|
||||
) {
|
||||
breakBlocks[statement] = breakBlock
|
||||
breakTargets[statement] = JumpTarget(breakBlock, currentTryDepth)
|
||||
if (continueBlock != null) {
|
||||
continueBlocks[statement] = continueBlock
|
||||
}
|
||||
|
||||
val oldDefaultBreakTarget = defaultBreakTarget
|
||||
val oldDefaultContinueTarget = defaultContinueTarget
|
||||
val (oldBreakTarget, oldContinueTarget) = if (label != null) {
|
||||
Pair(breakTargets[label], continueTargets[label])
|
||||
}
|
||||
else {
|
||||
Pair(null, null)
|
||||
}
|
||||
|
||||
defaultBreakTarget = JumpTarget(statement, currentTryDepth)
|
||||
if (label != null) {
|
||||
breakTargets[label] = JumpTarget(statement, currentTryDepth)
|
||||
if (continueBlock != null) {
|
||||
continueTargets[label] = JumpTarget(statement, currentTryDepth)
|
||||
}
|
||||
}
|
||||
if (continueBlock != null) {
|
||||
defaultContinueTarget = JumpTarget(statement, currentTryDepth)
|
||||
continueTargets[statement] = JumpTarget(continueBlock, currentTryDepth)
|
||||
}
|
||||
|
||||
action()
|
||||
|
||||
defaultBreakTarget = oldDefaultBreakTarget
|
||||
defaultContinueTarget = oldDefaultContinueTarget
|
||||
if (label != null) {
|
||||
if (oldBreakTarget == null) {
|
||||
breakTargets.keys -= label
|
||||
}
|
||||
else {
|
||||
breakTargets[label] = oldBreakTarget
|
||||
}
|
||||
if (oldContinueTarget == null) {
|
||||
continueTargets.keys -= label
|
||||
}
|
||||
else {
|
||||
continueTargets[label] = oldContinueTarget
|
||||
}
|
||||
}
|
||||
breakTargets.keys -= statement
|
||||
continueTargets.keys -= statement
|
||||
}
|
||||
|
||||
private fun relativeFinallyPath(targetTryDepth: Int) = tryStack
|
||||
@@ -604,28 +421,8 @@ class CoroutineBodyTransformer(val program: JsProgram, val scope: JsScope, val t
|
||||
.reversed()
|
||||
|
||||
private fun hasEnclosingFinallyBlock() = tryStack.any { it.finallyBlock != null }
|
||||
}
|
||||
|
||||
private data class JumpTarget(val statement: JsStatement, val tryDepth: Int)
|
||||
|
||||
private class TryBlock(val catchBlock: CoroutineBlock, val finallyBlock: CoroutineBlock?)
|
||||
|
||||
private fun CoroutineBlock.collectTargetBlocks(): Set<CoroutineBlock> {
|
||||
val targetBlocks = mutableSetOf<CoroutineBlock>()
|
||||
jsBlock.accept(object : RecursiveJsVisitor() {
|
||||
override fun visitDebugger(x: JsDebugger) {
|
||||
targetBlocks += x.targetExceptionBlock.singletonOrEmptyList() + x.targetBlock.singletonOrEmptyList()
|
||||
}
|
||||
})
|
||||
return targetBlocks
|
||||
}
|
||||
|
||||
private fun CoroutineBlock.collectFinallyPaths(): List<List<CoroutineBlock>> {
|
||||
val finallyPaths = mutableListOf<List<CoroutineBlock>>()
|
||||
jsBlock.accept(object : RecursiveJsVisitor() {
|
||||
override fun visitDebugger(x: JsDebugger) {
|
||||
x.finallyPath?.let { finallyPaths += it }
|
||||
}
|
||||
})
|
||||
return finallyPaths
|
||||
|
||||
private data class JumpTarget(val block: CoroutineBlock, val tryDepth: Int)
|
||||
|
||||
private class TryBlock(val catchBlock: CoroutineBlock, val finallyBlock: CoroutineBlock?)
|
||||
}
|
||||
|
||||
+33
-95
@@ -48,18 +48,18 @@ class CoroutineFunctionTransformer(
|
||||
function.scope.declareName(throwId)
|
||||
}
|
||||
|
||||
val bodyTransformer = CoroutineBodyTransformer(program, function.scope, throwName)
|
||||
val context = CoroutineTransformationContext(function.scope)
|
||||
val bodyTransformer = CoroutineBodyTransformer(program, context, throwName)
|
||||
bodyTransformer.preProcess(body)
|
||||
body.statements.forEach { it.accept(bodyTransformer) }
|
||||
val coroutineBlocks = bodyTransformer.postProcess()
|
||||
|
||||
for (coroutineBlock in coroutineBlocks) {
|
||||
coroutineBlock.jsBlock.replaceLocalVariables(function.scope, bodyTransformer)
|
||||
}
|
||||
coroutineBlocks.forEach { it.jsBlock.collectAdditionalLocalVariables() }
|
||||
coroutineBlocks.forEach { it.jsBlock.replaceLocalVariables(function.scope, context, localVariables) }
|
||||
|
||||
val additionalStatements = mutableListOf<JsStatement>()
|
||||
val resumeName = generateDoResume(coroutineBlocks, bodyTransformer, additionalStatements, throwName)
|
||||
generateContinuationConstructor(bodyTransformer, additionalStatements)
|
||||
val resumeName = generateDoResume(coroutineBlocks, context, additionalStatements, throwName)
|
||||
generateContinuationConstructor(context, additionalStatements, bodyTransformer.hasFinallyBlocks)
|
||||
generateContinuationMethods(resumeName, additionalStatements)
|
||||
|
||||
generateCoroutineInstantiation()
|
||||
@@ -67,7 +67,11 @@ class CoroutineFunctionTransformer(
|
||||
return additionalStatements
|
||||
}
|
||||
|
||||
private fun generateContinuationConstructor(bodyTransformer: CoroutineBodyTransformer, statements: MutableList<JsStatement>) {
|
||||
private fun generateContinuationConstructor(
|
||||
context: CoroutineTransformationContext,
|
||||
statements: MutableList<JsStatement>,
|
||||
hasFinallyBlocks: Boolean
|
||||
) {
|
||||
val constructor = JsFunction(function.scope.parent, JsBlock(), "Continuation")
|
||||
constructor.name = className
|
||||
constructor.parameters += function.parameters.map { JsParameter(it.name) }
|
||||
@@ -81,12 +85,12 @@ class CoroutineFunctionTransformer(
|
||||
val parameterNames = (function.parameters.map { it.name } + innerFunction?.parameters?.map { it.name }.orEmpty()).toSet()
|
||||
|
||||
constructor.body.statements.run {
|
||||
assign(bodyTransformer.stateFieldName, program.getNumberLiteral(0))
|
||||
assign(bodyTransformer.exceptionStateName, program.getNumberLiteral(0))
|
||||
if (bodyTransformer.hasFinallyBlocks) {
|
||||
assign(bodyTransformer.finallyPathFieldName, JsLiteral.NULL)
|
||||
assign(context.stateFieldName, program.getNumberLiteral(0))
|
||||
assign(context.exceptionStateName, program.getNumberLiteral(0))
|
||||
if (hasFinallyBlocks) {
|
||||
assign(context.finallyPathFieldName, JsLiteral.NULL)
|
||||
}
|
||||
assign(bodyTransformer.controllerFieldName, controllerName.makeRef())
|
||||
assign(context.controllerFieldName, controllerName.makeRef())
|
||||
for (localVariable in localVariables) {
|
||||
val value = if (localVariable !in parameterNames) JsLiteral.NULL else localVariable.makeRef()
|
||||
assign(function.scope.getFieldName(localVariable), value)
|
||||
@@ -142,7 +146,7 @@ class CoroutineFunctionTransformer(
|
||||
|
||||
private fun generateDoResume(
|
||||
coroutineBlocks: List<CoroutineBlock>,
|
||||
bodyTransformer: CoroutineBodyTransformer,
|
||||
context: CoroutineTransformationContext,
|
||||
statements: MutableList<JsStatement>,
|
||||
throwName: JsName?
|
||||
): JsName {
|
||||
@@ -151,11 +155,11 @@ class CoroutineFunctionTransformer(
|
||||
val resumeException = resumeFunction.scope.declareFreshName("exception")
|
||||
resumeFunction.parameters += listOf(JsParameter(resumeParameter), JsParameter(resumeException))
|
||||
|
||||
val coroutineBody = generateCoroutineBody(bodyTransformer, coroutineBlocks, throwName, resumeException)
|
||||
val coroutineBody = generateCoroutineBody(context, coroutineBlocks, throwName, resumeException)
|
||||
functionWithBody.body.statements.clear()
|
||||
|
||||
resumeFunction.body.statements.apply {
|
||||
assign(bodyTransformer.resultFieldName, resumeParameter.makeRef())
|
||||
assign(context.resultFieldName, resumeParameter.makeRef())
|
||||
this += coroutineBody
|
||||
}
|
||||
|
||||
@@ -182,29 +186,29 @@ class CoroutineFunctionTransformer(
|
||||
}
|
||||
|
||||
private fun generateCoroutineBody(
|
||||
transformer: CoroutineBodyTransformer,
|
||||
context: CoroutineTransformationContext,
|
||||
blocks: List<CoroutineBlock>,
|
||||
throwName: JsName?,
|
||||
exceptionName: JsName
|
||||
): List<JsStatement> {
|
||||
val indexOfGlobalCatch = blocks.indexOf(transformer.globalCatchBlock)
|
||||
val stateRef = JsNameRef(transformer.stateFieldName, JsLiteral.THIS)
|
||||
val indexOfGlobalCatch = blocks.indexOf(context.globalCatchBlock)
|
||||
val stateRef = JsNameRef(context.stateFieldName, JsLiteral.THIS)
|
||||
|
||||
val isFromGlobalCatch = JsAstUtils.equality(stateRef, program.getNumberLiteral(indexOfGlobalCatch))
|
||||
val catch = JsCatch(functionWithBody.scope, "e")
|
||||
val continueWithException = JsBlock(
|
||||
JsAstUtils.assignment(stateRef.deepCopy(), JsNameRef(transformer.exceptionStateName, JsLiteral.THIS)).makeStmt(),
|
||||
JsAstUtils.assignment(JsNameRef(transformer.exceptionFieldName, JsLiteral.THIS), catch.parameter.name.makeRef()).makeStmt()
|
||||
JsAstUtils.assignment(stateRef.deepCopy(), JsNameRef(context.exceptionStateName, JsLiteral.THIS)).makeStmt(),
|
||||
JsAstUtils.assignment(JsNameRef(context.exceptionFieldName, JsLiteral.THIS), catch.parameter.name.makeRef()).makeStmt()
|
||||
)
|
||||
catch.body = JsBlock(JsIf(isFromGlobalCatch, JsThrow(catch.parameter.name.makeRef()), continueWithException))
|
||||
|
||||
val throwResultRef = JsNameRef(transformer.exceptionFieldName, JsLiteral.THIS)
|
||||
val throwResultRef = JsNameRef(context.exceptionFieldName, JsLiteral.THIS)
|
||||
if (throwName != null) {
|
||||
val throwMethodRef = JsNameRef(throwName, JsNameRef(transformer.controllerFieldName, JsLiteral.THIS))
|
||||
transformer.globalCatchBlock.statements += JsReturn(JsInvocation(throwMethodRef.deepCopy(), throwResultRef))
|
||||
val throwMethodRef = JsNameRef(throwName, JsNameRef(context.controllerFieldName, JsLiteral.THIS))
|
||||
context.globalCatchBlock.statements += JsReturn(JsInvocation(throwMethodRef.deepCopy(), throwResultRef))
|
||||
}
|
||||
else {
|
||||
transformer.globalCatchBlock.statements += JsThrow(throwResultRef)
|
||||
context.globalCatchBlock.statements += JsThrow(throwResultRef)
|
||||
}
|
||||
|
||||
val cases = blocks.withIndex().map { (index, block) ->
|
||||
@@ -219,96 +223,30 @@ class CoroutineFunctionTransformer(
|
||||
val testExceptionPassed = JsAstUtils.notOptimized(
|
||||
JsAstUtils.typeOfIs(exceptionName.makeRef(), program.getStringLiteral("undefined")))
|
||||
val stateToException = JsAstUtils.assignment(
|
||||
JsNameRef(transformer.stateFieldName, JsLiteral.THIS),
|
||||
JsNameRef(transformer.exceptionStateName, JsLiteral.THIS))
|
||||
val exceptionToResult = JsAstUtils.assignment(JsNameRef(transformer.exceptionFieldName, JsLiteral.THIS), exceptionName.makeRef())
|
||||
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)
|
||||
}
|
||||
|
||||
private fun JsBlock.replaceLocalVariables(scope: JsScope, transformer: CoroutineBodyTransformer) {
|
||||
private fun JsBlock.collectAdditionalLocalVariables() {
|
||||
accept(object : RecursiveJsVisitor() {
|
||||
override fun visit(x: JsVars.JsVar) {
|
||||
super.visit(x)
|
||||
localVariables += x.name
|
||||
}
|
||||
})
|
||||
|
||||
val visitor = object : JsVisitorWithContextImpl() {
|
||||
override fun endVisit(x: JsNameRef, ctx: JsContext<in JsNode>) {
|
||||
when {
|
||||
x.coroutineController -> {
|
||||
ctx.replaceMe(JsNameRef(transformer.controllerFieldName, x.qualifier).apply {
|
||||
sideEffects = SideEffectKind.PURE
|
||||
})
|
||||
}
|
||||
|
||||
x.coroutineResult -> {
|
||||
ctx.replaceMe(JsNameRef(transformer.resultFieldName, x.qualifier).apply {
|
||||
sideEffects = SideEffectKind.DEPENDS_ON_STATE
|
||||
})
|
||||
}
|
||||
|
||||
x.qualifier == null && x.name in localVariables -> {
|
||||
val fieldName = scope.getFieldName(x.name!!)
|
||||
ctx.replaceMe(JsNameRef(fieldName, JsLiteral.THIS))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun endVisit(x: JsVars, ctx: JsContext<in JsStatement>) {
|
||||
val declaredNames = x.vars.map { it.name }
|
||||
val totalCount = declaredNames.size
|
||||
val localVarCount = declaredNames.count()
|
||||
|
||||
when {
|
||||
totalCount == localVarCount -> {
|
||||
val assignments = x.vars.mapNotNull {
|
||||
val fieldName = scope.getFieldName(it.name)
|
||||
val initExpression = it.initExpression
|
||||
if (initExpression != null) {
|
||||
JsAstUtils.assignment(JsNameRef(fieldName, JsLiteral.THIS), it.initExpression)
|
||||
}
|
||||
else {
|
||||
null
|
||||
}
|
||||
}
|
||||
if (assignments.isNotEmpty()) {
|
||||
ctx.replaceMe(JsExpressionStatement(JsAstUtils.newSequence(assignments)))
|
||||
}
|
||||
else {
|
||||
ctx.removeMe()
|
||||
}
|
||||
}
|
||||
localVarCount > 0 -> {
|
||||
for (declaration in x.vars) {
|
||||
if (declaration.name in localVariables) {
|
||||
val fieldName = scope.getFieldName(declaration.name)
|
||||
val assignment = JsAstUtils.assignment(JsNameRef(fieldName, JsLiteral.THIS), declaration.initExpression)
|
||||
ctx.addPrevious(assignment.makeStmt())
|
||||
}
|
||||
else {
|
||||
ctx.addPrevious(JsVars(declaration))
|
||||
}
|
||||
}
|
||||
ctx.removeMe()
|
||||
}
|
||||
}
|
||||
super.endVisit(x, ctx)
|
||||
}
|
||||
}
|
||||
visitor.accept(this)
|
||||
}
|
||||
|
||||
|
||||
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 JsScope.getFieldName(variableName: JsName) = declareName("local\$${variableName.ident}")
|
||||
|
||||
private fun MutableList<JsStatement>.assign(fieldName: JsName, value: JsExpression) {
|
||||
this += JsAstUtils.assignment(JsNameRef(fieldName, JsLiteral.THIS), value).makeStmt()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,237 @@
|
||||
/*
|
||||
* 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 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.js.translate.utils.JsAstUtils
|
||||
import org.jetbrains.kotlin.utils.singletonOrEmptyList
|
||||
|
||||
fun JsNode.collectNodesToSplit(): Set<JsNode> {
|
||||
val nodes = mutableSetOf<JsNode>()
|
||||
|
||||
val visitor = object : RecursiveJsVisitor() {
|
||||
var childrenInSet = false
|
||||
|
||||
override fun visitInvocation(invocation: JsInvocation) {
|
||||
super.visitInvocation(invocation)
|
||||
if (invocation.isSuspend) {
|
||||
nodes += invocation
|
||||
childrenInSet = true
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitReturn(x: JsReturn) {
|
||||
super.visitReturn(x)
|
||||
nodes += x
|
||||
childrenInSet = true
|
||||
}
|
||||
|
||||
override fun visitBreak(x: JsBreak) {
|
||||
super.visitBreak(x)
|
||||
|
||||
// It's a simplification
|
||||
// TODO: don't split break and continue statements when possible
|
||||
nodes += x
|
||||
childrenInSet = true
|
||||
}
|
||||
|
||||
override fun visitContinue(x: JsContinue) {
|
||||
super.visitContinue(x)
|
||||
nodes += x
|
||||
childrenInSet = true
|
||||
}
|
||||
|
||||
override fun visitElement(node: JsNode) {
|
||||
val oldChildrenInSet = childrenInSet
|
||||
childrenInSet = false
|
||||
|
||||
node.acceptChildren(this)
|
||||
|
||||
if (childrenInSet) {
|
||||
nodes += node
|
||||
}
|
||||
else {
|
||||
childrenInSet = oldChildrenInSet
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
visitor.accept(this)
|
||||
visitor.accept(this)
|
||||
|
||||
return nodes
|
||||
}
|
||||
|
||||
fun List<CoroutineBlock>.replaceCoroutineFlowStatements(context: CoroutineTransformationContext, program: JsProgram) {
|
||||
val blockIndexes = withIndex().associate { (index, block) -> Pair(block, index) }
|
||||
|
||||
val blockReplacementVisitor = object : JsVisitorWithContextImpl() {
|
||||
override fun endVisit(x: JsDebugger, ctx: JsContext<in JsStatement>) {
|
||||
val target = x.targetBlock
|
||||
if (target != null) {
|
||||
val lhs = JsNameRef(context.stateFieldName, JsLiteral.THIS)
|
||||
val rhs = program.getNumberLiteral(blockIndexes[target]!!)
|
||||
ctx.replaceMe(JsExpressionStatement(JsAstUtils.assignment(lhs, rhs)).apply {
|
||||
targetBlock = true
|
||||
})
|
||||
}
|
||||
|
||||
val exceptionTarget = x.targetExceptionBlock
|
||||
if (exceptionTarget != null) {
|
||||
val lhs = JsNameRef(context.exceptionStateName, JsLiteral.THIS)
|
||||
val rhs = program.getNumberLiteral(blockIndexes[exceptionTarget]!!)
|
||||
ctx.replaceMe(JsExpressionStatement(JsAstUtils.assignment(lhs, rhs)).apply {
|
||||
targetExceptionBlock = true
|
||||
})
|
||||
}
|
||||
|
||||
val finallyPath = x.finallyPath
|
||||
if (finallyPath != null) {
|
||||
if (finallyPath.isNotEmpty()) {
|
||||
val lhs = JsNameRef(context.finallyPathFieldName, JsLiteral.THIS)
|
||||
val rhs = JsArrayLiteral(finallyPath.map { program.getNumberLiteral(blockIndexes[it]!!) })
|
||||
ctx.replaceMe(JsExpressionStatement(JsAstUtils.assignment(lhs, rhs)).apply {
|
||||
this.finallyPath = true
|
||||
})
|
||||
}
|
||||
else {
|
||||
ctx.removeMe()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return forEach { blockReplacementVisitor.accept(it.jsBlock) }
|
||||
}
|
||||
|
||||
fun CoroutineBlock.buildGraph(): Map<CoroutineBlock, Set<CoroutineBlock>> {
|
||||
// That's a little more than DFS due to need of tracking finally paths
|
||||
|
||||
val visitedBlocks = mutableSetOf<CoroutineBlock>()
|
||||
val graph = mutableMapOf<CoroutineBlock, MutableSet<CoroutineBlock>>()
|
||||
|
||||
fun visitBlock(block: CoroutineBlock) {
|
||||
if (block in visitedBlocks) return
|
||||
|
||||
for (finallyPath in block.collectFinallyPaths()) {
|
||||
for ((finallySource, finallyTarget) in (listOf(block) + finallyPath).zip(finallyPath)) {
|
||||
if (graph.getOrPut(finallySource) { mutableSetOf() }.add(finallyTarget)) {
|
||||
visitedBlocks -= finallySource
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
visitedBlocks += block
|
||||
|
||||
val successors = graph.getOrPut(block) { mutableSetOf() }
|
||||
successors += block.collectTargetBlocks()
|
||||
successors.forEach(::visitBlock)
|
||||
}
|
||||
|
||||
visitBlock(this)
|
||||
|
||||
return graph
|
||||
}
|
||||
|
||||
private fun CoroutineBlock.collectTargetBlocks(): Set<CoroutineBlock> {
|
||||
val targetBlocks = mutableSetOf<CoroutineBlock>()
|
||||
jsBlock.accept(object : RecursiveJsVisitor() {
|
||||
override fun visitDebugger(x: JsDebugger) {
|
||||
targetBlocks += x.targetExceptionBlock.singletonOrEmptyList() + x.targetBlock.singletonOrEmptyList()
|
||||
}
|
||||
})
|
||||
return targetBlocks
|
||||
}
|
||||
|
||||
private fun CoroutineBlock.collectFinallyPaths(): List<List<CoroutineBlock>> {
|
||||
val finallyPaths = mutableListOf<List<CoroutineBlock>>()
|
||||
jsBlock.accept(object : RecursiveJsVisitor() {
|
||||
override fun visitDebugger(x: JsDebugger) {
|
||||
x.finallyPath?.let { finallyPaths += it }
|
||||
}
|
||||
})
|
||||
return finallyPaths
|
||||
}
|
||||
|
||||
fun JsBlock.replaceLocalVariables(scope: JsScope, context: CoroutineTransformationContext, localVariables: Set<JsName>) {
|
||||
val visitor = object : JsVisitorWithContextImpl() {
|
||||
override fun endVisit(x: JsNameRef, ctx: JsContext<in JsNode>) {
|
||||
when {
|
||||
x.coroutineController -> {
|
||||
ctx.replaceMe(JsNameRef(context.controllerFieldName, x.qualifier).apply {
|
||||
sideEffects = SideEffectKind.PURE
|
||||
})
|
||||
}
|
||||
|
||||
x.coroutineResult -> {
|
||||
ctx.replaceMe(JsNameRef(context.resultFieldName, x.qualifier).apply {
|
||||
sideEffects = SideEffectKind.DEPENDS_ON_STATE
|
||||
})
|
||||
}
|
||||
|
||||
x.qualifier == null && x.name in localVariables -> {
|
||||
val fieldName = scope.getFieldName(x.name!!)
|
||||
ctx.replaceMe(JsNameRef(fieldName, JsLiteral.THIS))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun endVisit(x: JsVars, ctx: JsContext<in JsStatement>) {
|
||||
val declaredNames = x.vars.map { it.name }
|
||||
val totalCount = declaredNames.size
|
||||
val localVarCount = declaredNames.count()
|
||||
|
||||
when {
|
||||
totalCount == localVarCount -> {
|
||||
val assignments = x.vars.mapNotNull {
|
||||
val fieldName = scope.getFieldName(it.name)
|
||||
val initExpression = it.initExpression
|
||||
if (initExpression != null) {
|
||||
JsAstUtils.assignment(JsNameRef(fieldName, JsLiteral.THIS), it.initExpression)
|
||||
}
|
||||
else {
|
||||
null
|
||||
}
|
||||
}
|
||||
if (assignments.isNotEmpty()) {
|
||||
ctx.replaceMe(JsExpressionStatement(JsAstUtils.newSequence(assignments)))
|
||||
}
|
||||
else {
|
||||
ctx.removeMe()
|
||||
}
|
||||
}
|
||||
localVarCount > 0 -> {
|
||||
for (declaration in x.vars) {
|
||||
if (declaration.name in localVariables) {
|
||||
val fieldName = scope.getFieldName(declaration.name)
|
||||
val assignment = JsAstUtils.assignment(JsNameRef(fieldName, JsLiteral.THIS), declaration.initExpression)
|
||||
ctx.addPrevious(assignment.makeStmt())
|
||||
}
|
||||
else {
|
||||
ctx.addPrevious(JsVars(declaration))
|
||||
}
|
||||
}
|
||||
ctx.removeMe()
|
||||
}
|
||||
}
|
||||
super.endVisit(x, ctx)
|
||||
}
|
||||
}
|
||||
visitor.accept(this)
|
||||
}
|
||||
|
||||
fun JsScope.getFieldName(variableName: JsName) = declareName("local\$${variableName.ident}")
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* 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 org.jetbrains.kotlin.js.coroutine
|
||||
|
||||
import com.google.dart.compiler.backend.js.ast.JsScope
|
||||
|
||||
class CoroutineTransformationContext(private val scope: JsScope) {
|
||||
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 controllerFieldName by lazy { scope.declareFreshName("\$controller") }
|
||||
val exceptionStateName by lazy { scope.declareFreshName("\$exceptionState") }
|
||||
val finallyPathFieldName by lazy { scope.declareFreshName("\$finallyPath") }
|
||||
}
|
||||
@@ -18,6 +18,7 @@ package org.jetbrains.kotlin.js.inline.util
|
||||
|
||||
import com.google.dart.compiler.backend.js.ast.*
|
||||
import com.google.dart.compiler.backend.js.ast.metadata.staticRef
|
||||
|
||||
import org.jetbrains.kotlin.js.inline.util.collectors.InstanceCollector
|
||||
import org.jetbrains.kotlin.js.translate.expression.InlineMetadata
|
||||
import org.jetbrains.kotlin.js.translate.utils.JsAstUtils
|
||||
@@ -184,3 +185,111 @@ fun <T : JsNode> collectInstances(klass: Class<T>, scope: JsNode): List<T> {
|
||||
collected
|
||||
}
|
||||
}
|
||||
|
||||
fun JsNode.collectBreakContinueTargets(): Map<JsContinue, JsStatement> {
|
||||
val targets = mutableMapOf<JsContinue, JsStatement>()
|
||||
|
||||
accept(object : RecursiveJsVisitor() {
|
||||
var defaultBreakTarget: JsStatement? = null
|
||||
var breakTargets = mutableMapOf<JsName, JsStatement>()
|
||||
var defaultContinueTarget: JsStatement? = null
|
||||
var continueTargets = mutableMapOf<JsName, JsStatement>()
|
||||
|
||||
override fun visitLabel(x: JsLabel) {
|
||||
val inner = x.statement
|
||||
when (inner) {
|
||||
is JsDoWhile -> handleLoop(inner, inner.body, x.name)
|
||||
|
||||
is JsWhile -> handleLoop(inner, inner.body, x.name)
|
||||
|
||||
is JsFor -> handleLoop(inner, inner.body, x.name)
|
||||
|
||||
else -> {
|
||||
withBreakAndContinue(x.name, x.statement, null) {
|
||||
accept(inner)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitWhile(x: JsWhile) = handleLoop(x, x.body, null)
|
||||
|
||||
override fun visitDoWhile(x: JsDoWhile) = handleLoop(x, x.body, null)
|
||||
|
||||
override fun visitFor(x: JsFor) = handleLoop(x, x.body, null)
|
||||
|
||||
private fun handleLoop(loop: JsStatement, body: JsStatement, label: JsName?) {
|
||||
withBreakAndContinue(label, loop, loop) {
|
||||
body.accept(this)
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitBreak(x: JsBreak) {
|
||||
val targetLabel = x.label?.name
|
||||
targets[x] = if (targetLabel == null) {
|
||||
defaultBreakTarget!!
|
||||
}
|
||||
else {
|
||||
breakTargets[targetLabel]!!
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitContinue(x: JsContinue) {
|
||||
val targetLabel = x.label?.name
|
||||
targets[x] = if (targetLabel == null) {
|
||||
defaultContinueTarget!!
|
||||
}
|
||||
else {
|
||||
continueTargets[targetLabel]!!
|
||||
}
|
||||
}
|
||||
|
||||
private fun withBreakAndContinue(
|
||||
label: JsName?,
|
||||
breakTargetStatement: JsStatement,
|
||||
continueTargetStatement: JsStatement? = null,
|
||||
action: () -> Unit
|
||||
) {
|
||||
val oldDefaultBreakTarget = defaultBreakTarget
|
||||
val oldDefaultContinueTarget = defaultContinueTarget
|
||||
val (oldBreakTarget, oldContinueTarget) = if (label != null) {
|
||||
Pair(breakTargets[label], continueTargets[label])
|
||||
}
|
||||
else {
|
||||
Pair(null, null)
|
||||
}
|
||||
|
||||
defaultBreakTarget = breakTargetStatement
|
||||
if (label != null) {
|
||||
breakTargets[label] = breakTargetStatement
|
||||
if (continueTargetStatement != null) {
|
||||
continueTargets[label] = continueTargetStatement
|
||||
}
|
||||
}
|
||||
if (continueTargetStatement != null) {
|
||||
defaultContinueTarget = continueTargetStatement
|
||||
}
|
||||
|
||||
action()
|
||||
|
||||
defaultBreakTarget = oldDefaultBreakTarget
|
||||
defaultContinueTarget = oldDefaultContinueTarget
|
||||
if (label != null) {
|
||||
if (oldBreakTarget == null) {
|
||||
breakTargets.keys -= label
|
||||
}
|
||||
else {
|
||||
breakTargets[label] = oldBreakTarget
|
||||
}
|
||||
if (oldContinueTarget == null) {
|
||||
continueTargets.keys -= label
|
||||
}
|
||||
else {
|
||||
continueTargets[label] = oldContinueTarget
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return targets
|
||||
}
|
||||
@@ -5696,6 +5696,12 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest {
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("instanceOfContinuation.kt")
|
||||
public void testInstanceOfContinuation() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/instanceOfContinuation.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("iterateOverArray.kt")
|
||||
public void testIterateOverArray() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/iterateOverArray.kt");
|
||||
|
||||
Reference in New Issue
Block a user