KT-6476 Kotlin J2K converter fails with for-loops with continues

#KT-6476 Fixed
This commit is contained in:
Valentin Kipyatkov
2015-03-25 22:20:29 +03:00
parent 2d4055e921
commit e31648a4b6
14 changed files with 280 additions and 58 deletions
@@ -63,6 +63,9 @@ class CodeConverter(
public fun withSpecialExpressionConverter(specialConverter: SpecialExpressionConverter): CodeConverter
= CodeConverter(converter, expressionConverter.withSpecialConverter(specialConverter), statementConverter, methodReturnType)
public fun withSpecialStatementConverter(specialConverter: SpecialStatementConverter): CodeConverter
= CodeConverter(converter, expressionConverter, statementConverter.withSpecialConverter(specialConverter), methodReturnType)
public fun withMethodReturnType(methodReturnType: PsiType?): CodeConverter
= CodeConverter(converter, expressionConverter, statementConverter, methodReturnType)
@@ -16,44 +16,9 @@
package org.jetbrains.kotlin.j2k
import com.intellij.psi.PsiForStatement
import org.jetbrains.kotlin.j2k.ast.ForeachStatement
import com.intellij.psi.PsiDeclarationStatement
import com.intellij.psi.PsiLocalVariable
import com.intellij.psi.PsiBinaryExpression
import com.intellij.psi.JavaTokenType
import com.intellij.psi.PsiReferenceExpression
import com.intellij.psi.PsiExpressionStatement
import org.jetbrains.kotlin.j2k.ast.PrimitiveType
import org.jetbrains.kotlin.j2k.ast.Identifier
import org.jetbrains.kotlin.j2k.ast.assignNoPrototype
import org.jetbrains.kotlin.j2k.ast.declarationIdentifier
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiVariable
import com.intellij.psi.PsiPostfixExpression
import com.intellij.psi.PsiPrefixExpression
import com.intellij.psi.PsiExpression
import com.intellij.psi.*
import com.intellij.psi.tree.IElementType
import org.jetbrains.kotlin.j2k.ast.Expression
import com.intellij.psi.PsiLiteralExpression
import com.intellij.psi.PsiMethodCallExpression
import com.intellij.psi.PsiElementFactory
import com.intellij.psi.CommonClassNames
import org.jetbrains.kotlin.j2k.ast.QualifiedExpression
import com.intellij.psi.PsiArrayType
import org.jetbrains.kotlin.j2k.ast.BinaryExpression
import org.jetbrains.kotlin.j2k.ast.LiteralExpression
import org.jetbrains.kotlin.j2k.ast.RangeExpression
import com.intellij.psi.PsiBlockStatement
import com.intellij.psi.PsiNamedElement
import org.jetbrains.kotlin.j2k.ast.Block
import org.jetbrains.kotlin.j2k.ast.LBrace
import org.jetbrains.kotlin.j2k.ast.RBrace
import org.jetbrains.kotlin.j2k.ast.assignPrototypesFrom
import org.jetbrains.kotlin.j2k.ast.WhileStatement
import org.jetbrains.kotlin.j2k.ast.MethodCallExpression
import org.jetbrains.kotlin.j2k.ast.LambdaExpression
import org.jetbrains.kotlin.j2k.ast.Statement
import org.jetbrains.kotlin.j2k.ast.*
class ForConverter(
private val statement: PsiForStatement,
@@ -83,28 +48,52 @@ class ForConverter(
val whileBody = if (updateConverted.isEmpty) {
codeConverter.convertStatementOrBlock(body)
}
else if (body is PsiBlockStatement) {
val nameConflict = initialization is PsiDeclarationStatement && initialization.getDeclaredElements().any { loopVar ->
loopVar is PsiNamedElement && body.getCodeBlock().getStatements().any { statement ->
statement is PsiDeclarationStatement && statement.getDeclaredElements().any {
it is PsiNamedElement && it.getName() == loopVar.getName()
else {
// we should process all continue-statements because we need to add update statement(s) before them
val codeConverterToUse = codeConverter.withSpecialStatementConverter(object : SpecialStatementConverter {
override fun convertStatement(statement: PsiStatement, codeConverter: CodeConverter): Statement? {
if (statement !is PsiContinueStatement) return null
if (statement.findContinuedStatement()?.toContinuedLoop() != this@ForConverter.statement) return null
val continueConverted = this@ForConverter.codeConverter.convertStatement(statement)
val statements = listOf(updateConverted, continueConverted)
if (statement.getParent() is PsiCodeBlock) {
// generate fictive statement which will generate multiple statements
return object : Statement() {
override fun generateCode(builder: CodeBuilder) {
builder.append(statements, "\n")
}
}
}
else {
return Block(statements, LBrace().assignNoPrototype(), RBrace().assignNoPrototype())
}
}
}
})
if (nameConflict) {
val statements = listOf(codeConverter.convertStatement(body), updateConverted)
Block(statements, LBrace().assignNoPrototype(), RBrace().assignNoPrototype(), true).assignNoPrototype()
if (body is PsiBlockStatement) {
val nameConflict = initialization is PsiDeclarationStatement && initialization.getDeclaredElements().any { loopVar ->
loopVar is PsiNamedElement && body.getCodeBlock().getStatements().any { statement ->
statement is PsiDeclarationStatement && statement.getDeclaredElements().any {
it is PsiNamedElement && it.getName() == loopVar.getName()
}
}
}
if (nameConflict) {
val statements = listOf(codeConverterToUse.convertStatement(body), updateConverted)
Block(statements, LBrace().assignNoPrototype(), RBrace().assignNoPrototype(), true).assignNoPrototype()
}
else {
val block = codeConverterToUse.convertBlock(body.getCodeBlock(), true)
Block(block.statements + listOf(updateConverted), block.lBrace, block.rBrace, true).assignPrototypesFrom(block)
}
}
else {
val block = codeConverter.convertBlock(body.getCodeBlock(), true)
Block(block.statements + listOf(updateConverted), block.lBrace, block.rBrace, true).assignPrototypesFrom(block)
val statements = listOf(codeConverterToUse.convertStatement(body), updateConverted)
Block(statements, LBrace().assignNoPrototype(), RBrace().assignNoPrototype(), true).assignNoPrototype()
}
}
else {
val statements = listOf(codeConverter.convertStatement(body), updateConverted)
Block(statements, LBrace().assignNoPrototype(), RBrace().assignNoPrototype(), true).assignNoPrototype()
}
val whileStatement = WhileStatement(
if (condition != null) codeConverter.convertExpression(condition) else LiteralExpression("true").assignNoPrototype(),
@@ -112,9 +101,24 @@ class ForConverter(
statement.isInSingleLine()).assignNoPrototype()
if (initializationConverted.isEmpty) return whileStatement
val statements = listOf(initializationConverted, whileStatement)
val block = Block(statements, LBrace().assignNoPrototype(), RBrace().assignNoPrototype()).assignNoPrototype()
return MethodCallExpression.build(null, "run", listOf(), listOf(), false, LambdaExpression(null, block))
//TODO: we could omit "run { ... }" when it won't cause any name conflicts
return RunBlockWithLoopStatement(initializationConverted, whileStatement)
}
public class RunBlockWithLoopStatement(
public val initialization: Statement,
public val loop: Statement
) : Statement() {
private val methodCall = run {
val statements = listOf(initialization, loop)
val block = Block(statements, LBrace().assignNoPrototype(), RBrace().assignNoPrototype()).assignNoPrototype()
MethodCallExpression.build(null, "run", listOf(), listOf(), false, LambdaExpression(null, block))
}
override fun generateCode(builder: CodeBuilder) {
methodCall.generateCode(builder)
}
}
private fun convertToForeach(): ForeachStatement? {
@@ -194,4 +198,11 @@ class ForConverter(
return RangeExpression(codeConverter.convertExpression(start), endExpression)
}
private fun PsiStatement.toContinuedLoop(): PsiLoopStatement? {
return when (this) {
is PsiLoopStatement -> this
is PsiLabeledStatement -> this.getStatement()?.toContinuedLoop()
else -> null
}
}
}
@@ -24,6 +24,17 @@ trait StatementConverter {
fun convertStatement(statement: PsiStatement, codeConverter: CodeConverter): Statement
}
trait SpecialStatementConverter {
fun convertStatement(statement: PsiStatement, codeConverter: CodeConverter): Statement?
}
fun StatementConverter.withSpecialConverter(specialConverter: SpecialStatementConverter): StatementConverter {
return object: StatementConverter {
override fun convertStatement(statement: PsiStatement, codeConverter: CodeConverter): Statement
= specialConverter.convertStatement(statement, codeConverter) ?: this@withSpecialConverter.convertStatement(statement, codeConverter)
}
}
class DefaultStatementConverter : JavaElementVisitor(), StatementConverter {
private var _codeConverter: CodeConverter? = null
private var result: Statement = Statement.Empty
@@ -133,8 +144,16 @@ class DefaultStatementConverter : JavaElementVisitor(), StatementConverter {
}
override fun visitLabeledStatement(statement: PsiLabeledStatement) {
result = LabelStatement(converter.convertIdentifier(statement.getLabelIdentifier()),
codeConverter.convertStatement(statement.getStatement()))
//TODO: multiple labels
val statementConverted = codeConverter.convertStatement(statement.getStatement())
val identifier = converter.convertIdentifier(statement.getLabelIdentifier())
if (statementConverted is ForConverter.RunBlockWithLoopStatement) { // special case - if our loop gets converted to run { ... } we should move label inside
val labeledLoop = LabeledStatement(identifier, statementConverted.loop).assignPrototype(statement)
result = ForConverter.RunBlockWithLoopStatement(statementConverted.initialization, labeledLoop)
}
else {
result = LabeledStatement(identifier, statementConverted)
}
}
override fun visitSwitchLabelStatement(statement: PsiSwitchLabelStatement) {
@@ -37,7 +37,7 @@ class ExpressionListStatement(val expressions: List<Expression>) : Expression()
}
}
class LabelStatement(val name: Identifier, val statement: Element) : Statement() {
class LabeledStatement(val name: Identifier, val statement: Element) : Statement() {
override fun generateCode(builder: CodeBuilder) {
builder append "@" append name append " " append statement
}
@@ -0,0 +1,11 @@
public class TestClass {
public static void main(String[] args) {
for (int i = 0; i < 10; ++i) {
if (i == 4 || i == 8) {
i++;
continue;
}
System.err.println(i);
}
}
}
@@ -0,0 +1,20 @@
public class TestClass {
companion object {
public fun main(args: Array<String>) {
run {
var i = 0
while (i < 10) {
if (i == 4 || i == 8) {
i++
++i
continue
}
System.err.println(i)
++i
}
}
}
}
}
fun main(args: Array<String>) = TestClass.main(args)
@@ -0,0 +1,11 @@
public class TestClass {
public static void main(String[] args) {
for (int i = 0, j = 1; i < 10; ++i, j *= 2) {
if (i == 4 || i == 8) {
i++;
continue;
}
System.err.println(j);
}
}
}
@@ -0,0 +1,23 @@
public class TestClass {
companion object {
public fun main(args: Array<String>) {
run {
var i = 0
var j = 1
while (i < 10) {
if (i == 4 || i == 8) {
i++
++i
j *= 2
continue
}
System.err.println(j)
++i
j *= 2
}
}
}
}
}
fun main(args: Array<String>) = TestClass.main(args)
@@ -0,0 +1,8 @@
public class TestClass {
public static void main(String[] args) {
for (int i = 1; i < 1000; i *= 2) {
if (i == 4 || i == 8) continue;
System.err.println(i);
}
}
}
@@ -0,0 +1,19 @@
public class TestClass {
companion object {
public fun main(args: Array<String>) {
run {
var i = 1
while (i < 1000) {
if (i == 4 || i == 8) {
i *= 2
continue
}
System.err.println(i)
i *= 2
}
}
}
}
}
fun main(args: Array<String>) = TestClass.main(args)
@@ -0,0 +1,15 @@
public class TestClass {
public static void main(String[] args) {
OuterLoop1:
OuterLoop2:
for (int i = 1; i < 1000; i *= 2) {
InnerLoop:
for (int j = 1; j < 100; j *= 3) {
if (j == 3) continue InnerLoop;
if (i == j) continue OuterLoop1;
System.err.println(j);
if (j == 9) continue;
}
}
}
}
@@ -0,0 +1,34 @@
// ERROR: The label '@OuterLoop1' does not denote a loop
public class TestClass {
companion object {
public fun main(args: Array<String>) {
run {
var i = 1
@OuterLoop1 @OuterLoop2 while (i < 1000) {
run {
var j = 1
@InnerLoop while (j < 100) {
if (j == 3) {
j *= 3
continue@InnerLoop
}
if (i == j) {
i *= 2
continue@OuterLoop1
}
System.err.println(j)
if (j == 9) {
j *= 3
continue
}
j *= 3
}
}
i *= 2
}
}
}
}
}
fun main(args: Array<String>) = TestClass.main(args)
@@ -1935,6 +1935,30 @@ public class JavaToKotlinConverterForWebDemoTestGenerated extends AbstractJavaTo
String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/for/infiniteFor.java");
doTest(fileName);
}
@TestMetadata("withContinue1.java")
public void testWithContinue1() throws Exception {
String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/for/withContinue1.java");
doTest(fileName);
}
@TestMetadata("withContinue2.java")
public void testWithContinue2() throws Exception {
String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/for/withContinue2.java");
doTest(fileName);
}
@TestMetadata("withContinue3.java")
public void testWithContinue3() throws Exception {
String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/for/withContinue3.java");
doTest(fileName);
}
@TestMetadata("withContinueAndLabels.java")
public void testWithContinueAndLabels() throws Exception {
String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/for/withContinueAndLabels.java");
doTest(fileName);
}
}
@TestMetadata("j2k/testData/fileOrElement/foreachStatement")
@@ -1935,6 +1935,30 @@ public class JavaToKotlinConverterSingleFileTestGenerated extends AbstractJavaTo
String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/for/infiniteFor.java");
doTest(fileName);
}
@TestMetadata("withContinue1.java")
public void testWithContinue1() throws Exception {
String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/for/withContinue1.java");
doTest(fileName);
}
@TestMetadata("withContinue2.java")
public void testWithContinue2() throws Exception {
String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/for/withContinue2.java");
doTest(fileName);
}
@TestMetadata("withContinue3.java")
public void testWithContinue3() throws Exception {
String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/for/withContinue3.java");
doTest(fileName);
}
@TestMetadata("withContinueAndLabels.java")
public void testWithContinueAndLabels() throws Exception {
String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/for/withContinueAndLabels.java");
doTest(fileName);
}
}
@TestMetadata("j2k/testData/fileOrElement/foreachStatement")