diff --git a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/ExpressionDecomposer.kt b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/ExpressionDecomposer.kt index 6157dda7acb..fe9c8e883d8 100644 --- a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/ExpressionDecomposer.kt +++ b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/ExpressionDecomposer.kt @@ -21,6 +21,8 @@ import com.google.dart.compiler.backend.js.ast.metadata.* import com.intellij.util.SmartList import org.jetbrains.kotlin.js.inline.util.IdentitySet import org.jetbrains.kotlin.js.translate.utils.JsAstUtils +import org.jetbrains.kotlin.js.inline.util.canHaveOwnSideEffect +import org.jetbrains.kotlin.js.inline.util.rewriters.ContinueReplacingVisitor import org.jetbrains.kotlin.js.translate.utils.JsAstUtils.* import org.jetbrains.kotlin.js.translate.utils.jsAstUtils.* @@ -93,17 +95,26 @@ internal class ExpressionDecomposer private constructor( return false } + override fun visit(x: JsLabel, ctx: JsContext): Boolean { + val statement = x.statement + when (statement) { + is JsDoWhile -> statement.process(false, x.name) + is JsWhile -> statement.process(true, x.name) + } + return false + } + override fun visit(x: JsWhile, ctx: JsContext): Boolean { - x.process(true) + x.process(true, null) return false } override fun visit(x: JsDoWhile, ctx: JsContext): Boolean { - x.process(false) + x.process(false, null) return false } - private fun JsWhile.process(addBreakToBegin: Boolean) { + private fun JsWhile.process(addBreakToBegin: Boolean, loopLabel: JsName?) { if (test !in containsExtractable) return withNewAdditionalStatements { @@ -118,7 +129,13 @@ internal class ExpressionDecomposer private constructor( addStatements(bodyStatements) } else { - addStatements(0, bodyStatements) + // See KT-12275 + val guardName = scope.declareFreshName("guard$") + val label = JsLabel(guardName).apply { synthetic = true } + label.statement = JsBlock(bodyStatements) + addStatements(0, listOf(label)) + ContinueReplacingVisitor(loopLabel, guardName).acceptList(bodyStatements) + addStatement(breakIfNotTest) } @@ -362,7 +379,6 @@ internal open class JsExpressionVisitor() : JsVisitorWithContextImpl() { override fun visit(x: JsBlock, ctx: JsContext): Boolean = false override fun visit(x: JsTry, ctx: JsContext): Boolean = false override fun visit(x: JsDebugger, ctx: JsContext): Boolean = false - override fun visit(x: JsLabel, ctx: JsContext): Boolean = false override fun visit(x: JsFunction, ctx: JsContext): Boolean = false override fun visit(x: JsObjectLiteral, ctx: JsContext): Boolean = false override fun visit(x: JsPropertyInitializer, ctx: JsContext): Boolean = false @@ -408,6 +424,11 @@ internal open class JsExpressionVisitor() : JsVisitorWithContextImpl() { return false } + override fun visit(x: JsLabel, ctx: JsContext): Boolean { + x.statement = accept(x.statement) + return false + } + override fun visit(x: JsArrayAccess, ctx: JsContext): Boolean = true override fun visit(x: JsArrayLiteral, ctx: JsContext): Boolean = true override fun visit(x: JsBinaryOperation, ctx: JsContext): Boolean = true diff --git a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/util/rewriters/ContinueReplacingVisitor.kt b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/util/rewriters/ContinueReplacingVisitor.kt new file mode 100644 index 00000000000..72ba6b374cd --- /dev/null +++ b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/util/rewriters/ContinueReplacingVisitor.kt @@ -0,0 +1,83 @@ +/* + * 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.inline.util.rewriters + +import com.google.dart.compiler.backend.js.ast.* + +class ContinueReplacingVisitor(val loopLabelName: JsName?, val guardLabelName: JsName) : JsVisitorWithContextImpl() { + var loopNestingLevel = 0 + + override fun visit(x: JsFunction, ctx: JsContext) = false + + override fun visit(x: JsContinue, ctx: JsContext): Boolean { + val target = x.label?.name + val shouldReplace = if (target == null) loopNestingLevel == 0 else target == loopLabelName + if (shouldReplace) { + ctx.replaceMe(JsBreak(guardLabelName.makeRef())) + } + + return false + } + + override fun visit(x: JsWhile, ctx: JsContext): Boolean { + if (loopLabelName == null) return false + + loopNestingLevel++ + return super.visit(x, ctx) + } + + override fun endVisit(x: JsWhile, ctx: JsContext) { + super.endVisit(x, ctx) + loopNestingLevel-- + } + + override fun visit(x: JsDoWhile, ctx: JsContext): Boolean { + if (loopLabelName == null) return false + + loopNestingLevel++ + return super.visit(x, ctx) + } + + override fun endVisit(x: JsDoWhile, ctx: JsContext) { + super.endVisit(x, ctx) + loopNestingLevel-- + } + + override fun visit(x: JsFor, ctx: JsContext): Boolean { + if (loopLabelName == null) return false + + loopNestingLevel++ + return super.visit(x, ctx) + } + + override fun endVisit(x: JsFor, ctx: JsContext) { + super.endVisit(x, ctx) + loopNestingLevel-- + } + + override fun visit(x: JsForIn, ctx: JsContext): Boolean { + if (loopLabelName == null) return false + + loopNestingLevel++ + return super.visit(x, ctx) + } + + override fun endVisit(x: JsForIn, ctx: JsContext) { + super.endVisit(x, ctx) + loopNestingLevel-- + } +} diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/InlineEvaluationOrderTestGenerated.java b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/InlineEvaluationOrderTestGenerated.java index f9fd0a2988d..7371e874ea7 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/InlineEvaluationOrderTestGenerated.java +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/InlineEvaluationOrderTestGenerated.java @@ -155,6 +155,12 @@ public class InlineEvaluationOrderTestGenerated extends AbstractInlineEvaluation doTest(fileName); } + @TestMetadata("continueInExtractedDoWhile.kt") + public void testContinueInExtractedDoWhile() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/inlineEvaluationOrder/cases/continueInExtractedDoWhile.kt"); + doTest(fileName); + } + @TestMetadata("doWhile.kt") public void testDoWhile() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/inlineEvaluationOrder/cases/doWhile.kt"); @@ -245,6 +251,12 @@ public class InlineEvaluationOrderTestGenerated extends AbstractInlineEvaluation doTest(fileName); } + @TestMetadata("nestedContinueInExtractedDoWhile.kt") + public void testNestedContinueInExtractedDoWhile() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/inlineEvaluationOrder/cases/nestedContinueInExtractedDoWhile.kt"); + doTest(fileName); + } + @TestMetadata("nestedInlineCall.kt") public void testNestedInlineCall() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/inlineEvaluationOrder/cases/nestedInlineCall.kt"); diff --git a/js/js.translator/testData/inlineEvaluationOrder/cases/continueInExtractedDoWhile.kt b/js/js.translator/testData/inlineEvaluationOrder/cases/continueInExtractedDoWhile.kt new file mode 100644 index 00000000000..a6e08f908ae --- /dev/null +++ b/js/js.translator/testData/inlineEvaluationOrder/cases/continueInExtractedDoWhile.kt @@ -0,0 +1,35 @@ +package foo + +private inline fun bar(predicate: (Char) -> Boolean): Int { + var i = -1 + val str = "abc " + do { + i++ + if (i == 1) continue + log(i.toString()) + } while (predicate(str[i]) && i < 3) + return i +} + +private fun test(c: Char): Int { + return bar { + log(it.toString()) + it != c + } +} + +fun box(): String { + assertEquals(0, test('a')) + assertEquals("0;a;", pullLog()) + + assertEquals(1, test('b')) + assertEquals("0;a;b;", pullLog()) + + assertEquals(2, test('c')) + assertEquals("0;a;b;2;c;", pullLog()) + + assertEquals(3, test('*')) + assertEquals("0;a;b;2;c;3; ;", pullLog()) + + return "OK" +} \ No newline at end of file diff --git a/js/js.translator/testData/inlineEvaluationOrder/cases/nestedContinueInExtractedDoWhile.kt b/js/js.translator/testData/inlineEvaluationOrder/cases/nestedContinueInExtractedDoWhile.kt new file mode 100644 index 00000000000..02b59e3a376 --- /dev/null +++ b/js/js.translator/testData/inlineEvaluationOrder/cases/nestedContinueInExtractedDoWhile.kt @@ -0,0 +1,28 @@ +package foo + +private inline fun bar(predicate: (Int) -> Boolean) { + var i = -1 + outer@do { + i++ + if (i == 1) continue + var j = -1 + do { + ++j + if (j == 1) { + if (i == 3) continue@outer else continue + } + log("i$j") + } while (j < 3) + log("o$i") + } while (predicate(i)) +} + +fun box(): String { + bar { + log("p$it") + it < 5 + } + assertEquals("i0;i2;i3;o0;p0;p1;i0;i2;i3;o2;p2;i0;p3;i0;i2;i3;o4;p4;i0;i2;i3;o5;p5;", pullLog()) + + return "OK" +} \ No newline at end of file