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:
@@ -249,6 +249,10 @@
|
||||
<item name='com.intellij.psi.PsiElement com.intellij.psi.PsiElement replace(com.intellij.psi.PsiElement)'>
|
||||
<annotation name='org.jetbrains.annotations.NotNull'/>
|
||||
</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'>
|
||||
<annotation name='org.jetbrains.annotations.NotNull'/>
|
||||
</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 {
|
||||
val elementType = arrayType.elementType
|
||||
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(
|
||||
val variable: Parameter,
|
||||
val expression: Expression,
|
||||
val variableName: Identifier,
|
||||
val collection: Expression,
|
||||
val body: Element,
|
||||
singleLine: Boolean
|
||||
) : Statement() {
|
||||
@@ -98,7 +99,7 @@ class ForeachStatement(
|
||||
private val br = if (singleLine) " " else "\n"
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -46,19 +46,21 @@ private fun Expression.precedence(): Int? {
|
||||
is BinaryExpression -> when(op) {
|
||||
"*", "/", "%" -> 3
|
||||
"+", "-" -> 4
|
||||
"?:" -> 6
|
||||
">", "<", ">=", "<=" -> 8
|
||||
"==", "!=", "===", "!===" -> 9
|
||||
"&&" -> 10
|
||||
"||" -> 11
|
||||
else -> 5 /* simple name */
|
||||
"?:" -> 7
|
||||
">", "<", ">=", "<=" -> 9
|
||||
"==", "!=", "===", "!===" -> 10
|
||||
"&&" -> 11
|
||||
"||" -> 12
|
||||
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
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ import org.jetbrains.jet.j2k.countWriteAccesses
|
||||
import java.util.ArrayList
|
||||
import org.jetbrains.jet.j2k.hasWriteAccesses
|
||||
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() {
|
||||
public var result: Statement = Statement.Empty
|
||||
@@ -102,35 +102,33 @@ open class StatementVisitor(public val converter: Converter) : JavaElementVisito
|
||||
val condition = statement.getCondition()
|
||||
val body = statement.getBody()
|
||||
|
||||
val loopVar = initialization?.getFirstChild() as? PsiLocalVariable
|
||||
val onceWritableIterator = loopVar != null
|
||||
if (initialization is PsiDeclarationStatement && initialization.getFirstChild()!!.getNextSibling() == null) {
|
||||
val loopVar = initialization.getFirstChild() as? PsiLocalVariable
|
||||
if (loopVar != null
|
||||
&& !loopVar.hasWriteAccesses(body)
|
||||
&& !loopVar.hasWriteAccesses(condition)
|
||||
&& loopVar.countWriteAccesses(update) == 1
|
||||
|
||||
val operationTokenType = (condition as? PsiBinaryExpression)?.getOperationTokenType()
|
||||
if (initialization is PsiDeclarationStatement
|
||||
&& initialization.getFirstChild() == initialization.getLastChild()
|
||||
&& condition != null
|
||||
&& 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 != null
|
||||
&& update.getChildren().size == 1
|
||||
&& update.getChildren().single().isPlusPlusExpression()
|
||||
&& (operationTokenType == JavaTokenType.LT || operationTokenType == JavaTokenType.LE)
|
||||
&& loopVar != null
|
||||
&& loopVar.getNameIdentifier() != null
|
||||
&& onceWritableIterator) {
|
||||
val end = converter.convertExpression((condition as PsiBinaryExpression).getROperand())
|
||||
val endExpression = if (operationTokenType == JavaTokenType.LT)
|
||||
BinaryExpression(end, LiteralExpression("1").assignNoPrototype(), "-").assignNoPrototype()
|
||||
else
|
||||
end
|
||||
result = ForeachWithRangeStatement(loopVar.declarationIdentifier(),
|
||||
converter.convertExpression(loopVar.getInitializer()),
|
||||
endExpression,
|
||||
convertStatementOrBlock(body),
|
||||
statement.isInSingleLine())
|
||||
&& update.getChildren().single().isPlusPlusExpression()) {
|
||||
val range = forIterationRange(start, upperBound, operationTokenType).assignNoPrototype()
|
||||
result = ForeachStatement(loopVar.declarationIdentifier(), range, convertStatementOrBlock(body), statement.isInSingleLine())
|
||||
return
|
||||
}
|
||||
else {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val initializationConverted = converter.convertStatement(initialization)
|
||||
val updateConverted = converter.convertStatement(update)
|
||||
|
||||
@@ -173,12 +171,48 @@ open class StatementVisitor(public val converter: Converter) : JavaElementVisito
|
||||
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) {
|
||||
val iteratorExpr = converter.convertExpression(statement.getIteratedValue())
|
||||
val iterator = if (iteratorExpr.isNullable) BangBangExpression(iteratorExpr).assignNoPrototype() else iteratorExpr
|
||||
result = ForeachStatement(converter.convertParameter(statement.getIterationParameter()),
|
||||
result = ForeachStatement(statement.getIterationParameter().declarationIdentifier(),
|
||||
iterator,
|
||||
convertStatementOrBlock(statement.getBody()),
|
||||
statement.isInSingleLine())
|
||||
|
||||
@@ -1222,6 +1222,11 @@ public class JavaToKotlinConverterTestGenerated extends AbstractJavaToKotlinConv
|
||||
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")
|
||||
public void testForRangeWithBlock() throws Exception {
|
||||
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");
|
||||
}
|
||||
|
||||
@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")
|
||||
public void testForWithBlock() throws Exception {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user