diff --git a/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/generators/ExpressionBodyGenerator.kt b/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/generators/ExpressionBodyGenerator.kt index 5a470f617a9..997b4a0f918 100644 --- a/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/generators/ExpressionBodyGenerator.kt +++ b/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/generators/ExpressionBodyGenerator.kt @@ -17,10 +17,12 @@ package org.jetbrains.kotlin.psi2ir.generators import org.jetbrains.kotlin.descriptors.CallableDescriptor -import org.jetbrains.kotlin.ir.expressions.* +import org.jetbrains.kotlin.ir.expressions.IrBlockImpl +import org.jetbrains.kotlin.ir.expressions.IrExpression +import org.jetbrains.kotlin.ir.expressions.IrLoop +import org.jetbrains.kotlin.ir.expressions.IrReturnImpl import org.jetbrains.kotlin.psi.KtBlockExpression import org.jetbrains.kotlin.psi.KtExpression -import org.jetbrains.kotlin.psi.KtWhileExpression import org.jetbrains.kotlin.psi.psiUtil.endOffset import org.jetbrains.kotlin.psi.psiUtil.startOffset import java.util.* @@ -62,5 +64,8 @@ class ExpressionBodyGenerator(val scopeOwner: CallableDescriptor, override val c fun putLoop(expression: KtExpression, irLoop: IrLoop) { loopTable[expression] = irLoop } + + fun getLoop(expression: KtExpression): IrLoop? = + loopTable[expression] } diff --git a/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/generators/LoopExpressionGenerator.kt b/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/generators/LoopExpressionGenerator.kt index c68746655bd..a47bbf73bd1 100644 --- a/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/generators/LoopExpressionGenerator.kt +++ b/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/generators/LoopExpressionGenerator.kt @@ -17,13 +17,15 @@ package org.jetbrains.kotlin.psi2ir.generators import org.jetbrains.kotlin.ir.expressions.* -import org.jetbrains.kotlin.psi.KtDoWhileExpression -import org.jetbrains.kotlin.psi.KtWhileExpression -import org.jetbrains.kotlin.psi.KtWhileExpressionBase +import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.endOffset +import org.jetbrains.kotlin.psi.psiUtil.getParentOfType import org.jetbrains.kotlin.psi.psiUtil.startOffset -class LoopExpressionGenerator(val statementGenerator: StatementGenerator) { +class LoopExpressionGenerator(val statementGenerator: StatementGenerator) : GeneratorWithScope { + override val scope: Scope get() = statementGenerator.scope + override val context: GeneratorContext get() = statementGenerator.context + fun generateWhileExpression(expression: KtWhileExpression): IrExpression = generateConditionalLoop(expression, IrWhileLoopImpl(expression.startOffset, expression.endOffset, IrOperator.WHILE_LOOP)) @@ -36,4 +38,47 @@ class LoopExpressionGenerator(val statementGenerator: StatementGenerator) { irLoop.body = statementGenerator.generateExpression(expression.body!!) return irLoop } + + fun generateBreakExpression(expression: KtBreakExpression): IrExpression { + val parentLoop = findParentLoop(expression) + return IrBreakImpl(expression.startOffset, expression.endOffset, context.builtIns.nothingType, parentLoop) + } + + fun generateContinueExpression(expression: KtContinueExpression): IrExpression { + val parentLoop = findParentLoop(expression) + return IrContinueImpl(expression.startOffset, expression.endOffset, context.builtIns.nothingType, parentLoop) + } + + private fun findParentLoop(expression: KtExpressionWithLabel): IrLoop = + findParentLoop(expression, expression.getTargetLabel()?.getReferencedName()) + + private fun findParentLoop(expression: KtExpression, targetLabel: String?): IrLoop { + var finger: KtExpression? = expression + while (finger != null) { + finger = finger.getParentOfType(true) + if (finger == null) { + break + } + if (targetLabel == null) { + return getLoop(finger) + } + else { + val parent = finger.parent + if (parent is KtLabeledExpression) { + val label = parent.getLabelName()!! + if (targetLabel == label) { + return getLoop(finger) + } + } + } + } + throw AssertionError("No parent loop for break/continue @$targetLabel") + } + + private fun getLoop(finger: KtLoopExpression): IrLoop { + return statementGenerator.expressionBodyGenerator.getLoop(finger) ?: + throw AssertionError("Loop was not visited:\n${finger.text}") + } + + } \ No newline at end of file diff --git a/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/generators/StatementGenerator.kt b/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/generators/StatementGenerator.kt index 7816ff1aaa4..01d68d1be6b 100644 --- a/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/generators/StatementGenerator.kt +++ b/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/generators/StatementGenerator.kt @@ -277,6 +277,12 @@ class StatementGenerator( override fun visitDoWhileExpression(expression: KtDoWhileExpression, data: Nothing?): IrStatement = LoopExpressionGenerator(this).generateDoWhileExpression(expression) + + override fun visitBreakExpression(expression: KtBreakExpression, data: Nothing?): IrStatement = + LoopExpressionGenerator(this).generateBreakExpression(expression) + + override fun visitContinueExpression(expression: KtContinueExpression, data: Nothing?): IrStatement = + LoopExpressionGenerator(this).generateContinueExpression(expression) } diff --git a/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/expressions/IrBreakContinue.kt b/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/expressions/IrBreakContinue.kt new file mode 100644 index 00000000000..854b4892530 --- /dev/null +++ b/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/expressions/IrBreakContinue.kt @@ -0,0 +1,74 @@ +/* + * 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.ir.expressions + +import org.jetbrains.kotlin.ir.IrElement +import org.jetbrains.kotlin.ir.util.dump +import org.jetbrains.kotlin.ir.util.render +import org.jetbrains.kotlin.ir.visitors.IrElementVisitor +import org.jetbrains.kotlin.types.KotlinType + +interface IrBreakContinue : IrExpression { + var loop: IrLoop +} + +interface IrBreak: IrBreakContinue + +interface IrContinue: IrBreakContinue + +fun IrBreakContinue.getDepth(): Int { + var depth = 0 + var finger: IrElement = this + while (true) { + val parent = finger.parent ?: throw AssertionError("No parent loop in tree for ${this.render()}:\n${finger.dump()}") + if (parent is IrLoop) { + if (parent == loop) { + return depth + } + depth++ + } + finger = parent + } +} + +abstract class IrBreakContinueBase( + startOffset: Int, + endOffset: Int, + type: KotlinType, + override var loop: IrLoop +) : IrTerminalExpressionBase(startOffset, endOffset, type), IrBreakContinue { +} + +class IrBreakImpl( + startOffset: Int, + endOffset: Int, + type: KotlinType, + loop: IrLoop +) : IrBreakContinueBase(startOffset, endOffset, type, loop), IrBreak { + override fun accept(visitor: IrElementVisitor, data: D): R = + visitor.visitBreak(this, data) +} + +class IrContinueImpl( + startOffset: Int, + endOffset: Int, + type: KotlinType, + loop: IrLoop +) : IrBreakContinueBase(startOffset, endOffset, type, loop), IrContinue { + override fun accept(visitor: IrElementVisitor, data: D): R = + visitor.visitContinue(this, data) +} \ No newline at end of file diff --git a/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/util/RenderIrElement.kt b/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/util/RenderIrElement.kt index be908348f84..abdf3bcf99e 100644 --- a/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/util/RenderIrElement.kt +++ b/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/util/RenderIrElement.kt @@ -106,6 +106,12 @@ class RenderIrElementVisitor : IrElementVisitor { override fun visitDoWhileLoop(loop: IrDoWhileLoop, data: Nothing?): String = "DO_WHILE operator=${loop.operator}" + override fun visitBreak(jump: IrBreak, data: Nothing?): String = + "BREAK loop.operator=${jump.loop.operator} depth=${jump.getDepth()}" + + override fun visitContinue(jump: IrContinue, data: Nothing?): String = + "CONTINUE loop.operator=${jump.loop.operator} depth=${jump.getDepth()}" + override fun visitDummyDeclaration(declaration: IrDummyDeclaration, data: Nothing?): String = "DUMMY ${declaration.descriptor.name}" diff --git a/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/visitors/IrElementVisitor.kt b/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/visitors/IrElementVisitor.kt index 0c8f858b9f4..77902d4dd11 100644 --- a/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/visitors/IrElementVisitor.kt +++ b/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/visitors/IrElementVisitor.kt @@ -61,8 +61,13 @@ interface IrElementVisitor { fun visitWhileLoop(loop: IrWhileLoop, data: D) = visitLoop(loop, data) fun visitDoWhileLoop(loop: IrDoWhileLoop, data: D) = visitLoop(loop, data) + fun visitBreakContinue(jump: IrBreakContinue, data: D) = visitExpression(jump, data) + fun visitBreak(jump: IrBreak, data: D) = visitBreakContinue(jump, data) + fun visitContinue(jump: IrContinue, data: D) = visitBreakContinue(jump, data) + // NB Use it only for testing purposes; will be removed as soon as all Kotlin expression types are covered fun visitDummyDeclaration(declaration: IrDummyDeclaration, data: D) = visitDeclaration(declaration, data) fun visitDummyExpression(expression: IrDummyExpression, data: D) = visitExpression(expression, data) + } diff --git a/compiler/testData/ir/irText/breakContinue.kt b/compiler/testData/ir/irText/breakContinue.kt new file mode 100644 index 00000000000..c501aa0b3f9 --- /dev/null +++ b/compiler/testData/ir/irText/breakContinue.kt @@ -0,0 +1,39 @@ +fun test1() { + while (true) { break } + do { break } while (true) + while (true) { continue } + do { continue } while (true) +} + +fun test2() { + OUTER@while(true) { + INNER@while(true) { + break@INNER + break@OUTER + } + break@OUTER + } + OUTER@while(true) { + INNER@while(true) { + continue@INNER + continue@OUTER + } + continue@OUTER + } +} + +fun test3() { + L@while(true) { + L@while(true) { + break@L + } + break@L + } + L@while(true) { + L@while(true) { + continue@L + } + continue@L + } +} + diff --git a/compiler/testData/ir/irText/breakContinue.txt b/compiler/testData/ir/irText/breakContinue.txt new file mode 100644 index 00000000000..14ba2caf2a2 --- /dev/null +++ b/compiler/testData/ir/irText/breakContinue.txt @@ -0,0 +1,60 @@ +IrFile /breakContinue.kt + IrFunction public fun test1(): kotlin.Unit + IrExpressionBody + BLOCK type= hasResult=false operator=null + WHILE operator=WHILE_LOOP + condition: CONST Boolean type=kotlin.Boolean value='true' + body: BLOCK type= hasResult=false operator=null + BREAK loop.operator=WHILE_LOOP depth=0 + DO_WHILE operator=DO_WHILE_LOOP + body: BLOCK type= hasResult=false operator=null + BREAK loop.operator=DO_WHILE_LOOP depth=0 + condition: CONST Boolean type=kotlin.Boolean value='true' + WHILE operator=WHILE_LOOP + condition: CONST Boolean type=kotlin.Boolean value='true' + body: BLOCK type= hasResult=false operator=null + CONTINUE loop.operator=WHILE_LOOP depth=0 + DO_WHILE operator=DO_WHILE_LOOP + body: BLOCK type= hasResult=false operator=null + CONTINUE loop.operator=DO_WHILE_LOOP depth=0 + condition: CONST Boolean type=kotlin.Boolean value='true' + IrFunction public fun test2(): kotlin.Unit + IrExpressionBody + BLOCK type= hasResult=false operator=null + WHILE operator=WHILE_LOOP + condition: CONST Boolean type=kotlin.Boolean value='true' + body: BLOCK type= hasResult=false operator=null + WHILE operator=WHILE_LOOP + condition: CONST Boolean type=kotlin.Boolean value='true' + body: BLOCK type= hasResult=false operator=null + BREAK loop.operator=WHILE_LOOP depth=0 + BREAK loop.operator=WHILE_LOOP depth=1 + BREAK loop.operator=WHILE_LOOP depth=0 + WHILE operator=WHILE_LOOP + condition: CONST Boolean type=kotlin.Boolean value='true' + body: BLOCK type= hasResult=false operator=null + WHILE operator=WHILE_LOOP + condition: CONST Boolean type=kotlin.Boolean value='true' + body: BLOCK type= hasResult=false operator=null + CONTINUE loop.operator=WHILE_LOOP depth=0 + CONTINUE loop.operator=WHILE_LOOP depth=1 + CONTINUE loop.operator=WHILE_LOOP depth=0 + IrFunction public fun test3(): kotlin.Unit + IrExpressionBody + BLOCK type= hasResult=false operator=null + WHILE operator=WHILE_LOOP + condition: CONST Boolean type=kotlin.Boolean value='true' + body: BLOCK type= hasResult=false operator=null + WHILE operator=WHILE_LOOP + condition: CONST Boolean type=kotlin.Boolean value='true' + body: BLOCK type= hasResult=false operator=null + BREAK loop.operator=WHILE_LOOP depth=0 + BREAK loop.operator=WHILE_LOOP depth=0 + WHILE operator=WHILE_LOOP + condition: CONST Boolean type=kotlin.Boolean value='true' + body: BLOCK type= hasResult=false operator=null + WHILE operator=WHILE_LOOP + condition: CONST Boolean type=kotlin.Boolean value='true' + body: BLOCK type= hasResult=false operator=null + CONTINUE loop.operator=WHILE_LOOP depth=0 + CONTINUE loop.operator=WHILE_LOOP depth=0 diff --git a/compiler/tests/org/jetbrains/kotlin/ir/IrTextTestCaseGenerated.java b/compiler/tests/org/jetbrains/kotlin/ir/IrTextTestCaseGenerated.java index 4dc9b3425e0..68959e1792f 100644 --- a/compiler/tests/org/jetbrains/kotlin/ir/IrTextTestCaseGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/ir/IrTextTestCaseGenerated.java @@ -83,6 +83,12 @@ public class IrTextTestCaseGenerated extends AbstractIrTextTestCase { doTest(fileName); } + @TestMetadata("breakContinue.kt") + public void testBreakContinue() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/ir/irText/breakContinue.kt"); + doTest(fileName); + } + @TestMetadata("callWithReorderedArguments.kt") public void testCallWithReorderedArguments() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/ir/irText/callWithReorderedArguments.kt");