KT-7819 Implement non-local returns
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
+16
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -31,11 +31,11 @@ class IfStatementReduction(private val root: JsStatement) {
|
||||
|
||||
val visitor = object : JsVisitorWithContextImpl() {
|
||||
override fun visit(x: JsIf, ctx: JsContext<JsNode>): 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)
|
||||
|
||||
+2
-1
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
+32
-15
@@ -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<JsName, ArrayDeque<JsName>> = mutableMapOf()
|
||||
|
||||
override fun visit(x: JsLabel, ctx: JsContext<*>): Boolean {
|
||||
override fun visit(x: JsFunction, ctx: JsContext<JsNode>): Boolean = false
|
||||
|
||||
override fun endVisit(x: JsBreak, ctx: JsContext<JsNode>) {
|
||||
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<JsNode>) {
|
||||
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<JsNode>): 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<JsNode>) {
|
||||
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
|
||||
}
|
||||
|
||||
+13
-9
@@ -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<JsNode>): Boolean = false
|
||||
|
||||
override fun endVisit(x: JsReturn, ctx: JsContext<JsNode>) {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -41,7 +41,6 @@ fun JsExpression.needToAlias(): Boolean =
|
||||
|
||||
fun JsExpression.shouldHaveOwnAlias(): Boolean =
|
||||
when (this) {
|
||||
is JsThisRef,
|
||||
is JsConditional,
|
||||
is JsBinaryOperation,
|
||||
is JsArrayLiteral -> true
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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<String, String> {
|
||||
override fun createFile(module: String?, fileName: String, text: String, directives: Map<String, String>): 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>): String? = error("Multi-module tests not supported")
|
||||
})
|
||||
runFunctionOutputTestByPaths(BasicTest.DEFAULT_ECMA_VERSIONS, inputFiles, testPackage!!, BasicTest.TEST_FUNCTION, "OK")
|
||||
}
|
||||
finally {
|
||||
tempDir!!.deleteRecursively()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<String> {
|
||||
val file = File(fileName)
|
||||
val expectedText = KotlinTestUtils.doLoadFile(file)
|
||||
|
||||
return KotlinTestUtils.createTestFiles(file.name, expectedText, object : TestFileFactory<String, String> {
|
||||
override fun createFile(module: String?, fileName: String, text: String, directives: Map<String, String>): 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>): String? = null
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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<EcmaVersion> 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<String> 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 {
|
||||
|
||||
@@ -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");
|
||||
|
||||
+6
-6
@@ -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");
|
||||
|
||||
+1
-1
@@ -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/")
|
||||
|
||||
@@ -134,13 +134,11 @@ public class DirectiveTestUtils {
|
||||
private static final DirectiveHandler COUNT_LABELS = new CountNodesDirective<JsLabel>("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;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
+2
-1
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+4
@@ -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 {
|
||||
|
||||
+8
-9
@@ -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();
|
||||
}
|
||||
|
||||
+2
-1
@@ -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())
|
||||
|
||||
+51
-21
@@ -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<JsNode> {
|
||||
@Override
|
||||
@@ -137,8 +137,7 @@ public final class ExpressionVisitor extends TranslatorVisitor<JsNode> {
|
||||
|
||||
@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<JsNode> {
|
||||
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<JsNode> {
|
||||
|
||||
@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<JsNode> {
|
||||
|
||||
@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<JsNode> {
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
|
||||
+4
-2
@@ -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
|
||||
|
||||
+6
-1
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
+5
-2
@@ -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;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
Vendored
+14
@@ -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) {
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
Reference in New Issue
Block a user