KT-5692 Java to Kotlin conversion: convert int range loops in reverse order + a few more improvements

#KT-5692 Fixed
This commit is contained in:
Valentin Kipyatkov
2015-03-26 17:50:49 +03:00
parent 16295b875b
commit 46193f3ef7
30 changed files with 379 additions and 52 deletions
+100 -45
View File
@@ -123,22 +123,25 @@ class ForConverter(
private fun convertToForeach(): ForeachStatement? {
if (initialization is PsiDeclarationStatement) {
val loopVar = initialization.getDeclaredElements().singleOrNull() as? PsiLocalVariable
if (loopVar != null
&& !loopVar.hasWriteAccesses(referenceSearcher, body)
&& !loopVar.hasWriteAccesses(referenceSearcher, condition)
&& condition is PsiBinaryExpression) {
val loopVar = initialization.getDeclaredElements().singleOrNull() as? PsiLocalVariable ?: return null
if (!loopVar.hasWriteAccesses(referenceSearcher, body)
&& !loopVar.hasWriteAccesses(referenceSearcher, condition)
&& condition is PsiBinaryExpression) {
val operationTokenType = condition.getOperationTokenType()
val lowerBound = condition.getLOperand()
val upperBound = condition.getROperand()
if ((operationTokenType == JavaTokenType.LT || operationTokenType == JavaTokenType.LE) &&
lowerBound is PsiReferenceExpression &&
lowerBound.resolve() == loopVar &&
upperBound != null) {
val start = loopVar.getInitializer()
if (start != null &&
(update as? PsiExpressionStatement)?.getExpression()?.isVariablePlusPlus(loopVar) ?: false) {
val range = forIterationRange(start, upperBound, operationTokenType).assignNoPrototype()
val reversed = when (operationTokenType) {
JavaTokenType.LT, JavaTokenType.LE -> false
JavaTokenType.GT, JavaTokenType.GE -> true
else -> return null
}
val left = condition.getLOperand() as? PsiReferenceExpression ?: return null
val right = condition.getROperand() ?: return null
if (left.resolve() == loopVar) {
val start = loopVar.getInitializer() ?: return null
val operationType = if (reversed) JavaTokenType.MINUSMINUS else JavaTokenType.PLUSPLUS
if ((update as? PsiExpressionStatement)?.getExpression()?.isVariableIncrementOrDecrement(loopVar, operationType) ?: false) {
val range = forIterationRange(start, right, operationTokenType).assignNoPrototype()
val explicitType = if (settings.specifyLocalVariableTypeByDefault)
PrimitiveType(Identifier("Int").assignNoPrototype()).assignNoPrototype()
else
@@ -151,51 +154,103 @@ class ForConverter(
return null
}
private fun PsiElement.isVariablePlusPlus(variable: PsiVariable): Boolean {
private fun PsiElement.isVariableIncrementOrDecrement(variable: PsiVariable, operationTokenType: IElementType): Boolean {
//TODO: simplify code when KT-5453 fixed
val pair = when (this) {
is PsiPostfixExpression -> getOperationTokenType() to getOperand()
is PsiPrefixExpression -> getOperationTokenType() to getOperand()
else -> return false
}
return pair.first == JavaTokenType.PLUSPLUS && (pair.second as? PsiReferenceExpression)?.resolve() == variable
return pair.first == operationTokenType && (pair.second as? PsiReferenceExpression)?.resolve() == variable
}
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(project).createTypeByFQClassName(CommonClassNames.JAVA_UTIL_LIST)
val qualifierType = qualifier.getType()
if (qualifierType != null && listType.isAssignableFrom(qualifierType)) {
return QualifiedExpression(codeConverter.convertExpression(qualifier), Identifier("indices", false).assignNoPrototype())
}
private fun forIterationRange(start: PsiExpression, bound: PsiExpression, comparisonTokenType: IElementType): Expression {
val indicesRange = indicesIterationRange(start, bound, comparisonTokenType)
if (indicesRange != null) return indicesRange
val startConverted = codeConverter.convertExpression(start)
return when (comparisonTokenType) {
JavaTokenType.LT, JavaTokenType.LE ->
RangeExpression(startConverted, convertBound(bound, if (comparisonTokenType == JavaTokenType.LT) -1 else 0))
JavaTokenType.GT, JavaTokenType.GE ->
DownToExpression(startConverted, convertBound(bound, if (comparisonTokenType == JavaTokenType.GT) +1 else 0))
else ->
throw IllegalAccessException()
}
}
private fun indicesIterationRange(start: PsiExpression, bound: PsiExpression, comparisonTokenType: IElementType): Expression? {
val reversed = when (comparisonTokenType) {
JavaTokenType.LT -> false
JavaTokenType.GE -> true
else -> return null
}
val lower = if (reversed) bound else start
val upper = if (reversed) start else bound
if ((lower as? PsiLiteralExpression)?.getValue() != 0) return null
val collectionSize = if (reversed) {
if (upper !is PsiBinaryExpression) return null
if (upper.getOperationTokenType() != JavaTokenType.MINUS) return null
if ((upper.getROperand() as? PsiLiteralExpression)?.getValue() != 1) return null
upper.getLOperand()
}
else {
upper
}
var indices: Expression? = null
// check if it's iteration through list indices
if (collectionSize is PsiMethodCallExpression && collectionSize.getArgumentList().getExpressions().isEmpty()) {
val methodExpr = collectionSize.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 collectionType = PsiElementFactory.SERVICE.getInstance(project).createTypeByFQClassName(CommonClassNames.JAVA_UTIL_COLLECTION)
val qualifierType = qualifier.getType()
if (qualifierType != null && collectionType.isAssignableFrom(qualifierType)) {
indices = QualifiedExpression(codeConverter.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(codeConverter.convertExpression(qualifier), Identifier("indices", false).assignNoPrototype())
}
}
// check if it's iteration through array indices
else if (collectionSize is PsiReferenceExpression /* we don't convert to .indices if qualifier is method call or something because of possible side effects */
&& collectionSize.getReferenceName() == "length") {
val qualifier = collectionSize.getQualifierExpression()
if (qualifier is PsiReferenceExpression && qualifier.getType() is PsiArrayType) {
indices = QualifiedExpression(codeConverter.convertExpression(qualifier), Identifier("indices", false).assignNoPrototype())
}
}
val end = codeConverter.convertExpression(upperBound)
val endExpression = if (comparisonTokenType == JavaTokenType.LT)
BinaryExpression(end, LiteralExpression("1").assignNoPrototype(), "-").assignNoPrototype()
if (indices == null) return null
return if (reversed)
MethodCallExpression.build(indices!!.assignNoPrototype(), "reversed", listOf(), listOf(), false)
else
end
return RangeExpression(codeConverter.convertExpression(start), endExpression)
indices
}
private fun convertBound(bound: PsiExpression, correction: Int): Expression {
if (correction == 0) {
return codeConverter.convertExpression(bound)
}
if (bound is PsiLiteralExpression) {
val value = bound.getValue()
if (value is Int) {
return LiteralExpression((value + correction).toString()).assignPrototype(bound)
}
}
val converted = codeConverter.convertExpression(bound)
val sign = if (correction > 0) "+" else "-"
return BinaryExpression(converted, LiteralExpression(Math.abs(correction).toString()).assignNoPrototype(), sign).assignNoPrototype()
}
private fun PsiStatement.toContinuedLoop(): PsiLoopStatement? {
@@ -149,6 +149,12 @@ class RangeExpression(val start: Expression, val end: Expression): Expression()
}
}
class DownToExpression(val start: Expression, val end: Expression): Expression() {
override fun generateCode(builder: CodeBuilder) {
builder.appendOperand(this, start).append(" downTo ").appendOperand(this, end)
}
}
fun createArrayInitializerExpression(arrayType: ArrayType, initializers: List<Expression>, needExplicitType: Boolean) : MethodCallExpression {
val elementType = arrayType.elementType
val createArrayFunction = if (elementType is PrimitiveType)
+2 -2
View File
@@ -25,7 +25,7 @@ fun CodeBuilder.appendWithPrefix(element: Element, prefix: String): CodeBuilder
fun CodeBuilder.appendWithSuffix(element: Element, suffix: String): CodeBuilder = if (!element.isEmpty) this append element append suffix else this
fun CodeBuilder.appendOperand(expression: Expression, operand: Expression, parenthesisForSamePrecedence: Boolean = false): CodeBuilder {
val parentPrecedence = expression.precedence() ?: throw IllegalArgumentException("Unknown precendence for $this")
val parentPrecedence = expression.precedence() ?: throw IllegalArgumentException("Unknown precendence for $expression")
val operandPrecedence = operand.precedence()
val needParenthesis = operandPrecedence != null &&
(parentPrecedence < operandPrecedence || parentPrecedence == operandPrecedence && parenthesisForSamePrecedence)
@@ -54,7 +54,7 @@ private fun Expression.precedence(): Int? {
else -> 6 /* simple name */
}
is RangeExpression -> 5
is RangeExpression, is DownToExpression -> 5
is IsOperator -> 8
@@ -0,0 +1,9 @@
import java.util.Collection;
public class A {
void foo(String[] array) {
for(int i = array.length - 1; i >= 0; i--) {
System.out.println(i);
}
}
}
@@ -0,0 +1,7 @@
public class A {
fun foo(array: Array<String>) {
for (i in array.indices.reversed()) {
System.out.println(i)
}
}
}
@@ -0,0 +1,7 @@
public class A {
void foo() {
for(int i = 10; i >= 0; i--) {
System.out.println(i);
}
}
}
@@ -0,0 +1,7 @@
public class A {
fun foo() {
for (i in 10 downTo 0) {
System.out.println(i)
}
}
}
@@ -0,0 +1,7 @@
public class A {
void foo() {
for(int i = 10; i > 0; i--) {
System.out.println(i);
}
}
}
@@ -0,0 +1,7 @@
public class A {
fun foo() {
for (i in 10 downTo 1) {
System.out.println(i)
}
}
}
@@ -0,0 +1,7 @@
public class A {
void foo(int min) {
for(int i = 10; i > min; i--) {
System.out.println(i);
}
}
}
@@ -0,0 +1,7 @@
public class A {
fun foo(min: Int) {
for (i in 10 downTo min + 1) {
System.out.println(i)
}
}
}
@@ -0,0 +1,9 @@
import java.util.Collection;
public class A {
void foo(String[] array) {
for(int i = array.length; i >= 0; i--) {
System.out.println(i);
}
}
}
@@ -0,0 +1,7 @@
public class A {
fun foo(array: Array<String>) {
for (i in array.size() downTo 0) {
System.out.println(i)
}
}
}
@@ -0,0 +1,9 @@
import java.util.Collection;
public class A {
void foo(String[] array) {
for(int i = array.length - 2; i >= 0; i--) {
System.out.println(i);
}
}
}
@@ -0,0 +1,7 @@
public class A {
fun foo(array: Array<String>) {
for (i in array.size() - 2 downTo 0) {
System.out.println(i)
}
}
}
@@ -0,0 +1,9 @@
import java.util.Collection;
public class A {
void foo(Collection<String> collection) {
for(int i = collection.size(); i >= 0; i--) {
System.out.println(i);
}
}
}
@@ -0,0 +1,7 @@
public class A {
fun foo(collection: Collection<String>) {
for (i in collection.size() downTo 0) {
System.out.println(i)
}
}
}
@@ -1,4 +1,4 @@
val array = IntArray(10)
for (i in 0..10 - 1) {
for (i in 0..9) {
array[i] = i
}
@@ -1,4 +1,4 @@
val array = IntArray(10)
for (i in 0..10 - 1) {
for (i in 0..9) {
array[i] = i
}
@@ -0,0 +1,2 @@
//statement
for (int i = 0; i < N; i++) { System.out.println(i); }
@@ -0,0 +1,3 @@
for (i in 0..N - 1) {
System.out.println(i)
}
@@ -0,0 +1,10 @@
import java.lang.System;
import java.util.Collection;
class C{
void foo1(Collection<String> collection) {
for (int i = 0; i < collection.size(); i++) {
System.out.print(i);
}
}
}
@@ -0,0 +1,9 @@
import java.lang.System
class C {
fun foo1(collection: Collection<String>) {
for (i in collection.indices) {
System.out.print(i)
}
}
}
@@ -1 +1 @@
for (i in 0..0 - 1) t++
for (i in 0..-1) t++
@@ -0,0 +1,9 @@
import java.util.Collection;
public class A {
void foo(Collection<String> collection) {
for(int i = collection.size() - 1; i >= 0; i--) {
System.out.println(i);
}
}
}
@@ -0,0 +1,7 @@
public class A {
fun foo(collection: Collection<String>) {
for (i in collection.indices.reversed()) {
System.out.println(i)
}
}
}
+1 -1
View File
@@ -2,7 +2,7 @@ package demo
class Test {
fun test() {
for (i in 0..10 - 1) {
for (i in 0..9) {
System.out.println(i)
}
}
@@ -1,7 +1,7 @@
// !specifyLocalVariableTypeByDefault: true
public fun foo(list: List<String>) {
val array: IntArray = IntArray(10)
for (i: Int in 0..10 - 1) {
for (i: Int in 0..9) {
array[i] = i
}
@@ -1834,18 +1834,60 @@ public class JavaToKotlinConverterForWebDemoTestGenerated extends AbstractJavaTo
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("j2k/testData/fileOrElement/for"), Pattern.compile("^(.+)\\.java$"), true);
}
@TestMetadata("arrayIndicesReversed.java")
public void testArrayIndicesReversed() throws Exception {
String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/for/arrayIndicesReversed.java");
doTest(fileName);
}
@TestMetadata("commonCaseForTest.java")
public void testCommonCaseForTest() throws Exception {
String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/for/commonCaseForTest.java");
doTest(fileName);
}
@TestMetadata("downTo1.java")
public void testDownTo1() throws Exception {
String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/for/downTo1.java");
doTest(fileName);
}
@TestMetadata("downTo2.java")
public void testDownTo2() throws Exception {
String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/for/downTo2.java");
doTest(fileName);
}
@TestMetadata("downTo3.java")
public void testDownTo3() throws Exception {
String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/for/downTo3.java");
doTest(fileName);
}
@TestMetadata("falseArrayIndicesReversed.java")
public void testFalseArrayIndicesReversed() throws Exception {
String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/for/falseArrayIndicesReversed.java");
doTest(fileName);
}
@TestMetadata("falseArrayIndicesReversed2.java")
public void testFalseArrayIndicesReversed2() throws Exception {
String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/for/falseArrayIndicesReversed2.java");
doTest(fileName);
}
@TestMetadata("falseForRange.java")
public void testFalseForRange() throws Exception {
String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/for/falseForRange.java");
doTest(fileName);
}
@TestMetadata("falseIndicesReversed.java")
public void testFalseIndicesReversed() throws Exception {
String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/for/falseIndicesReversed.java");
doTest(fileName);
}
@TestMetadata("forRangeWithBlock.java")
public void testForRangeWithBlock() throws Exception {
String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/for/forRangeWithBlock.java");
@@ -1864,12 +1906,24 @@ public class JavaToKotlinConverterForWebDemoTestGenerated extends AbstractJavaTo
doTest(fileName);
}
@TestMetadata("forRangeWithLT2.java")
public void testForRangeWithLT2() throws Exception {
String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/for/forRangeWithLT2.java");
doTest(fileName);
}
@TestMetadata("forThroughArrayIndices.java")
public void testForThroughArrayIndices() throws Exception {
String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/for/forThroughArrayIndices.java");
doTest(fileName);
}
@TestMetadata("forThroughCollectionIndices.java")
public void testForThroughCollectionIndices() throws Exception {
String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/for/forThroughCollectionIndices.java");
doTest(fileName);
}
@TestMetadata("forThroughListIndices.java")
public void testForThroughListIndices() throws Exception {
String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/for/forThroughListIndices.java");
@@ -1936,6 +1990,12 @@ public class JavaToKotlinConverterForWebDemoTestGenerated extends AbstractJavaTo
doTest(fileName);
}
@TestMetadata("indicesReversed.java")
public void testIndicesReversed() throws Exception {
String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/for/indicesReversed.java");
doTest(fileName);
}
@TestMetadata("infiniteFor.java")
public void testInfiniteFor() throws Exception {
String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/for/infiniteFor.java");
@@ -1834,18 +1834,60 @@ public class JavaToKotlinConverterSingleFileTestGenerated extends AbstractJavaTo
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("j2k/testData/fileOrElement/for"), Pattern.compile("^(.+)\\.java$"), true);
}
@TestMetadata("arrayIndicesReversed.java")
public void testArrayIndicesReversed() throws Exception {
String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/for/arrayIndicesReversed.java");
doTest(fileName);
}
@TestMetadata("commonCaseForTest.java")
public void testCommonCaseForTest() throws Exception {
String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/for/commonCaseForTest.java");
doTest(fileName);
}
@TestMetadata("downTo1.java")
public void testDownTo1() throws Exception {
String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/for/downTo1.java");
doTest(fileName);
}
@TestMetadata("downTo2.java")
public void testDownTo2() throws Exception {
String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/for/downTo2.java");
doTest(fileName);
}
@TestMetadata("downTo3.java")
public void testDownTo3() throws Exception {
String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/for/downTo3.java");
doTest(fileName);
}
@TestMetadata("falseArrayIndicesReversed.java")
public void testFalseArrayIndicesReversed() throws Exception {
String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/for/falseArrayIndicesReversed.java");
doTest(fileName);
}
@TestMetadata("falseArrayIndicesReversed2.java")
public void testFalseArrayIndicesReversed2() throws Exception {
String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/for/falseArrayIndicesReversed2.java");
doTest(fileName);
}
@TestMetadata("falseForRange.java")
public void testFalseForRange() throws Exception {
String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/for/falseForRange.java");
doTest(fileName);
}
@TestMetadata("falseIndicesReversed.java")
public void testFalseIndicesReversed() throws Exception {
String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/for/falseIndicesReversed.java");
doTest(fileName);
}
@TestMetadata("forRangeWithBlock.java")
public void testForRangeWithBlock() throws Exception {
String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/for/forRangeWithBlock.java");
@@ -1864,12 +1906,24 @@ public class JavaToKotlinConverterSingleFileTestGenerated extends AbstractJavaTo
doTest(fileName);
}
@TestMetadata("forRangeWithLT2.java")
public void testForRangeWithLT2() throws Exception {
String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/for/forRangeWithLT2.java");
doTest(fileName);
}
@TestMetadata("forThroughArrayIndices.java")
public void testForThroughArrayIndices() throws Exception {
String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/for/forThroughArrayIndices.java");
doTest(fileName);
}
@TestMetadata("forThroughCollectionIndices.java")
public void testForThroughCollectionIndices() throws Exception {
String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/for/forThroughCollectionIndices.java");
doTest(fileName);
}
@TestMetadata("forThroughListIndices.java")
public void testForThroughListIndices() throws Exception {
String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/for/forThroughListIndices.java");
@@ -1936,6 +1990,12 @@ public class JavaToKotlinConverterSingleFileTestGenerated extends AbstractJavaTo
doTest(fileName);
}
@TestMetadata("indicesReversed.java")
public void testIndicesReversed() throws Exception {
String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/for/indicesReversed.java");
doTest(fileName);
}
@TestMetadata("infiniteFor.java")
public void testInfiniteFor() throws Exception {
String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/for/infiniteFor.java");