diff --git a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/clean/LabeledBlockToDoWhileTransformation.kt b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/clean/LabeledBlockToDoWhileTransformation.kt index cd91f0db600..ec9cfccde2b 100644 --- a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/clean/LabeledBlockToDoWhileTransformation.kt +++ b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/clean/LabeledBlockToDoWhileTransformation.kt @@ -30,18 +30,18 @@ object LabeledBlockToDoWhileTransformation { fun apply(root: JsNode) { object : JsVisitorWithContextImpl() { - val loopStack = Stack() + val loopOrSwitchStack = Stack() val newFakeLoops = HashSet() - val loopLabels = HashMap() + val statementsLabels = HashMap() - // 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 // relationships across new fake do-while loop - val loopsToLabel = HashSet() + val loopsAndSwitchesToLabel = HashSet() override fun endVisit(x: JsLabel, ctx: JsContext) { if (x.statement is JsBlock) { - loopsToLabel.addIfNotNull(loopStack.lastOrNull()) + loopsAndSwitchesToLabel.addIfNotNull(loopOrSwitchStack.lastOrNull()) val fakeLoop = JsDoWhile(JsBooleanLiteral(false), x.statement) newFakeLoops.add(fakeLoop) x.statement = fakeLoop @@ -51,30 +51,46 @@ object LabeledBlockToDoWhileTransformation { override fun visit(x: JsLabel, ctx: JsContext): Boolean { if (x.statement is JsLoop) { - loopLabels[x.statement] = x + statementsLabels[x.statement] = x } return true } override fun visit(x: JsLoop, ctx: JsContext): Boolean { - loopStack.push(x) + loopOrSwitchStack.push(x) return true } - override fun endVisit(x: JsLoop, ctx: JsContext) { - loopStack.pop() - if (loopsToLabel.contains(x)) { + override fun visit(x: JsSwitch, ctx: JsContext): Boolean { + loopOrSwitchStack.push(x) + return true + } + + fun endVisitLoopOrSwitch(x: JsStatement, ctx: JsContext) { + val top = loopOrSwitchStack.pop() + assert(top === x) + + if (loopsAndSwitchesToLabel.contains(x)) { // Reuse loop label if present. Otherwise create new label. - var label = loopLabels[x] + var label = statementsLabels[x] if (label == null) { val labelName = JsScope.declareTemporaryName("loop_label") label = JsLabel(labelName, x) - loopLabels[x] = label + statementsLabels[x] = label ctx.replaceMe(label) } labelLoopBreaksAndContinues(x, newFakeLoops, label.name.makeRef()) } } + + override fun endVisit(x: JsSwitch, ctx: JsContext) { + endVisitLoopOrSwitch(x, ctx) + } + + override fun endVisit(x: JsLoop, ctx: JsContext) { + endVisitLoopOrSwitch(x, ctx) + } + }.accept(root) } @@ -82,10 +98,13 @@ object LabeledBlockToDoWhileTransformation { Label unlabeled breaks that correspond to current loop. Skip newly created fake do-while loops. */ - private fun labelLoopBreaksAndContinues(loop: JsStatement, fakeLoops: Set, label: JsNameRef) { + private fun labelLoopBreaksAndContinues(loopOrSwitch: JsStatement, fakeLoops: Set, label: JsNameRef) { object : JsVisitorWithContextImpl() { override fun visit(x: JsLoop, ctx: JsContext): Boolean = - fakeLoops.contains(x) || x === loop + fakeLoops.contains(x) || x === loopOrSwitch + + override fun visit(x: JsSwitch, ctx: JsContext): Boolean = + x === loopOrSwitch override fun endVisit(x: JsBreak, ctx: JsContext) { if (x.label == null) @@ -98,6 +117,6 @@ object LabeledBlockToDoWhileTransformation { ctx.replaceMe(JsContinue(label)) super.endVisit(x, ctx) } - }.accept(loop) + }.accept(loopOrSwitch) } } diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/BoxJsTestGenerated.java b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/BoxJsTestGenerated.java index cdfc1897b45..73f38903083 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/BoxJsTestGenerated.java +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/BoxJsTestGenerated.java @@ -4000,6 +4000,11 @@ public class BoxJsTestGenerated extends AbstractBoxJsTest { 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") public void testLambdaInLambda() throws Exception { runTest("js/js.translator/testData/box/inline/lambdaInLambda.kt"); diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/IrBoxJsTestGenerated.java b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/IrBoxJsTestGenerated.java index 070355a0ea8..b85956c86e7 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/IrBoxJsTestGenerated.java +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/IrBoxJsTestGenerated.java @@ -4000,6 +4000,11 @@ public class IrBoxJsTestGenerated extends AbstractIrBoxJsTest { 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") public void testLambdaInLambda() throws Exception { runTest("js/js.translator/testData/box/inline/lambdaInLambda.kt"); diff --git a/js/js.translator/testData/box/inline/kt26787.kt b/js/js.translator/testData/box/inline/kt26787.kt new file mode 100644 index 00000000000..13d054f4cfb --- /dev/null +++ b/js/js.translator/testData/box/inline/kt26787.kt @@ -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() + + 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" +}