[JS BE] Fix KT-26787: handle JsSwitch in LabeledBlockToDoWhileTransformation

This commit is contained in:
Svyatoslav Kuzmich
2018-10-08 16:07:08 +03:00
parent f543170998
commit 0210ec3114
4 changed files with 78 additions and 15 deletions
@@ -30,18 +30,18 @@ object LabeledBlockToDoWhileTransformation {
fun apply(root: JsNode) { fun apply(root: JsNode) {
object : JsVisitorWithContextImpl() { object : JsVisitorWithContextImpl() {
val loopStack = Stack<JsStatement>() val loopOrSwitchStack = Stack<JsStatement>()
val newFakeLoops = HashSet<JsDoWhile>() val newFakeLoops = HashSet<JsDoWhile>()
val loopLabels = HashMap<JsStatement, JsLabel>() val statementsLabels = HashMap<JsStatement, JsLabel>()
// If labeled block sits in between loop and corresponding unlabeled breaks/continues // If labeled block sits in between loop (or switch) and corresponding unlabeled breaks/continues
// we have to label this loop, breaks and continues in order to preserve their // we have to label this loop, breaks and continues in order to preserve their
// relationships across new fake do-while loop // relationships across new fake do-while loop
val loopsToLabel = HashSet<JsStatement>() val loopsAndSwitchesToLabel = HashSet<JsStatement>()
override fun endVisit(x: JsLabel, ctx: JsContext<JsNode>) { override fun endVisit(x: JsLabel, ctx: JsContext<JsNode>) {
if (x.statement is JsBlock) { if (x.statement is JsBlock) {
loopsToLabel.addIfNotNull(loopStack.lastOrNull()) loopsAndSwitchesToLabel.addIfNotNull(loopOrSwitchStack.lastOrNull())
val fakeLoop = JsDoWhile(JsBooleanLiteral(false), x.statement) val fakeLoop = JsDoWhile(JsBooleanLiteral(false), x.statement)
newFakeLoops.add(fakeLoop) newFakeLoops.add(fakeLoop)
x.statement = fakeLoop x.statement = fakeLoop
@@ -51,30 +51,46 @@ object LabeledBlockToDoWhileTransformation {
override fun visit(x: JsLabel, ctx: JsContext<JsNode>): Boolean { override fun visit(x: JsLabel, ctx: JsContext<JsNode>): Boolean {
if (x.statement is JsLoop) { if (x.statement is JsLoop) {
loopLabels[x.statement] = x statementsLabels[x.statement] = x
} }
return true return true
} }
override fun visit(x: JsLoop, ctx: JsContext<JsNode>): Boolean { override fun visit(x: JsLoop, ctx: JsContext<JsNode>): Boolean {
loopStack.push(x) loopOrSwitchStack.push(x)
return true return true
} }
override fun endVisit(x: JsLoop, ctx: JsContext<JsNode>) { override fun visit(x: JsSwitch, ctx: JsContext<JsNode>): Boolean {
loopStack.pop() loopOrSwitchStack.push(x)
if (loopsToLabel.contains(x)) { return true
}
fun endVisitLoopOrSwitch(x: JsStatement, ctx: JsContext<JsNode>) {
val top = loopOrSwitchStack.pop()
assert(top === x)
if (loopsAndSwitchesToLabel.contains(x)) {
// Reuse loop label if present. Otherwise create new label. // Reuse loop label if present. Otherwise create new label.
var label = loopLabels[x] var label = statementsLabels[x]
if (label == null) { if (label == null) {
val labelName = JsScope.declareTemporaryName("loop_label") val labelName = JsScope.declareTemporaryName("loop_label")
label = JsLabel(labelName, x) label = JsLabel(labelName, x)
loopLabels[x] = label statementsLabels[x] = label
ctx.replaceMe(label) ctx.replaceMe(label)
} }
labelLoopBreaksAndContinues(x, newFakeLoops, label.name.makeRef()) labelLoopBreaksAndContinues(x, newFakeLoops, label.name.makeRef())
} }
} }
override fun endVisit(x: JsSwitch, ctx: JsContext<JsNode>) {
endVisitLoopOrSwitch(x, ctx)
}
override fun endVisit(x: JsLoop, ctx: JsContext<JsNode>) {
endVisitLoopOrSwitch(x, ctx)
}
}.accept(root) }.accept(root)
} }
@@ -82,10 +98,13 @@ object LabeledBlockToDoWhileTransformation {
Label unlabeled breaks that correspond to current loop. Label unlabeled breaks that correspond to current loop.
Skip newly created fake do-while loops. Skip newly created fake do-while loops.
*/ */
private fun labelLoopBreaksAndContinues(loop: JsStatement, fakeLoops: Set<JsDoWhile>, label: JsNameRef) { private fun labelLoopBreaksAndContinues(loopOrSwitch: JsStatement, fakeLoops: Set<JsDoWhile>, label: JsNameRef) {
object : JsVisitorWithContextImpl() { object : JsVisitorWithContextImpl() {
override fun visit(x: JsLoop, ctx: JsContext<JsNode>): Boolean = override fun visit(x: JsLoop, ctx: JsContext<JsNode>): Boolean =
fakeLoops.contains(x) || x === loop fakeLoops.contains(x) || x === loopOrSwitch
override fun visit(x: JsSwitch, ctx: JsContext<JsNode>): Boolean =
x === loopOrSwitch
override fun endVisit(x: JsBreak, ctx: JsContext<JsNode>) { override fun endVisit(x: JsBreak, ctx: JsContext<JsNode>) {
if (x.label == null) if (x.label == null)
@@ -98,6 +117,6 @@ object LabeledBlockToDoWhileTransformation {
ctx.replaceMe(JsContinue(label)) ctx.replaceMe(JsContinue(label))
super.endVisit(x, ctx) super.endVisit(x, ctx)
} }
}.accept(loop) }.accept(loopOrSwitch)
} }
} }
@@ -4000,6 +4000,11 @@ public class BoxJsTestGenerated extends AbstractBoxJsTest {
runTest("js/js.translator/testData/box/inline/kt26466.kt"); runTest("js/js.translator/testData/box/inline/kt26466.kt");
} }
@TestMetadata("kt26787.kt")
public void testKt26787() throws Exception {
runTest("js/js.translator/testData/box/inline/kt26787.kt");
}
@TestMetadata("lambdaInLambda.kt") @TestMetadata("lambdaInLambda.kt")
public void testLambdaInLambda() throws Exception { public void testLambdaInLambda() throws Exception {
runTest("js/js.translator/testData/box/inline/lambdaInLambda.kt"); runTest("js/js.translator/testData/box/inline/lambdaInLambda.kt");
@@ -4000,6 +4000,11 @@ public class IrBoxJsTestGenerated extends AbstractIrBoxJsTest {
runTest("js/js.translator/testData/box/inline/kt26466.kt"); runTest("js/js.translator/testData/box/inline/kt26466.kt");
} }
@TestMetadata("kt26787.kt")
public void testKt26787() throws Exception {
runTest("js/js.translator/testData/box/inline/kt26787.kt");
}
@TestMetadata("lambdaInLambda.kt") @TestMetadata("lambdaInLambda.kt")
public void testLambdaInLambda() throws Exception { public void testLambdaInLambda() throws Exception {
runTest("js/js.translator/testData/box/inline/lambdaInLambda.kt"); runTest("js/js.translator/testData/box/inline/lambdaInLambda.kt");
+34
View File
@@ -0,0 +1,34 @@
// EXPECTED_REACHABLE_NODES: 1618
// Simplified example from https://youtrack.jetbrains.com/issue/KT-26787
enum class Role { PRIMARY, EXTRA }
class Location(val role: Role, val building: Int = 0)
fun box() : String {
val result = mutableListOf<Location>()
val props = arrayOf(
Location(Role.PRIMARY),
Location(Role.EXTRA),
Location(Role.PRIMARY)
)
var loopCount = 0
for (possiblyOutdated in props) {
when (possiblyOutdated.role) {
Role.PRIMARY -> {
val index = result.indexOfFirst { it.building == possiblyOutdated.building }
}
Role.EXTRA -> {
}
}
loopCount++
}
if (loopCount != 3) return "Wrong loop count $loopCount"
return "OK"
}