KT-5405 J2K: convert for's through indices of some list or array into use of ".indices" + fixed a bug in for-statement conversion

#KT-5405 Fixed
This commit is contained in:
Valentin Kipyatkov
2014-07-09 15:38:49 +04:00
parent 5c688e9916
commit d3a1fa6b9a
16 changed files with 242 additions and 74 deletions
@@ -249,6 +249,10 @@
<item name='com.intellij.psi.PsiElement com.intellij.psi.PsiElement replace(com.intellij.psi.PsiElement)'> <item name='com.intellij.psi.PsiElement com.intellij.psi.PsiElement replace(com.intellij.psi.PsiElement)'>
<annotation name='org.jetbrains.annotations.NotNull'/> <annotation name='org.jetbrains.annotations.NotNull'/>
</item> </item>
<item
name='com.intellij.psi.PsiElementFactory.SERVICE com.intellij.psi.PsiElementFactory getInstance(com.intellij.openapi.project.Project)'>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>
<item name='com.intellij.psi.PsiElementVisitor void visitBinaryFile(com.intellij.psi.PsiBinaryFile) 0'> <item name='com.intellij.psi.PsiElementVisitor void visitBinaryFile(com.intellij.psi.PsiBinaryFile) 0'>
<annotation name='org.jetbrains.annotations.NotNull'/> <annotation name='org.jetbrains.annotations.NotNull'/>
</item> </item>
@@ -143,6 +143,12 @@ class StarExpression(val operand: Expression) : Expression() {
} }
} }
class RangeExpression(val start: Expression, val end: Expression): Expression() {
override fun generateCode(builder: CodeBuilder) {
builder.appendOperand(this, start).append("..").appendOperand(this, end)
}
}
fun createArrayInitializerExpression(arrayType: ArrayType, initializers: List<Expression>, needExplicitType: Boolean) : MethodCallExpression { fun createArrayInitializerExpression(arrayType: ArrayType, initializers: List<Expression>, needExplicitType: Boolean) : MethodCallExpression {
val elementType = arrayType.elementType val elementType = arrayType.elementType
val createArrayFunction = if (elementType is PrimitiveType) val createArrayFunction = if (elementType is PrimitiveType)
@@ -88,9 +88,10 @@ class DoWhileStatement(val condition: Expression, val body: Element, singleLine:
} }
} }
//TODO: explicit type (if option)
class ForeachStatement( class ForeachStatement(
val variable: Parameter, val variableName: Identifier,
val expression: Expression, val collection: Expression,
val body: Element, val body: Element,
singleLine: Boolean singleLine: Boolean
) : Statement() { ) : Statement() {
@@ -98,7 +99,7 @@ class ForeachStatement(
private val br = if (singleLine) " " else "\n" private val br = if (singleLine) " " else "\n"
override fun generateCode(builder: CodeBuilder) { override fun generateCode(builder: CodeBuilder) {
builder append "for (" append variable.identifier append " in " append expression append ")" append br append body builder append "for (" append variableName append " in " append collection append ")" append br append body
} }
} }
+11 -9
View File
@@ -46,19 +46,21 @@ private fun Expression.precedence(): Int? {
is BinaryExpression -> when(op) { is BinaryExpression -> when(op) {
"*", "/", "%" -> 3 "*", "/", "%" -> 3
"+", "-" -> 4 "+", "-" -> 4
"?:" -> 6 "?:" -> 7
">", "<", ">=", "<=" -> 8 ">", "<", ">=", "<=" -> 9
"==", "!=", "===", "!===" -> 9 "==", "!=", "===", "!===" -> 10
"&&" -> 10 "&&" -> 11
"||" -> 11 "||" -> 12
else -> 5 /* simple name */ else -> 6 /* simple name */
} }
is IsOperator -> 7 is RangeExpression -> 5
is IfStatement -> 12 is IsOperator -> 8
is AssignmentExpression -> 13 is IfStatement -> 13
is AssignmentExpression -> 14
else -> null else -> null
} }
@@ -23,7 +23,7 @@ import org.jetbrains.jet.j2k.countWriteAccesses
import java.util.ArrayList import java.util.ArrayList
import org.jetbrains.jet.j2k.hasWriteAccesses import org.jetbrains.jet.j2k.hasWriteAccesses
import org.jetbrains.jet.j2k.isInSingleLine import org.jetbrains.jet.j2k.isInSingleLine
import org.jetbrains.jet.j2k.getContainingMethod import com.intellij.psi.tree.IElementType
open class StatementVisitor(public val converter: Converter) : JavaElementVisitor() { open class StatementVisitor(public val converter: Converter) : JavaElementVisitor() {
public var result: Statement = Statement.Empty public var result: Statement = Statement.Empty
@@ -102,83 +102,117 @@ open class StatementVisitor(public val converter: Converter) : JavaElementVisito
val condition = statement.getCondition() val condition = statement.getCondition()
val body = statement.getBody() val body = statement.getBody()
val loopVar = initialization?.getFirstChild() as? PsiLocalVariable if (initialization is PsiDeclarationStatement && initialization.getFirstChild()!!.getNextSibling() == null) {
val onceWritableIterator = loopVar != null val loopVar = initialization.getFirstChild() as? PsiLocalVariable
&& !loopVar.hasWriteAccesses(body) if (loopVar != null
&& !loopVar.hasWriteAccesses(condition) && !loopVar.hasWriteAccesses(body)
&& loopVar.countWriteAccesses(update) == 1 && !loopVar.hasWriteAccesses(condition)
&& loopVar.countWriteAccesses(update) == 1
val operationTokenType = (condition as? PsiBinaryExpression)?.getOperationTokenType() && condition is PsiBinaryExpression) {
if (initialization is PsiDeclarationStatement val operationTokenType = condition.getOperationTokenType()
&& initialization.getFirstChild() == initialization.getLastChild() val lowerBound = condition.getLOperand()
&& condition != null val upperBound = condition.getROperand()
&& update != null if ((operationTokenType == JavaTokenType.LT || operationTokenType == JavaTokenType.LE) &&
&& update.getChildren().size == 1 lowerBound is PsiReferenceExpression &&
&& update.getChildren().single().isPlusPlusExpression() lowerBound.resolve() == loopVar &&
&& (operationTokenType == JavaTokenType.LT || operationTokenType == JavaTokenType.LE) upperBound != null) {
&& loopVar != null val start = loopVar.getInitializer()
&& loopVar.getNameIdentifier() != null if (start != null
&& onceWritableIterator) { && update != null
val end = converter.convertExpression((condition as PsiBinaryExpression).getROperand()) && update.getChildren().size == 1
val endExpression = if (operationTokenType == JavaTokenType.LT) && update.getChildren().single().isPlusPlusExpression()) {
BinaryExpression(end, LiteralExpression("1").assignNoPrototype(), "-").assignNoPrototype() val range = forIterationRange(start, upperBound, operationTokenType).assignNoPrototype()
else result = ForeachStatement(loopVar.declarationIdentifier(), range, convertStatementOrBlock(body), statement.isInSingleLine())
end return
result = ForeachWithRangeStatement(loopVar.declarationIdentifier(),
converter.convertExpression(loopVar.getInitializer()),
endExpression,
convertStatementOrBlock(body),
statement.isInSingleLine())
}
else {
val initializationConverted = converter.convertStatement(initialization)
val updateConverted = converter.convertStatement(update)
val whileBody = if (updateConverted.isEmpty) {
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()
}
} }
} }
}
}
if (nameConflict) { val initializationConverted = converter.convertStatement(initialization)
val statements = listOf(converter.convertStatement(body), updateConverted) val updateConverted = converter.convertStatement(update)
Block(statements, LBrace().assignNoPrototype(), RBrace().assignNoPrototype(), true).assignNoPrototype()
} val whileBody = if (updateConverted.isEmpty) {
else { convertStatementOrBlock(body)
val block = converter.convertBlock(body.getCodeBlock(), true) }
Block(block.statements + listOf(updateConverted), block.lBrace, block.rBrace, true).assignPrototypesFrom(block) 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 {
if (nameConflict) {
val statements = listOf(converter.convertStatement(body), updateConverted) val statements = listOf(converter.convertStatement(body), updateConverted)
Block(statements, LBrace().assignNoPrototype(), RBrace().assignNoPrototype(), true).assignNoPrototype() Block(statements, LBrace().assignNoPrototype(), RBrace().assignNoPrototype(), true).assignNoPrototype()
} }
val whileStatement = WhileStatement(
if (condition != null) converter.convertExpression(condition) else LiteralExpression("true").assignNoPrototype(),
whileBody,
statement.isInSingleLine()).assignNoPrototype()
if (initializationConverted.isEmpty) {
result = whileStatement
}
else { else {
val statements = listOf(initializationConverted, whileStatement) val block = converter.convertBlock(body.getCodeBlock(), true)
val block = Block(statements, LBrace().assignNoPrototype(), RBrace().assignNoPrototype()).assignNoPrototype() Block(block.statements + listOf(updateConverted), block.lBrace, block.rBrace, true).assignPrototypesFrom(block)
result = MethodCallExpression.build(null, "run", listOf(), listOf(), false, LambdaExpression(null, block))
} }
} }
else {
val statements = listOf(converter.convertStatement(body), updateConverted)
Block(statements, LBrace().assignNoPrototype(), RBrace().assignNoPrototype(), true).assignNoPrototype()
}
val whileStatement = WhileStatement(
if (condition != null) converter.convertExpression(condition) else LiteralExpression("true").assignNoPrototype(),
whileBody,
statement.isInSingleLine()).assignNoPrototype()
if (initializationConverted.isEmpty) {
result = whileStatement
}
else {
val statements = listOf(initializationConverted, whileStatement)
val block = Block(statements, LBrace().assignNoPrototype(), RBrace().assignNoPrototype()).assignNoPrototype()
result = MethodCallExpression.build(null, "run", listOf(), listOf(), false, LambdaExpression(null, block))
}
}
private fun forIterationRange(start: PsiExpression, upperBound: PsiExpression, comparisonTokenType: IElementType): Expression {
if (start is PsiLiteralExpression
&& start.getValue() == 0
&& comparisonTokenType == JavaTokenType.LT) {
// check if it's iteration through list indices
if (upperBound is PsiMethodCallExpression && upperBound.getArgumentList().getExpressions().isEmpty()) {
val methodExpr = upperBound.getMethodExpression()
if (methodExpr is PsiReferenceExpression && methodExpr.getReferenceName() == "size") {
val qualifier = methodExpr.getQualifierExpression()
if (qualifier is PsiReferenceExpression /* we don't convert to .indices if qualifier is method call or something because of possible side effects */) {
val listType = PsiElementFactory.SERVICE.getInstance(converter.project).createTypeByFQClassName(CommonClassNames.JAVA_UTIL_LIST)
val qualifierType = qualifier.getType()
if (qualifierType != null && listType.isAssignableFrom(qualifierType)) {
return QualifiedExpression(converter.convertExpression(qualifier), Identifier("indices", false).assignNoPrototype())
}
}
}
}
// check if it's iteration through array indices
if (upperBound is PsiReferenceExpression /* we don't convert to .indices if qualifier is method call or something because of possible side effects */
&& upperBound.getReferenceName() == "length") {
val qualifier = upperBound.getQualifierExpression()
if (qualifier is PsiReferenceExpression && qualifier.getType() is PsiArrayType) {
return QualifiedExpression(converter.convertExpression(qualifier), Identifier("indices", false).assignNoPrototype())
}
}
}
val end = converter.convertExpression(upperBound)
val endExpression = if (comparisonTokenType == JavaTokenType.LT)
BinaryExpression(end, LiteralExpression("1").assignNoPrototype(), "-").assignNoPrototype()
else
end
return RangeExpression(converter.convertExpression(start), endExpression)
} }
override fun visitForeachStatement(statement: PsiForeachStatement) { override fun visitForeachStatement(statement: PsiForeachStatement) {
val iteratorExpr = converter.convertExpression(statement.getIteratedValue()) val iteratorExpr = converter.convertExpression(statement.getIteratedValue())
val iterator = if (iteratorExpr.isNullable) BangBangExpression(iteratorExpr).assignNoPrototype() else iteratorExpr val iterator = if (iteratorExpr.isNullable) BangBangExpression(iteratorExpr).assignNoPrototype() else iteratorExpr
result = ForeachStatement(converter.convertParameter(statement.getIterationParameter()), result = ForeachStatement(statement.getIterationParameter().declarationIdentifier(),
iterator, iterator,
convertStatementOrBlock(statement.getBody()), convertStatementOrBlock(statement.getBody()),
statement.isInSingleLine()) statement.isInSingleLine())
@@ -1222,6 +1222,11 @@ public class JavaToKotlinConverterTestGenerated extends AbstractJavaToKotlinConv
doTest("j2k/tests/testData/ast/for/commonCaseForTest.java"); doTest("j2k/tests/testData/ast/for/commonCaseForTest.java");
} }
@TestMetadata("falseForRange.java")
public void testFalseForRange() throws Exception {
doTest("j2k/tests/testData/ast/for/falseForRange.java");
}
@TestMetadata("forRangeWithBlock.java") @TestMetadata("forRangeWithBlock.java")
public void testForRangeWithBlock() throws Exception { public void testForRangeWithBlock() throws Exception {
doTest("j2k/tests/testData/ast/for/forRangeWithBlock.java"); doTest("j2k/tests/testData/ast/for/forRangeWithBlock.java");
@@ -1237,6 +1242,26 @@ public class JavaToKotlinConverterTestGenerated extends AbstractJavaToKotlinConv
doTest("j2k/tests/testData/ast/for/forRangeWithLT.java"); doTest("j2k/tests/testData/ast/for/forRangeWithLT.java");
} }
@TestMetadata("forThroughArrayIndices.java")
public void testForThroughArrayIndices() throws Exception {
doTest("j2k/tests/testData/ast/for/forThroughArrayIndices.java");
}
@TestMetadata("forThroughListIndices.java")
public void testForThroughListIndices() throws Exception {
doTest("j2k/tests/testData/ast/for/forThroughListIndices.java");
}
@TestMetadata("forThroughNonArrayIndices.java")
public void testForThroughNonArrayIndices() throws Exception {
doTest("j2k/tests/testData/ast/for/forThroughNonArrayIndices.java");
}
@TestMetadata("forThroughNonCollectionIndices.java")
public void testForThroughNonCollectionIndices() throws Exception {
doTest("j2k/tests/testData/ast/for/forThroughNonCollectionIndices.java");
}
@TestMetadata("forWithBlock.java") @TestMetadata("forWithBlock.java")
public void testForWithBlock() throws Exception { public void testForWithBlock() throws Exception {
doTest("j2k/tests/testData/ast/for/forWithBlock.java"); doTest("j2k/tests/testData/ast/for/forWithBlock.java");
@@ -0,0 +1,2 @@
//statement
for (int i = 0; i * 2 <= 10; i++) { foo(i); }
@@ -0,0 +1,7 @@
run {
var i = 0
while (i * 2 <= 10) {
foo(i)
i++
}
}
@@ -0,0 +1,3 @@
//statement
int[] array = new int[10];
for (int i = 0; i < array.length; i++) {array[i] = i;}
@@ -0,0 +1,4 @@
val array = IntArray(10)
for (i in array.indices) {
array[i] = i
}
@@ -0,0 +1,17 @@
//file
import java.util.List;
import java.util.ArrayList;
class C{
void foo1(List<String> list) {
for (int i = 0; i < list.size(); i++) {
list.set(i, "a");
}
}
void foo2(ArrayList<String> list) {
for (int i = 0; i < list.size(); i++) {
list.set(i, "a");
}
}
}
@@ -0,0 +1,15 @@
import java.util.ArrayList
class C {
fun foo1(list: List<String>) {
for (i in list.indices) {
list.set(i, "a")
}
}
fun foo2(list: ArrayList<String>) {
for (i in list.indices) {
list.set(i, "a")
}
}
}
@@ -0,0 +1,12 @@
//file
class X {
public int length = 5;
}
class C{
void foo(X x) {
for (int i = 0; i < x.length; i++) {
System.out.print(i);
}
}
}
@@ -0,0 +1,11 @@
class X {
public var length: Int = 5
}
class C {
fun foo(x: X) {
for (i in 0..x.length - 1) {
System.out.print(i)
}
}
}
@@ -0,0 +1,12 @@
//file
class X {
public int size() { return 5; }
}
class C{
void foo(X x) {
for (int i = 0; i < x.size(); i++) {
System.out.print(i);
}
}
}
@@ -0,0 +1,13 @@
class X {
public fun size(): Int {
return 5
}
}
class C {
fun foo(x: X) {
for (i in 0..x.size() - 1) {
System.out.print(i)
}
}
}