Skip to content

Commit e31648a

Browse files
committed
KT-6476 Kotlin J2K converter fails with for-loops with continues
#KT-6476 Fixed
1 parent 2d4055e commit e31648a

14 files changed

+280
-58
lines changed

j2k/src/org/jetbrains/kotlin/j2k/CodeConverter.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,9 @@ class CodeConverter(
6363
public fun withSpecialExpressionConverter(specialConverter: SpecialExpressionConverter): CodeConverter
6464
= CodeConverter(converter, expressionConverter.withSpecialConverter(specialConverter), statementConverter, methodReturnType)
6565

66+
public fun withSpecialStatementConverter(specialConverter: SpecialStatementConverter): CodeConverter
67+
= CodeConverter(converter, expressionConverter, statementConverter.withSpecialConverter(specialConverter), methodReturnType)
68+
6669
public fun withMethodReturnType(methodReturnType: PsiType?): CodeConverter
6770
= CodeConverter(converter, expressionConverter, statementConverter, methodReturnType)
6871

j2k/src/org/jetbrains/kotlin/j2k/ForConverter.kt

Lines changed: 66 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -16,44 +16,9 @@
1616

1717
package org.jetbrains.kotlin.j2k
1818

19-
import com.intellij.psi.PsiForStatement
20-
import org.jetbrains.kotlin.j2k.ast.ForeachStatement
21-
import com.intellij.psi.PsiDeclarationStatement
22-
import com.intellij.psi.PsiLocalVariable
23-
import com.intellij.psi.PsiBinaryExpression
24-
import com.intellij.psi.JavaTokenType
25-
import com.intellij.psi.PsiReferenceExpression
26-
import com.intellij.psi.PsiExpressionStatement
27-
import org.jetbrains.kotlin.j2k.ast.PrimitiveType
28-
import org.jetbrains.kotlin.j2k.ast.Identifier
29-
import org.jetbrains.kotlin.j2k.ast.assignNoPrototype
30-
import org.jetbrains.kotlin.j2k.ast.declarationIdentifier
31-
import com.intellij.psi.PsiElement
32-
import com.intellij.psi.PsiVariable
33-
import com.intellij.psi.PsiPostfixExpression
34-
import com.intellij.psi.PsiPrefixExpression
35-
import com.intellij.psi.PsiExpression
19+
import com.intellij.psi.*
3620
import com.intellij.psi.tree.IElementType
37-
import org.jetbrains.kotlin.j2k.ast.Expression
38-
import com.intellij.psi.PsiLiteralExpression
39-
import com.intellij.psi.PsiMethodCallExpression
40-
import com.intellij.psi.PsiElementFactory
41-
import com.intellij.psi.CommonClassNames
42-
import org.jetbrains.kotlin.j2k.ast.QualifiedExpression
43-
import com.intellij.psi.PsiArrayType
44-
import org.jetbrains.kotlin.j2k.ast.BinaryExpression
45-
import org.jetbrains.kotlin.j2k.ast.LiteralExpression
46-
import org.jetbrains.kotlin.j2k.ast.RangeExpression
47-
import com.intellij.psi.PsiBlockStatement
48-
import com.intellij.psi.PsiNamedElement
49-
import org.jetbrains.kotlin.j2k.ast.Block
50-
import org.jetbrains.kotlin.j2k.ast.LBrace
51-
import org.jetbrains.kotlin.j2k.ast.RBrace
52-
import org.jetbrains.kotlin.j2k.ast.assignPrototypesFrom
53-
import org.jetbrains.kotlin.j2k.ast.WhileStatement
54-
import org.jetbrains.kotlin.j2k.ast.MethodCallExpression
55-
import org.jetbrains.kotlin.j2k.ast.LambdaExpression
56-
import org.jetbrains.kotlin.j2k.ast.Statement
21+
import org.jetbrains.kotlin.j2k.ast.*
5722

5823
class ForConverter(
5924
private val statement: PsiForStatement,
@@ -83,38 +48,77 @@ class ForConverter(
8348
val whileBody = if (updateConverted.isEmpty) {
8449
codeConverter.convertStatementOrBlock(body)
8550
}
86-
else if (body is PsiBlockStatement) {
87-
val nameConflict = initialization is PsiDeclarationStatement && initialization.getDeclaredElements().any { loopVar ->
88-
loopVar is PsiNamedElement && body.getCodeBlock().getStatements().any { statement ->
89-
statement is PsiDeclarationStatement && statement.getDeclaredElements().any {
90-
it is PsiNamedElement && it.getName() == loopVar.getName()
51+
else {
52+
// we should process all continue-statements because we need to add update statement(s) before them
53+
val codeConverterToUse = codeConverter.withSpecialStatementConverter(object : SpecialStatementConverter {
54+
override fun convertStatement(statement: PsiStatement, codeConverter: CodeConverter): Statement? {
55+
if (statement !is PsiContinueStatement) return null
56+
if (statement.findContinuedStatement()?.toContinuedLoop() != this@ForConverter.statement) return null
57+
58+
val continueConverted = this@ForConverter.codeConverter.convertStatement(statement)
59+
val statements = listOf(updateConverted, continueConverted)
60+
if (statement.getParent() is PsiCodeBlock) {
61+
// generate fictive statement which will generate multiple statements
62+
return object : Statement() {
63+
override fun generateCode(builder: CodeBuilder) {
64+
builder.append(statements, "\n")
65+
}
66+
}
67+
}
68+
else {
69+
return Block(statements, LBrace().assignNoPrototype(), RBrace().assignNoPrototype())
9170
}
9271
}
93-
}
72+
})
9473

95-
if (nameConflict) {
96-
val statements = listOf(codeConverter.convertStatement(body), updateConverted)
97-
Block(statements, LBrace().assignNoPrototype(), RBrace().assignNoPrototype(), true).assignNoPrototype()
74+
if (body is PsiBlockStatement) {
75+
val nameConflict = initialization is PsiDeclarationStatement && initialization.getDeclaredElements().any { loopVar ->
76+
loopVar is PsiNamedElement && body.getCodeBlock().getStatements().any { statement ->
77+
statement is PsiDeclarationStatement && statement.getDeclaredElements().any {
78+
it is PsiNamedElement && it.getName() == loopVar.getName()
79+
}
80+
}
81+
}
82+
83+
if (nameConflict) {
84+
val statements = listOf(codeConverterToUse.convertStatement(body), updateConverted)
85+
Block(statements, LBrace().assignNoPrototype(), RBrace().assignNoPrototype(), true).assignNoPrototype()
86+
}
87+
else {
88+
val block = codeConverterToUse.convertBlock(body.getCodeBlock(), true)
89+
Block(block.statements + listOf(updateConverted), block.lBrace, block.rBrace, true).assignPrototypesFrom(block)
90+
}
9891
}
9992
else {
100-
val block = codeConverter.convertBlock(body.getCodeBlock(), true)
101-
Block(block.statements + listOf(updateConverted), block.lBrace, block.rBrace, true).assignPrototypesFrom(block)
93+
val statements = listOf(codeConverterToUse.convertStatement(body), updateConverted)
94+
Block(statements, LBrace().assignNoPrototype(), RBrace().assignNoPrototype(), true).assignNoPrototype()
10295
}
10396
}
104-
else {
105-
val statements = listOf(codeConverter.convertStatement(body), updateConverted)
106-
Block(statements, LBrace().assignNoPrototype(), RBrace().assignNoPrototype(), true).assignNoPrototype()
107-
}
10897

10998
val whileStatement = WhileStatement(
11099
if (condition != null) codeConverter.convertExpression(condition) else LiteralExpression("true").assignNoPrototype(),
111100
whileBody,
112101
statement.isInSingleLine()).assignNoPrototype()
113102
if (initializationConverted.isEmpty) return whileStatement
114103

115-
val statements = listOf(initializationConverted, whileStatement)
116-
val block = Block(statements, LBrace().assignNoPrototype(), RBrace().assignNoPrototype()).assignNoPrototype()
117-
return MethodCallExpression.build(null, "run", listOf(), listOf(), false, LambdaExpression(null, block))
104+
//TODO: we could omit "run { ... }" when it won't cause any name conflicts
105+
return RunBlockWithLoopStatement(initializationConverted, whileStatement)
106+
}
107+
108+
public class RunBlockWithLoopStatement(
109+
public val initialization: Statement,
110+
public val loop: Statement
111+
) : Statement() {
112+
113+
private val methodCall = run {
114+
val statements = listOf(initialization, loop)
115+
val block = Block(statements, LBrace().assignNoPrototype(), RBrace().assignNoPrototype()).assignNoPrototype()
116+
MethodCallExpression.build(null, "run", listOf(), listOf(), false, LambdaExpression(null, block))
117+
}
118+
119+
override fun generateCode(builder: CodeBuilder) {
120+
methodCall.generateCode(builder)
121+
}
118122
}
119123

120124
private fun convertToForeach(): ForeachStatement? {
@@ -194,4 +198,11 @@ class ForConverter(
194198
return RangeExpression(codeConverter.convertExpression(start), endExpression)
195199
}
196200

201+
private fun PsiStatement.toContinuedLoop(): PsiLoopStatement? {
202+
return when (this) {
203+
is PsiLoopStatement -> this
204+
is PsiLabeledStatement -> this.getStatement()?.toContinuedLoop()
205+
else -> null
206+
}
207+
}
197208
}

j2k/src/org/jetbrains/kotlin/j2k/StatementConverter.kt

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,17 @@ trait StatementConverter {
2424
fun convertStatement(statement: PsiStatement, codeConverter: CodeConverter): Statement
2525
}
2626

27+
trait SpecialStatementConverter {
28+
fun convertStatement(statement: PsiStatement, codeConverter: CodeConverter): Statement?
29+
}
30+
31+
fun StatementConverter.withSpecialConverter(specialConverter: SpecialStatementConverter): StatementConverter {
32+
return object: StatementConverter {
33+
override fun convertStatement(statement: PsiStatement, codeConverter: CodeConverter): Statement
34+
= specialConverter.convertStatement(statement, codeConverter) ?: this@withSpecialConverter.convertStatement(statement, codeConverter)
35+
}
36+
}
37+
2738
class DefaultStatementConverter : JavaElementVisitor(), StatementConverter {
2839
private var _codeConverter: CodeConverter? = null
2940
private var result: Statement = Statement.Empty
@@ -133,8 +144,16 @@ class DefaultStatementConverter : JavaElementVisitor(), StatementConverter {
133144
}
134145

135146
override fun visitLabeledStatement(statement: PsiLabeledStatement) {
136-
result = LabelStatement(converter.convertIdentifier(statement.getLabelIdentifier()),
137-
codeConverter.convertStatement(statement.getStatement()))
147+
//TODO: multiple labels
148+
val statementConverted = codeConverter.convertStatement(statement.getStatement())
149+
val identifier = converter.convertIdentifier(statement.getLabelIdentifier())
150+
if (statementConverted is ForConverter.RunBlockWithLoopStatement) { // special case - if our loop gets converted to run { ... } we should move label inside
151+
val labeledLoop = LabeledStatement(identifier, statementConverted.loop).assignPrototype(statement)
152+
result = ForConverter.RunBlockWithLoopStatement(statementConverted.initialization, labeledLoop)
153+
}
154+
else {
155+
result = LabeledStatement(identifier, statementConverted)
156+
}
138157
}
139158

140159
override fun visitSwitchLabelStatement(statement: PsiSwitchLabelStatement) {

j2k/src/org/jetbrains/kotlin/j2k/ast/Statements.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ class ExpressionListStatement(val expressions: List<Expression>) : Expression()
3737
}
3838
}
3939

40-
class LabelStatement(val name: Identifier, val statement: Element) : Statement() {
40+
class LabeledStatement(val name: Identifier, val statement: Element) : Statement() {
4141
override fun generateCode(builder: CodeBuilder) {
4242
builder append "@" append name append " " append statement
4343
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
public class TestClass {
2+
public static void main(String[] args) {
3+
for (int i = 0; i < 10; ++i) {
4+
if (i == 4 || i == 8) {
5+
i++;
6+
continue;
7+
}
8+
System.err.println(i);
9+
}
10+
}
11+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
public class TestClass {
2+
companion object {
3+
public fun main(args: Array<String>) {
4+
run {
5+
var i = 0
6+
while (i < 10) {
7+
if (i == 4 || i == 8) {
8+
i++
9+
++i
10+
continue
11+
}
12+
System.err.println(i)
13+
++i
14+
}
15+
}
16+
}
17+
}
18+
}
19+
20+
fun main(args: Array<String>) = TestClass.main(args)
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
public class TestClass {
2+
public static void main(String[] args) {
3+
for (int i = 0, j = 1; i < 10; ++i, j *= 2) {
4+
if (i == 4 || i == 8) {
5+
i++;
6+
continue;
7+
}
8+
System.err.println(j);
9+
}
10+
}
11+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
public class TestClass {
2+
companion object {
3+
public fun main(args: Array<String>) {
4+
run {
5+
var i = 0
6+
var j = 1
7+
while (i < 10) {
8+
if (i == 4 || i == 8) {
9+
i++
10+
++i
11+
j *= 2
12+
continue
13+
}
14+
System.err.println(j)
15+
++i
16+
j *= 2
17+
}
18+
}
19+
}
20+
}
21+
}
22+
23+
fun main(args: Array<String>) = TestClass.main(args)
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
public class TestClass {
2+
public static void main(String[] args) {
3+
for (int i = 1; i < 1000; i *= 2) {
4+
if (i == 4 || i == 8) continue;
5+
System.err.println(i);
6+
}
7+
}
8+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
public class TestClass {
2+
companion object {
3+
public fun main(args: Array<String>) {
4+
run {
5+
var i = 1
6+
while (i < 1000) {
7+
if (i == 4 || i == 8) {
8+
i *= 2
9+
continue
10+
}
11+
System.err.println(i)
12+
i *= 2
13+
}
14+
}
15+
}
16+
}
17+
}
18+
19+
fun main(args: Array<String>) = TestClass.main(args)
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
public class TestClass {
2+
public static void main(String[] args) {
3+
OuterLoop1:
4+
OuterLoop2:
5+
for (int i = 1; i < 1000; i *= 2) {
6+
InnerLoop:
7+
for (int j = 1; j < 100; j *= 3) {
8+
if (j == 3) continue InnerLoop;
9+
if (i == j) continue OuterLoop1;
10+
System.err.println(j);
11+
if (j == 9) continue;
12+
}
13+
}
14+
}
15+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// ERROR: The label '@OuterLoop1' does not denote a loop
2+
public class TestClass {
3+
companion object {
4+
public fun main(args: Array<String>) {
5+
run {
6+
var i = 1
7+
@OuterLoop1 @OuterLoop2 while (i < 1000) {
8+
run {
9+
var j = 1
10+
@InnerLoop while (j < 100) {
11+
if (j == 3) {
12+
j *= 3
13+
continue@InnerLoop
14+
}
15+
if (i == j) {
16+
i *= 2
17+
continue@OuterLoop1
18+
}
19+
System.err.println(j)
20+
if (j == 9) {
21+
j *= 3
22+
continue
23+
}
24+
j *= 3
25+
}
26+
}
27+
i *= 2
28+
}
29+
}
30+
}
31+
}
32+
}
33+
34+
fun main(args: Array<String>) = TestClass.main(args)

j2k/tests/org/jetbrains/kotlin/j2k/JavaToKotlinConverterForWebDemoTestGenerated.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1935,6 +1935,30 @@ public void testInfiniteFor() throws Exception {
19351935
String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/for/infiniteFor.java");
19361936
doTest(fileName);
19371937
}
1938+
1939+
@TestMetadata("withContinue1.java")
1940+
public void testWithContinue1() throws Exception {
1941+
String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/for/withContinue1.java");
1942+
doTest(fileName);
1943+
}
1944+
1945+
@TestMetadata("withContinue2.java")
1946+
public void testWithContinue2() throws Exception {
1947+
String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/for/withContinue2.java");
1948+
doTest(fileName);
1949+
}
1950+
1951+
@TestMetadata("withContinue3.java")
1952+
public void testWithContinue3() throws Exception {
1953+
String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/for/withContinue3.java");
1954+
doTest(fileName);
1955+
}
1956+
1957+
@TestMetadata("withContinueAndLabels.java")
1958+
public void testWithContinueAndLabels() throws Exception {
1959+
String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/for/withContinueAndLabels.java");
1960+
doTest(fileName);
1961+
}
19381962
}
19391963

19401964
@TestMetadata("j2k/testData/fileOrElement/foreachStatement")

0 commit comments

Comments
 (0)