From 05887f45f7a061f7c7aa8968283aa0689b06f75f Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Mon, 4 Apr 2016 18:47:55 +0300 Subject: [PATCH] KT-7819 Implement non-local returns --- .../boxInline/nonLocalReturns/kt8948v2.kt | 6 +- .../js/ast/metadata/metadataProperties.kt | 16 +++++ .../kotlin/js/inline/FunctionInlineMutator.kt | 16 +---- .../jetbrains/kotlin/js/inline/JsInliner.java | 8 +-- .../js/inline/clean/IfStatementReduction.kt | 10 +-- .../clean/TemporaryAssignmentElimination.kt | 3 +- .../kotlin/js/inline/util/namingUtils.kt | 12 ++-- .../rewriters/LabelNameRefreshingVisitor.kt | 47 ++++++++---- .../util/rewriters/ReturnReplacingVisitor.kt | 22 +++--- .../kotlin/js/inline/util/sideEffectUtils.kt | 1 - .../jetbrains/kotlin/js/test/BasicTest.java | 8 ++- .../kotlin/js/test/KotlinJSMultiFileTest.kt | 60 ++++++++++++++++ .../kotlin/js/test/KotlinJSMutliFileTest.kt | 55 -------------- .../js/test/SingleFileTranslationTest.java | 6 +- .../js/test/semantics/LabelTestGenerated.java | 6 ++ .../NonLocalReturnsTestGenerated.java | 12 ++-- .../abstractClassesForGeneratedTests.kt | 2 +- .../js/test/utils/DirectiveTestUtils.java | 8 +-- .../callTranslator/CallInfoExtensions.kt | 3 +- .../callTranslator/FunctionCallCases.kt | 4 ++ .../translate/context/TranslationContext.java | 17 +++-- .../declaration/DelegationTranslator.kt | 3 +- .../expression/ExpressionVisitor.java | 72 +++++++++++++------ .../expression/FunctionTranslator.java | 6 +- .../expression/LiteralFunctionTranslator.kt | 7 +- .../js/translate/general/Translation.java | 4 -- .../utils/FunctionBodyTranslator.java | 7 +- .../kotlin/js/translate/utils/JsAstUtils.java | 2 +- .../js/translate/utils/TranslationUtils.java | 9 +-- .../cases/intrinsicWithBreakContinueReturn.kt | 14 ++++ .../labels/cases/nestedInlineLabels.kt | 46 ++++++++++++ 31 files changed, 310 insertions(+), 182 deletions(-) create mode 100644 js/js.tests/test/org/jetbrains/kotlin/js/test/KotlinJSMultiFileTest.kt delete mode 100644 js/js.tests/test/org/jetbrains/kotlin/js/test/KotlinJSMutliFileTest.kt create mode 100644 js/js.translator/testData/labels/cases/nestedInlineLabels.kt diff --git a/compiler/testData/codegen/boxInline/nonLocalReturns/kt8948v2.kt b/compiler/testData/codegen/boxInline/nonLocalReturns/kt8948v2.kt index 18d4dbab358..7a0be75b0a4 100644 --- a/compiler/testData/codegen/boxInline/nonLocalReturns/kt8948v2.kt +++ b/compiler/testData/codegen/boxInline/nonLocalReturns/kt8948v2.kt @@ -13,13 +13,11 @@ inline fun foo(f: () -> Unit) { // FILE: 2.kt -// TODO: enabled when KT-6397 gets fixed -// TARGET_BACKEND: JVM import test.* var p = "fail" -fun test() { +fun test1() { foo { try { p = "O" @@ -33,6 +31,6 @@ fun test() { } fun box(): String { - test() + test1() return p } diff --git a/js/js.dart-ast/src/com/google/dart/compiler/backend/js/ast/metadata/metadataProperties.kt b/js/js.dart-ast/src/com/google/dart/compiler/backend/js/ast/metadata/metadataProperties.kt index 73300ee6a9a..03cbaf42be0 100644 --- a/js/js.dart-ast/src/com/google/dart/compiler/backend/js/ast/metadata/metadataProperties.kt +++ b/js/js.dart-ast/src/com/google/dart/compiler/backend/js/ast/metadata/metadataProperties.kt @@ -20,6 +20,7 @@ package com.google.dart.compiler.backend.js.ast.metadata import com.google.dart.compiler.backend.js.ast.* import com.intellij.psi.PsiElement import org.jetbrains.kotlin.descriptors.CallableDescriptor +import org.jetbrains.kotlin.descriptors.FunctionDescriptor import org.jetbrains.kotlin.resolve.inline.InlineStrategy var JsName.staticRef: JsNode? by MetadataProperty(default = null) @@ -37,6 +38,21 @@ var JsParameter.hasDefaultValue: Boolean by MetadataProperty(default = false) var JsInvocation.typeCheck: TypeCheck? by MetadataProperty(default = null) +/** + * For function and lambda bodies indicates what declaration corresponds to. + * When absent (`null`) on body of a named function, this function is from external JS module. + */ +var JsFunction.functionDescriptor: FunctionDescriptor? by MetadataProperty(default = null) + +/** + * For return statement specifies corresponding target descriptor given by [functionDescriptor]. + * For all JsReturn nodes created by K2JSTranslator, this property is filled, either for local/non-local labeled and non-labeled returns. + * + * Absence of this property (expressed as `null`) means that the corresponding JsReturn got from external JS library. + * In this case we assume that such return can never be non-local. + */ +var JsReturn.returnTarget: FunctionDescriptor? by MetadataProperty(default = null) + var HasMetadata.synthetic: Boolean by MetadataProperty(default = false) var HasMetadata.sideEffects: Boolean by MetadataProperty(default = true) diff --git a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/FunctionInlineMutator.kt b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/FunctionInlineMutator.kt index 65a8cf8c78a..05774e2e052 100644 --- a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/FunctionInlineMutator.kt +++ b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/FunctionInlineMutator.kt @@ -52,7 +52,6 @@ private constructor( removeDefaultInitializers(arguments, parameters, body) aliasArgumentsIfNeeded(namingContext, arguments, parameters) renameLocalNames(namingContext, invokedFunction) - removeStatementsAfterTopReturn() processReturns() namingContext.applyRenameTo(body) @@ -76,15 +75,6 @@ private constructor( replaceThisReference(body, thisReplacement) } - private fun removeStatementsAfterTopReturn() { - val statements = body.statements - - val returnIndex = statements.indexOfFirst { it is JsReturn } - if (returnIndex >= 0) { - statements.subList(returnIndex + 1, statements.size).clear() - } - } - private fun processReturns() { val resultReference = getResultReference() if (resultReference != null) { @@ -93,11 +83,9 @@ private constructor( assert(resultExpr == null || resultExpr is JsNameRef) val breakName = namingContext.getFreshName(getBreakLabel()) - val breakLabel = JsLabel(breakName) - breakLabel.synthetic = true - this.breakLabel = breakLabel + this.breakLabel = JsLabel(breakName).apply { synthetic = true } - val visitor = ReturnReplacingVisitor(resultExpr as? JsNameRef, breakName.makeRef()) + val visitor = ReturnReplacingVisitor(resultExpr as? JsNameRef, breakName.makeRef(), invokedFunction) visitor.accept(body) } diff --git a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/JsInliner.java b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/JsInliner.java index ef60e62a003..f48f1cc8ee2 100644 --- a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/JsInliner.java +++ b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/JsInliner.java @@ -98,7 +98,7 @@ public class JsInliner extends JsVisitorWithContextImpl { @Override public void endVisit(@NotNull JsFunction function, @NotNull JsContext context) { super.endVisit(function, context); - NamingUtilsKt.refreshLabelNames(getInliningContext().newNamingContext(), function); + NamingUtilsKt.refreshLabelNames(function.getBody(), function.getScope()); RemoveUnusedLocalFunctionDeclarationsKt.removeUnusedLocalFunctionDeclarations(function); processedFunctions.add(function); @@ -185,7 +185,6 @@ public class JsInliner extends JsVisitorWithContextImpl { // body of inline function can contain call to lambdas that need to be inlined JsStatement inlineableBodyWithLambdasInlined = accept(inlineableBody); assert inlineableBody == inlineableBodyWithLambdasInlined; - statementContext.addPrevious(flattenStatement(inlineableBody)); /** @@ -216,7 +215,8 @@ public class JsInliner extends JsVisitorWithContextImpl { return inliningContexts.peek(); } - @NotNull FunctionContext getFunctionContext() { + @NotNull + private FunctionContext getFunctionContext() { return getInliningContext().getFunctionContext(); } @@ -245,7 +245,7 @@ public class JsInliner extends JsVisitorWithContextImpl { } } - public boolean hasToBeInlined(@NotNull JsInvocation call) { + private boolean hasToBeInlined(@NotNull JsInvocation call) { InlineStrategy strategy = MetadataProperties.getInlineStrategy(call); if (strategy == null || !strategy.isInline()) return false; diff --git a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/clean/IfStatementReduction.kt b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/clean/IfStatementReduction.kt index 7fc5d234b59..e0044e8f72b 100644 --- a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/clean/IfStatementReduction.kt +++ b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/clean/IfStatementReduction.kt @@ -31,11 +31,11 @@ class IfStatementReduction(private val root: JsStatement) { val visitor = object : JsVisitorWithContextImpl() { override fun visit(x: JsIf, ctx: JsContext): Boolean { - var thenStatement = x.thenStatement - var elseStatement = x.elseStatement - if (x.synthetic && elseStatement != null) { - thenStatement = extractSingleStatement(thenStatement) - elseStatement = extractSingleStatement(elseStatement) + val thenStatementRaw = x.thenStatement + val elseStatementRaw = x.elseStatement + if (x.synthetic && elseStatementRaw != null) { + val thenStatement = extractSingleStatement(thenStatementRaw) + val elseStatement = extractSingleStatement(elseStatementRaw) if (thenStatement is JsExpressionStatement && elseStatement is JsExpressionStatement) { val thenAssignment = JsAstUtils.decomposeAssignment(thenStatement.expression) diff --git a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/clean/TemporaryAssignmentElimination.kt b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/clean/TemporaryAssignmentElimination.kt index d1abb594bbe..c28045b9527 100644 --- a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/clean/TemporaryAssignmentElimination.kt +++ b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/clean/TemporaryAssignmentElimination.kt @@ -17,6 +17,7 @@ package org.jetbrains.kotlin.js.inline.clean import com.google.dart.compiler.backend.js.ast.* +import com.google.dart.compiler.backend.js.ast.metadata.HasMetadata import com.google.dart.compiler.backend.js.ast.metadata.synthetic import org.jetbrains.kotlin.js.inline.util.canHaveSideEffect import org.jetbrains.kotlin.js.inline.util.collectDefinedNames @@ -183,7 +184,7 @@ internal class TemporaryAssignmentElimination(private val root: JsBlock) { val usage = getUsage(name) if (usage != null) { val replacement = when (usage) { - is Usage.Return -> JsReturn(value).source(x.expression.source) + is Usage.Return -> JsReturn(value).apply { source(x.expression.source) } is Usage.VariableAssignment -> { val expr = JsAstUtils.assignment(usage.target.makeRef(), value).source(x.expression.source) val statement = JsExpressionStatement(expr) diff --git a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/util/namingUtils.kt b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/util/namingUtils.kt index 32fdc1da9b8..4148b2388fb 100644 --- a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/util/namingUtils.kt +++ b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/util/namingUtils.kt @@ -66,13 +66,11 @@ fun renameLocalNames( } fun refreshLabelNames( - context: NamingContext, - function: JsFunction -) { - val scope = function.scope + node: JsNode, + scope: JsScope +): JsNode { if (scope !is JsFunctionScope) throw AssertionError("JsFunction is expected to have JsFunctionScope") - val visitor = LabelNameRefreshingVisitor(context, scope) - visitor.accept(function.body) - context.applyRenameTo(function) + val visitor = LabelNameRefreshingVisitor(scope) + return visitor.accept(node) } diff --git a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/util/rewriters/LabelNameRefreshingVisitor.kt b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/util/rewriters/LabelNameRefreshingVisitor.kt index 69f33643feb..c4cf50f292b 100644 --- a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/util/rewriters/LabelNameRefreshingVisitor.kt +++ b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/util/rewriters/LabelNameRefreshingVisitor.kt @@ -16,29 +16,46 @@ package org.jetbrains.kotlin.js.inline.util.rewriters -import com.google.dart.compiler.backend.js.ast.JsVisitorWithContextImpl -import com.google.dart.compiler.backend.js.ast.JsFunctionScope -import org.jetbrains.kotlin.js.inline.context.NamingContext -import com.google.dart.compiler.backend.js.ast.JsFunction -import com.google.dart.compiler.backend.js.ast.JsContext -import com.google.dart.compiler.backend.js.ast.JsLabel +import com.google.dart.compiler.backend.js.ast.* +import java.util.* -class LabelNameRefreshingVisitor(val context: NamingContext, val functionScope: JsFunctionScope) : JsVisitorWithContextImpl() { - override fun visit(x: JsFunction, ctx: JsContext<*>): Boolean = false +class LabelNameRefreshingVisitor(val functionScope: JsFunctionScope) : JsVisitorWithContextImpl() { + private val substitutions: MutableMap> = mutableMapOf() - override fun visit(x: JsLabel, ctx: JsContext<*>): Boolean { + override fun visit(x: JsFunction, ctx: JsContext): Boolean = false + + override fun endVisit(x: JsBreak, ctx: JsContext) { + val label = x.label?.name + if (label != null) { + ctx.replaceMe(JsBreak(getSubstitution(label).makeRef())) + } + super.endVisit(x, ctx) + } + + override fun endVisit(x: JsContinue, ctx: JsContext) { + val label = x.label?.name + if (label != null) { + ctx.replaceMe(JsContinue(getSubstitution(label).makeRef())) + } + super.endVisit(x, ctx) + } + + override fun visit(x: JsLabel, ctx: JsContext): Boolean { val labelName = x.name val freshName = functionScope.enterLabel(labelName.ident) - - if (freshName.ident != labelName.ident) { - context.replaceName(labelName, freshName.makeRef()) - } + substitutions.getOrPut(labelName) { ArrayDeque() }.push(freshName) return super.visit(x, ctx) } - override fun endVisit(x: JsLabel, ctx: JsContext<*>) { - super.endVisit(x, ctx) + override fun endVisit(x: JsLabel, ctx: JsContext) { + val labelName = x.name + val stack = substitutions[labelName]!! + val replacementLabel = JsLabel(stack.pop(), x.statement).apply { copyMetadataFrom(x) } + ctx.replaceMe(replacementLabel) functionScope.exitLabel() + super.endVisit(x, ctx) } + + private fun getSubstitution(name: JsName) = substitutions[name]?.let { it.peek() } ?: name } diff --git a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/util/rewriters/ReturnReplacingVisitor.kt b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/util/rewriters/ReturnReplacingVisitor.kt index da77925fa29..1dd71e6c7b9 100644 --- a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/util/rewriters/ReturnReplacingVisitor.kt +++ b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/util/rewriters/ReturnReplacingVisitor.kt @@ -17,11 +17,17 @@ package org.jetbrains.kotlin.js.inline.util.rewriters import com.google.dart.compiler.backend.js.ast.* +import com.google.dart.compiler.backend.js.ast.metadata.functionDescriptor +import com.google.dart.compiler.backend.js.ast.metadata.returnTarget import com.google.dart.compiler.backend.js.ast.metadata.synthetic import org.jetbrains.kotlin.js.inline.util.canHaveSideEffect import org.jetbrains.kotlin.js.translate.utils.JsAstUtils -class ReturnReplacingVisitor(private val resultRef: JsNameRef?, private val breakLabel: JsNameRef?) : JsVisitorWithContextImpl() { +class ReturnReplacingVisitor( + private val resultRef: JsNameRef?, + private val breakLabel: JsNameRef?, + private val function: JsFunction +) : JsVisitorWithContextImpl() { /** * Prevents replacing returns in object literal @@ -34,31 +40,29 @@ class ReturnReplacingVisitor(private val resultRef: JsNameRef?, private val brea override fun visit(x: JsFunction, ctx: JsContext): Boolean = false override fun endVisit(x: JsReturn, ctx: JsContext) { + if (x.returnTarget != null && function.functionDescriptor != x.returnTarget) return + ctx.removeMe() val returnReplacement = getReturnReplacement(x.expression) if (returnReplacement != null) { - val statement = JsExpressionStatement(returnReplacement) - statement.synthetic = true - ctx.addNext(statement) + ctx.addNext(JsExpressionStatement(returnReplacement).apply { synthetic = true }) } if (breakLabel != null) { ctx.addNext(JsBreak(breakLabel)) } - } private fun getReturnReplacement(returnExpression: JsExpression?): JsExpression? { if (returnExpression != null) { if (resultRef != null) { - return JsAstUtils.assignment(resultRef, returnExpression) + return JsAstUtils.assignment(resultRef, returnExpression).apply { synthetic = true } } - if (returnExpression.canHaveSideEffect()) - return returnExpression + if (returnExpression.canHaveSideEffect()) return returnExpression } return null } -} +} \ No newline at end of file diff --git a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/util/sideEffectUtils.kt b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/util/sideEffectUtils.kt index 8eaef4188e2..c6b6ceecbdf 100644 --- a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/util/sideEffectUtils.kt +++ b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/util/sideEffectUtils.kt @@ -41,7 +41,6 @@ fun JsExpression.needToAlias(): Boolean = fun JsExpression.shouldHaveOwnAlias(): Boolean = when (this) { - is JsThisRef, is JsConditional, is JsBinaryOperation, is JsArrayLiteral -> true diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/BasicTest.java b/js/js.tests/test/org/jetbrains/kotlin/js/test/BasicTest.java index c3d17036a59..ea8615169c0 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/BasicTest.java +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/BasicTest.java @@ -385,7 +385,13 @@ public abstract class BasicTest extends KotlinTestWithEnvironment { String content = FileUtil.loadFile(new File(filename), true); KtPsiFactory psiFactory = new KtPsiFactory(getProject()); KtFile jetFile = psiFactory.createFile(content); - String packageName = jetFile.getPackageFqName().asString(); + KtFile ktFile = psiFactory.createFile(content); + return getPackageName(ktFile); + } + + @NotNull + protected static String getPackageName(KtFile ktFile) { + String packageName = ktFile.getPackageFqName().asString(); return packageName.isEmpty() ? Namer.getRootPackageName() : packageName; } diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/KotlinJSMultiFileTest.kt b/js/js.tests/test/org/jetbrains/kotlin/js/test/KotlinJSMultiFileTest.kt new file mode 100644 index 00000000000..bc3de9cdd5d --- /dev/null +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/KotlinJSMultiFileTest.kt @@ -0,0 +1,60 @@ +/* + * 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.test + +import com.intellij.openapi.util.io.FileUtil +import org.jetbrains.kotlin.psi.KtNamedFunction +import org.jetbrains.kotlin.psi.KtPsiFactory +import org.jetbrains.kotlin.test.KotlinTestUtils +import org.jetbrains.kotlin.test.KotlinTestUtils.TestFileFactory +import java.io.File + +abstract class KotlinJSMultiFileTest(path: String) : SingleFileTranslationTest(path) { + var tempDir: File? = null + + override fun doTest(filename: String) { + tempDir = FileUtil.createTempDirectory("test", "src") + tempDir!!.deleteOnExit() + try { + val file = File(filename) + val expectedText = KotlinTestUtils.doLoadFile(file) + var testPackage: String? = null + + val inputFiles = KotlinTestUtils.createTestFiles(file.name, expectedText, object : TestFileFactory { + override fun createFile(module: String?, fileName: String, text: String, directives: Map): String? { + val output = File(tempDir!!, fileName) + KotlinTestUtils.mkdirs(file.parentFile) + output.writeText(text, Charsets.UTF_8) + + val ktFile = KtPsiFactory(project).createFile(text) + val boxFunction = ktFile.declarations.find { it is KtNamedFunction && it.name == TEST_FUNCTION } + if (boxFunction != null) { + testPackage = getPackageName(ktFile) + } + + return output.path + } + + override fun createModule(name: String, dependencies: List): String? = error("Multi-module tests not supported") + }) + runFunctionOutputTestByPaths(BasicTest.DEFAULT_ECMA_VERSIONS, inputFiles, testPackage!!, BasicTest.TEST_FUNCTION, "OK") + } + finally { + tempDir!!.deleteRecursively() + } + } +} \ No newline at end of file diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/KotlinJSMutliFileTest.kt b/js/js.tests/test/org/jetbrains/kotlin/js/test/KotlinJSMutliFileTest.kt deleted file mode 100644 index 6c08b8342be..00000000000 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/KotlinJSMutliFileTest.kt +++ /dev/null @@ -1,55 +0,0 @@ -/* - * 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.test - -import com.intellij.openapi.util.io.FileUtil -import org.jetbrains.kotlin.test.KotlinTestUtils -import org.jetbrains.kotlin.test.KotlinTestUtils.TestFileFactory -import org.junit.Ignore -import java.io.File - -abstract class KotlinJSMutliFileTest(path: String) : SingleFileTranslationTest(path) { - var tempDir: File? = null - - override fun doTest(filename: String) { - tempDir = FileUtil.createTempDirectory("test", "src") - tempDir!!.deleteOnExit() - try { - val inputFiles = splitFile(filename) - runFunctionOutputTestByPaths(BasicTest.DEFAULT_ECMA_VERSIONS, inputFiles, "_", BasicTest.TEST_FUNCTION, "OK") - } - finally { - tempDir!!.deleteRecursively() - } - } - - private fun splitFile(fileName: String): List { - val file = File(fileName) - val expectedText = KotlinTestUtils.doLoadFile(file) - - return KotlinTestUtils.createTestFiles(file.name, expectedText, object : TestFileFactory { - override fun createFile(module: String?, fileName: String, text: String, directives: Map): String? { - val output = File(tempDir!!, fileName) - KotlinTestUtils.mkdirs(file.parentFile) - output.writeText(text, Charsets.UTF_8) - return output.path - } - - override fun createModule(name: String, dependencies: List): String? = null - }) - } -} \ No newline at end of file diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/SingleFileTranslationTest.java b/js/js.tests/test/org/jetbrains/kotlin/js/test/SingleFileTranslationTest.java index 59d2b0cd479..c0a395933ce 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/SingleFileTranslationTest.java +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/SingleFileTranslationTest.java @@ -53,7 +53,7 @@ public abstract class SingleFileTranslationTest extends BasicTest { runFunctionOutputTestByPath(ecmaVersions, getInputFilePath(kotlinFilename), packageName, functionName, expectedResult); } - protected void runFunctionOutputTestByPath( + private void runFunctionOutputTestByPath( @NotNull Iterable ecmaVersions, @NotNull String kotlinFilePath, @NotNull String packageName, @@ -105,10 +105,6 @@ public abstract class SingleFileTranslationTest extends BasicTest { runFunctionOutputTestByPath(DEFAULT_ECMA_VERSIONS, filePath, getPackageName(filePath), TEST_FUNCTION, "OK"); } - protected void checkBlackBoxIsOkByPaths(@NotNull List filePaths) throws Exception { - runFunctionOutputTestByPaths(DEFAULT_ECMA_VERSIONS, filePaths, getPackageName(filePaths.get(0)), TEST_FUNCTION, "OK"); - } - protected void checkOutput(@NotNull String kotlinFilename, @NotNull String expectedResult, @NotNull String... args) throws Exception { diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/LabelTestGenerated.java b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/LabelTestGenerated.java index 02221b0234a..40a636e8ea7 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/LabelTestGenerated.java +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/LabelTestGenerated.java @@ -47,6 +47,12 @@ public class LabelTestGenerated extends AbstractLabelTest { doTest(fileName); } + @TestMetadata("nestedInlineLabels.kt") + public void testNestedInlineLabels() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/labels/cases/nestedInlineLabels.kt"); + doTest(fileName); + } + @TestMetadata("nestedLabels.kt") public void testNestedLabels() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/labels/cases/nestedLabels.kt"); diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/NonLocalReturnsTestGenerated.java b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/NonLocalReturnsTestGenerated.java index 01139eecbcb..ecdc8b015ab 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/NonLocalReturnsTestGenerated.java +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/NonLocalReturnsTestGenerated.java @@ -31,12 +31,6 @@ import java.util.regex.Pattern; @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) public class NonLocalReturnsTestGenerated extends AbstractNonLocalReturnsTest { - @TestMetadata("kt8948v2.kt") - public void ignoredKt8948v2() throws Exception { - String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/nonLocalReturns/kt8948v2.kt"); - doTest(fileName); - } - public void testAllFilesPresentInNonLocalReturns() throws Exception { KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/boxInline/nonLocalReturns"), Pattern.compile("^(.+)\\.kt$"), true); } @@ -65,6 +59,12 @@ public class NonLocalReturnsTestGenerated extends AbstractNonLocalReturnsTest { doTest(fileName); } + @TestMetadata("kt8948v2.kt") + public void testKt8948v2() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/nonLocalReturns/kt8948v2.kt"); + doTest(fileName); + } + @TestMetadata("nestedNonLocals.kt") public void testNestedNonLocals() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/nonLocalReturns/nestedNonLocals.kt"); diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/abstractClassesForGeneratedTests.kt b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/abstractClassesForGeneratedTests.kt index 3cdbc7df685..9868811ebf8 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/abstractClassesForGeneratedTests.kt +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/abstractClassesForGeneratedTests.kt @@ -58,4 +58,4 @@ abstract class AbstractClassesTest : AbstractBlackBoxTest("classes/") abstract class AbstractSuperTest : AbstractBlackBoxTest("super/") -abstract class AbstractNonLocalReturnsTest : KotlinJSMutliFileTest("inline.generated/nonLocalReturns/") +abstract class AbstractNonLocalReturnsTest : KotlinJSMultiFileTest("inline.generated/nonLocalReturns/") diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/utils/DirectiveTestUtils.java b/js/js.tests/test/org/jetbrains/kotlin/js/test/utils/DirectiveTestUtils.java index e69ced2ccd3..0c8fd84f5e8 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/utils/DirectiveTestUtils.java +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/utils/DirectiveTestUtils.java @@ -134,13 +134,11 @@ public class DirectiveTestUtils { private static final DirectiveHandler COUNT_LABELS = new CountNodesDirective("CHECK_LABELS_COUNT", JsLabel.class) { @Override protected int getActualCountFor(@NotNull JsLabel node, @NotNull ArgumentsHelper arguments) { - String labelName = arguments.getNamedArgument("name"); - - if (node.getName().getIdent().equals(labelName)) { + String labelName = arguments.findNamedArgument("name"); + if (labelName == null) { return 1; } - - return 0; + return node.getName().getIdent().equals(labelName) ? 1 : 0; } }; diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/callTranslator/CallInfoExtensions.kt b/js/js.translator/src/org/jetbrains/kotlin/js/translate/callTranslator/CallInfoExtensions.kt index e12f8cbffd7..da5cbd0fecd 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/callTranslator/CallInfoExtensions.kt +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/callTranslator/CallInfoExtensions.kt @@ -16,6 +16,7 @@ package org.jetbrains.kotlin.js.translate.callTranslator +import com.google.dart.compiler.backend.js.ast.JsEmptyExpression import com.google.dart.compiler.backend.js.ast.JsExpression import com.google.dart.compiler.backend.js.ast.JsName import org.jetbrains.kotlin.descriptors.CallableDescriptor @@ -72,7 +73,7 @@ fun VariableAccessInfo.constructAccessExpression(ref: JsExpression): JsExpressio if (isGetAccess()) { return ref } else { - return JsAstUtils.assignment(ref, value!!) + return if (value !is JsEmptyExpression) JsAstUtils.assignment(ref, value!!) else context.emptyExpression } } diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/callTranslator/FunctionCallCases.kt b/js/js.translator/src/org/jetbrains/kotlin/js/translate/callTranslator/FunctionCallCases.kt index 3d4b3bf7d85..42b095fdb17 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/callTranslator/FunctionCallCases.kt +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/callTranslator/FunctionCallCases.kt @@ -294,6 +294,10 @@ object DynamicOperatorCallCase : FunctionCallCase() { } fun FunctionCallInfo.translateFunctionCall(): JsExpression { + // When call has `continue` or `break` as one of its argument, they'll be temporarily represented ad JsEmptyExpression. + // Such call should not be translated. All side effects are already extracted by this point. + if (argumentsInfo.valueArguments.any { JsAstUtils.isEmptyExpression(it) }) return context.emptyExpression + val intrinsic = DelegateFunctionIntrinsic.intrinsic(this) return when { diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/TranslationContext.java b/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/TranslationContext.java index 090fe9e23aa..c86045cb888 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/TranslationContext.java +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/TranslationContext.java @@ -113,26 +113,26 @@ public class TranslationContext { @NotNull public TranslationContext contextWithScope(@NotNull JsFunction fun) { - return this.newFunctionBody(fun, aliasingContext); + return this.newFunctionBody(fun, aliasingContext, declarationDescriptor); } @NotNull - public TranslationContext newFunctionBody(@NotNull JsFunction fun, @Nullable AliasingContext aliasingContext) { + public TranslationContext newFunctionBody(@NotNull JsFunction fun, @Nullable AliasingContext aliasingContext, + DeclarationDescriptor descriptor) { DynamicContext dynamicContext = DynamicContext.newContext(fun.getScope(), fun.getBody()); if (aliasingContext == null) { aliasingContext = this.aliasingContext.inner(); } - return new TranslationContext(this, this.staticContext, dynamicContext, aliasingContext, this.usageTracker, null, - this.declarationDescriptor); + return new TranslationContext(this, this.staticContext, dynamicContext, aliasingContext, this.usageTracker, null, descriptor); } @NotNull public TranslationContext newFunctionBodyWithUsageTracker(@NotNull JsFunction fun, @NotNull MemberDescriptor descriptor) { DynamicContext dynamicContext = DynamicContext.newContext(fun.getScope(), fun.getBody()); UsageTracker usageTracker = new UsageTracker(this.usageTracker, descriptor, fun.getScope()); - return new TranslationContext(this, this.staticContext, dynamicContext, this.aliasingContext.inner(), usageTracker, this.definitionPlace, - descriptor); + return new TranslationContext(this, this.staticContext, dynamicContext, this.aliasingContext.inner(), usageTracker, + this.definitionPlace, descriptor); } @NotNull @@ -149,8 +149,7 @@ public class TranslationContext { @NotNull public TranslationContext newDeclaration(@NotNull DeclarationDescriptor descriptor, @Nullable DefinitionPlace place) { DynamicContext dynamicContext = DynamicContext.newContext(getScopeForDescriptor(descriptor), getBlockForDescriptor(descriptor)); - return new TranslationContext(this, staticContext, dynamicContext, aliasingContext, usageTracker, place, - descriptor); + return new TranslationContext(this, staticContext, dynamicContext, aliasingContext, usageTracker, place, descriptor); } @NotNull @@ -175,7 +174,7 @@ public class TranslationContext { } @NotNull - public JsBlock getBlockForDescriptor(@NotNull DeclarationDescriptor descriptor) { + private JsBlock getBlockForDescriptor(@NotNull DeclarationDescriptor descriptor) { if (descriptor instanceof CallableDescriptor) { return getFunctionObject((CallableDescriptor) descriptor).getBody(); } diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/declaration/DelegationTranslator.kt b/js/js.translator/src/org/jetbrains/kotlin/js/translate/declaration/DelegationTranslator.kt index 56f34b59cc0..963e6063c14 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/declaration/DelegationTranslator.kt +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/declaration/DelegationTranslator.kt @@ -144,7 +144,8 @@ class DelegationTranslator( val delegateRefName = context().getScopeForDescriptor(setterDescriptor).declareName(delegateName) val delegateRef = JsNameRef(delegateRefName, JsLiteral.THIS) - val setExpression = if (DescriptorUtils.isExtension(descriptor)) { + // TODO: remove explicit type annotation when Kotlin compiler works this out + val setExpression: JsExpression = if (DescriptorUtils.isExtension(descriptor)) { val setterName = context().getNameForDescriptor(setterDescriptor) val setterNameRef = JsNameRef(setterName, delegateRef) val extensionFunctionReceiverName = jsFunction.scope.declareName(Namer.getReceiverParameterName()) diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/ExpressionVisitor.java b/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/ExpressionVisitor.java index 72ac9aed74c..c5138c7ad65 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/ExpressionVisitor.java +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/ExpressionVisitor.java @@ -61,10 +61,10 @@ import static org.jetbrains.kotlin.js.translate.utils.JsAstUtils.convertToStatem import static org.jetbrains.kotlin.js.translate.utils.JsAstUtils.newVar; import static org.jetbrains.kotlin.js.translate.utils.JsDescriptorUtils.getReceiverParameterForDeclaration; import static org.jetbrains.kotlin.js.translate.utils.TranslationUtils.translateInitializerForProperty; -import static org.jetbrains.kotlin.resolve.BindingContext.DECLARATION_TO_DESCRIPTOR; -import static org.jetbrains.kotlin.resolve.BindingContext.LABEL_TARGET; -import static org.jetbrains.kotlin.resolve.BindingContext.REFERENCE_TARGET; +import static org.jetbrains.kotlin.resolve.BindingContext.*; import static org.jetbrains.kotlin.resolve.BindingContextUtils.isVarCapturedInClosure; +import static org.jetbrains.kotlin.types.expressions.ExpressionTypingUtils.isFunctionExpression; +import static org.jetbrains.kotlin.types.expressions.ExpressionTypingUtils.isFunctionLiteral; public final class ExpressionVisitor extends TranslatorVisitor { @Override @@ -137,8 +137,7 @@ public final class ExpressionVisitor extends TranslatorVisitor { @Override @NotNull - public JsNode visitReturnExpression(@NotNull KtReturnExpression jetReturnExpression, - @NotNull TranslationContext context) { + public JsNode visitReturnExpression(@NotNull KtReturnExpression jetReturnExpression, @NotNull TranslationContext context) { KtExpression returned = jetReturnExpression.getReturnedExpression(); // TODO: add related descriptor to context and use it here @@ -146,14 +145,51 @@ public final class ExpressionVisitor extends TranslatorVisitor { if (parent instanceof KtSecondaryConstructor) { return new JsReturn(new JsNameRef(Namer.ANOTHER_THIS_PARAMETER_NAME)).source(jetReturnExpression); } + + JsReturn jsReturn; if (returned == null) { - return new JsReturn(null).source(jetReturnExpression); + jsReturn = new JsReturn(null); } - JsExpression jsReturnExpression = translateAsExpression(returned, context); - if (JsAstUtils.isEmptyExpression(jsReturnExpression)) { - return context.getEmptyExpression(); + else { + JsExpression jsReturnExpression = translateAsExpression(returned, context); + if (JsAstUtils.isEmptyExpression(jsReturnExpression)) { + return context.getEmptyExpression(); + } + + jsReturn = new JsReturn(jsReturnExpression); } - return new JsReturn(jsReturnExpression).source(jetReturnExpression); + + MetadataProperties.setReturnTarget(jsReturn, getNonLocalReturnTarget(jetReturnExpression, context)); + + return jsReturn.source(jetReturnExpression); + } + + @Nullable + private static FunctionDescriptor getNonLocalReturnTarget( + @NotNull KtReturnExpression expression, + @NotNull TranslationContext context + ) { + DeclarationDescriptor descriptor = context.getDeclarationDescriptor(); + assert descriptor instanceof CallableMemberDescriptor : "Return expression can only be inside callable declaration: " + + PsiUtilsKt.getTextWithLocation(expression); + KtSimpleNameExpression target = expression.getTargetLabel(); + + //call inside lambda + if (isFunctionLiteral(descriptor) || isFunctionExpression(descriptor)) { + if (target == null) { + if (isFunctionLiteral(descriptor)) { + return BindingContextUtils.getContainingFunctionSkipFunctionLiterals(descriptor, true).getFirst(); + } + } + else { + PsiElement element = context.bindingContext().get(LABEL_TARGET, target); + descriptor = context.bindingContext().get(DECLARATION_TO_DESCRIPTOR, element); + } + } + + assert descriptor == null || descriptor instanceof FunctionDescriptor : + "Function descriptor expected to be target of return label: " + PsiUtilsKt.getTextWithLocation(expression); + return (FunctionDescriptor) descriptor; } @Override @@ -260,8 +296,7 @@ public final class ExpressionVisitor extends TranslatorVisitor { @Override @NotNull - public JsNode visitStringTemplateExpression(@NotNull KtStringTemplateExpression expression, - @NotNull TranslationContext context) { + public JsNode visitStringTemplateExpression(@NotNull KtStringTemplateExpression expression, @NotNull TranslationContext context) { JsStringLiteral stringLiteral = resolveAsStringConstant(expression, context); if (stringLiteral != null) { return stringLiteral; @@ -289,15 +324,12 @@ public final class ExpressionVisitor extends TranslatorVisitor { @Override @NotNull - public JsNode visitDotQualifiedExpression(@NotNull KtDotQualifiedExpression expression, - @NotNull TranslationContext context) { + public JsNode visitDotQualifiedExpression(@NotNull KtDotQualifiedExpression expression, @NotNull TranslationContext context) { return QualifiedExpressionTranslator.translateQualifiedExpression(expression, context); } @Override - public JsNode visitLabeledExpression( - @NotNull KtLabeledExpression expression, TranslationContext context - ) { + public JsNode visitLabeledExpression(@NotNull KtLabeledExpression expression, @NotNull TranslationContext context) { KtExpression baseExpression = expression.getBaseExpression(); assert baseExpression != null; @@ -400,15 +432,13 @@ public final class ExpressionVisitor extends TranslatorVisitor { @Override @NotNull - public JsNode visitBreakExpression(@NotNull KtBreakExpression expression, - @NotNull TranslationContext context) { + public JsNode visitBreakExpression(@NotNull KtBreakExpression expression, @NotNull TranslationContext context) { return new JsBreak(getTargetLabel(expression, context)).source(expression); } @Override @NotNull - public JsNode visitContinueExpression(@NotNull KtContinueExpression expression, - @NotNull TranslationContext context) { + public JsNode visitContinueExpression(@NotNull KtContinueExpression expression, @NotNull TranslationContext context) { return new JsContinue(getTargetLabel(expression, context)).source(expression); } diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/FunctionTranslator.java b/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/FunctionTranslator.java index c4786e351e1..5be4b2814f9 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/FunctionTranslator.java +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/FunctionTranslator.java @@ -69,6 +69,7 @@ public final class FunctionTranslator extends AbstractTranslator { : message(descriptor, "Function " + functionDeclaration.getText() + " processed for the second time."); //NOTE: it's important we compute the context before we start the computation this.functionBodyContext = getFunctionBodyContext(); + MetadataProperties.setFunctionDescriptor(functionObject, descriptor); } @NotNull @@ -84,7 +85,7 @@ public final class FunctionTranslator extends AbstractTranslator { else { aliasingContext = null; } - return context().newFunctionBody(functionObject, aliasingContext); + return context().newFunctionBody(functionObject, aliasingContext, descriptor); } @NotNull @@ -116,7 +117,8 @@ public final class FunctionTranslator extends AbstractTranslator { assert descriptor instanceof ConstructorDescriptor || descriptor.getModality().equals(Modality.ABSTRACT); return; } - functionObject.getBody().getStatements().addAll(translateFunctionBody(descriptor, functionDeclaration, functionBodyContext).getStatements()); + JsBlock body = translateFunctionBody(descriptor, functionDeclaration, functionBodyContext); + functionObject.getBody().getStatements().addAll(body.getStatements()); } @NotNull diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/LiteralFunctionTranslator.kt b/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/LiteralFunctionTranslator.kt index 6fbf30d8da0..f6c332c099b 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/LiteralFunctionTranslator.kt +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/LiteralFunctionTranslator.kt @@ -17,6 +17,7 @@ package org.jetbrains.kotlin.js.translate.expression import com.google.dart.compiler.backend.js.ast.* +import com.google.dart.compiler.backend.js.ast.metadata.functionDescriptor import com.google.dart.compiler.backend.js.ast.metadata.isLocal import com.google.dart.compiler.backend.js.ast.metadata.staticRef import org.jetbrains.kotlin.descriptors.* @@ -44,6 +45,7 @@ class LiteralFunctionTranslator(context: TranslationContext) : AbstractTranslato FunctionTranslator.addParameters(lambda.parameters, descriptor, functionContext) val functionBody = translateFunctionBody(descriptor, declaration, functionContext) lambda.body.statements.addAll(functionBody.statements) + lambda.functionDescriptor = descriptor val tracker = functionContext.usageTracker()!! @@ -142,9 +144,12 @@ private fun moveCapturedLocalInside(capturingFunction: JsFunction, capturedName: * and localFunAlias declaration is moved inside. * * For example: + * + * ``` * val x = 0 - * [inline] fun id() = x + * inline fun id() = x * val lambda = {println(id())} + * ``` * * `lambda` should capture x in this case */ diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/general/Translation.java b/js/js.translator/src/org/jetbrains/kotlin/js/translate/general/Translation.java index 96312de3732..d005ba69758 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/general/Translation.java +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/general/Translation.java @@ -93,10 +93,6 @@ public final class Translation { context.moveVarsFrom(innerContext); block.getStatements().addAll(innerContext.dynamicContext().jsBlock().getStatements()); - if (BindingContextUtilsKt.isUnreachableCode(expression, context.bindingContext())) { - return context.getEmptyExpression(); - } - return result; } diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/utils/FunctionBodyTranslator.java b/js/js.translator/src/org/jetbrains/kotlin/js/translate/utils/FunctionBodyTranslator.java index d15899e4eab..de5763e5329 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/utils/FunctionBodyTranslator.java +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/utils/FunctionBodyTranslator.java @@ -17,6 +17,7 @@ package org.jetbrains.kotlin.js.translate.utils; import com.google.dart.compiler.backend.js.ast.*; +import com.google.dart.compiler.backend.js.ast.metadata.MetadataProperties; import org.jetbrains.annotations.NotNull; import org.jetbrains.kotlin.builtins.KotlinBuiltIns; import org.jetbrains.kotlin.descriptors.FunctionDescriptor; @@ -107,7 +108,7 @@ public final class FunctionBodyTranslator extends AbstractTranslator { } @NotNull - private static JsNode lastExpressionReturned(@NotNull JsNode body) { + private JsNode lastExpressionReturned(@NotNull JsNode body) { return mutateLastExpression(body, new Mutator() { @Override @NotNull @@ -115,7 +116,9 @@ public final class FunctionBodyTranslator extends AbstractTranslator { if (!(node instanceof JsExpression)) { return node; } - return new JsReturn((JsExpression)node); + JsReturn jsReturn = new JsReturn((JsExpression)node); + MetadataProperties.setReturnTarget(jsReturn, descriptor); + return jsReturn; } }); } diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/utils/JsAstUtils.java b/js/js.translator/src/org/jetbrains/kotlin/js/translate/utils/JsAstUtils.java index 3276ff250cb..d4b65b62066 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/utils/JsAstUtils.java +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/utils/JsAstUtils.java @@ -297,7 +297,7 @@ public final class JsAstUtils { } @NotNull - public static JsExpression assignment(@NotNull JsExpression left, @NotNull JsExpression right) { + public static JsBinaryOperation assignment(@NotNull JsExpression left, @NotNull JsExpression right) { return new JsBinaryOperation(JsBinaryOperator.ASG, left, right); } diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/utils/TranslationUtils.java b/js/js.translator/src/org/jetbrains/kotlin/js/translate/utils/TranslationUtils.java index f2d2979e760..8ad8682d17e 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/utils/TranslationUtils.java +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/utils/TranslationUtils.java @@ -22,6 +22,7 @@ import org.jetbrains.annotations.Nullable; import org.jetbrains.kotlin.builtins.KotlinBuiltIns; import org.jetbrains.kotlin.descriptors.*; import org.jetbrains.kotlin.descriptors.impl.AnonymousFunctionDescriptor; +import org.jetbrains.kotlin.js.translate.context.Namer; import org.jetbrains.kotlin.js.translate.context.TemporaryConstVariable; import org.jetbrains.kotlin.js.translate.context.TranslationContext; import org.jetbrains.kotlin.js.translate.general.Translation; @@ -185,12 +186,6 @@ public final class TranslationUtils { return Translation.translateAsExpression(baseExpression, context); } - @NotNull - public static JsExpression translateLeftExpression(@NotNull TranslationContext context, - @NotNull KtBinaryExpression expression) { - return translateLeftExpression(context, expression, context.dynamicContext().jsBlock()); - } - @NotNull public static JsExpression translateLeftExpression( @NotNull TranslationContext context, @@ -252,7 +247,7 @@ public final class TranslationUtils { @NotNull public static JsConditional sure(@NotNull JsExpression expression, @NotNull TranslationContext context) { - JsInvocation throwNPE = new JsInvocation(context.namer().throwNPEFunctionRef()); + JsInvocation throwNPE = new JsInvocation(Namer.throwNPEFunctionRef()); JsConditional ensureNotNull = notNullConditional(expression, throwNPE, context); JsExpression thenExpression = ensureNotNull.getThenExpression(); diff --git a/js/js.translator/testData/expression/evaluationOrder/cases/intrinsicWithBreakContinueReturn.kt b/js/js.translator/testData/expression/evaluationOrder/cases/intrinsicWithBreakContinueReturn.kt index 5f000a67c65..2313b1fcf4a 100644 --- a/js/js.translator/testData/expression/evaluationOrder/cases/intrinsicWithBreakContinueReturn.kt +++ b/js/js.translator/testData/expression/evaluationOrder/cases/intrinsicWithBreakContinueReturn.kt @@ -16,6 +16,11 @@ fun barLt(): String { 10 < (return "lt") as Int } +fun setGlobal(i: Int): Int { + global = "setGlobal" + return i +} + fun box(): String { var b: Boolean @@ -66,6 +71,15 @@ fun box(): String { assertEquals(2, i, "break 6") assertEquals("A", global, "break 6") + i = 0 + while(i++<5) { + if (i==2) { + bVarArray[setGlobal(0)] = 10 < (break as Int) + } + } + assertEquals(2, i, "break 6a") + assertEquals("setGlobal", global, "break 6a") + i = 0 global = "" while(i++<5) { diff --git a/js/js.translator/testData/labels/cases/nestedInlineLabels.kt b/js/js.translator/testData/labels/cases/nestedInlineLabels.kt new file mode 100644 index 00000000000..8764189d533 --- /dev/null +++ b/js/js.translator/testData/labels/cases/nestedInlineLabels.kt @@ -0,0 +1,46 @@ +package foo + +var state = false + +inline fun blockImpl(p: () -> Unit) { + if (state) return + + p() +} + +inline fun block(p: () -> Unit) { + if (state) return + + blockImpl(p) +} + +fun test(x: Int): Int { + block outer@ { + block inner@ { + if (x < 10) return@inner + if (x == 10) return@outer + + block innermost@ { + if (x > 500) { + return 500500 + } + } + + return x + } + + if (x < 5) return@outer + return x + 10 + } + return x + 100 +} + +fun box(): String { + assertEquals(16, test(6)) + assertEquals(104, test(4)) + assertEquals(110, test(110)) + assertEquals(11, test(11)) + assertEquals(500500, test(502)) + + return "OK" +} \ No newline at end of file