New J2K: Add Java for loop to Kotlin while or for in loop conversion

This commit is contained in:
Ilya Kirillov
2018-10-13 23:12:30 +03:00
committed by Ilya Kirillov
parent 07f99fe0d2
commit ed61aac4fd
14 changed files with 406 additions and 2 deletions
@@ -50,6 +50,7 @@ object ConversionsRunner {
+BinaryExpressionConversion()
+SwitchStatementConversion(context)
+InstanceofConversion()
+ForConversion(context)
}
fun doApply(trees: List<JKTreeElement>, context: ConversionContext) {
@@ -16,6 +16,7 @@ import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.psi.KtClassOrObject
import org.jetbrains.kotlin.psi.KtNamedFunction
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
import org.jetbrains.kotlin.psi.KtProperty
class JKSymbolProvider {
@@ -36,6 +37,7 @@ class JKSymbolProvider {
is PsiMethod -> JKMultiverseMethodSymbol(psi, this)
is PsiField -> JKMultiverseFieldSymbol(psi)
is KtNamedFunction -> JKMultiverseFunctionSymbol(psi)
is KtProperty -> JKMultiversePropertySymbol(psi)
else -> TODO(psi::class.toString())
}
}
@@ -45,6 +45,15 @@ class NewCodeBuilder {
printer.print("/* !!! Hit visitElement for element type: ${treeElement::class} !!! */")
}
override fun visitKtForInStatement(ktForInStatement: JKKtForInStatement) {
printer.printWithNoIndent("for (")
printer.printWithNoIndent(ktForInStatement.variableIdentifier.value)
printer.printWithNoIndent(" in ")
ktForInStatement.iterationExpression.accept(this)
printer.printWithNoIndent(") ")
ktForInStatement.body.accept(this)
}
override fun visitDoWhileStatement(doWhileStatement: JKDoWhileStatement) {
printer.printWithNoIndent("do")
doWhileStatement.body.accept(this)
@@ -226,6 +235,13 @@ class NewCodeBuilder {
}
}
override fun visitKtOperatorExpression(ktOperatorExpression: JKKtOperatorExpression) {
ktOperatorExpression.receiver.accept(this)
printer.printWithNoIndent(" ")
printer.printWithNoIndent(ktOperatorExpression.identifier.name)
printer.printWithNoIndent(" ")
ktOperatorExpression.argument.accept(this)
}
override fun visitIfElseExpression(ifElseExpression: JKIfElseExpression) {
printer.printWithNoIndent("if (")
@@ -0,0 +1,261 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.j2k.conversions
import com.intellij.psi.*
import jdk.nashorn.internal.ir.BlockStatement
import org.jetbrains.kotlin.j2k.ConversionContext
import org.jetbrains.kotlin.j2k.ReferenceSearcher
import org.jetbrains.kotlin.j2k.ast.*
import org.jetbrains.kotlin.j2k.hasWriteAccesses
import org.jetbrains.kotlin.j2k.isInSingleLine
import org.jetbrains.kotlin.j2k.tree.*
import org.jetbrains.kotlin.j2k.tree.impl.*
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.name.ClassId
class ForConversion(private val context: ConversionContext) : RecursiveApplicableConversionBase() {
private val referenceSearcher: ReferenceSearcher
get() = context.converter.converterServices.oldServices.referenceSearcher
override fun applyToElement(element: JKTreeElement): JKTreeElement {
if (element !is JKJavaForLoopStatement) return recurse(element)
convertToForeach(element)?.also { return recurse(it) }
convertToWhile(element)?.also { return recurse(it) }
return recurse(element)
}
private fun convertToWhile(loopStatement: JKJavaForLoopStatement): JKStatement? {
val whileBody = createWhileBody(loopStatement)
val whileStatement =
JKWhileStatementImpl(
if (loopStatement.condition !is JKStubExpression) loopStatement.condition.detached()
else JKKtLiteralExpressionImpl("true", JKLiteralExpression.LiteralType.BOOLEAN),
whileBody
)
val whileStatement = JKWhileStatementImpl(condition, whileBody)
if (loopStatement.initializer is JKEmptyStatement) return whileStatement
//TODO check for error conflict
return JKBlockStatementImpl(JKBlockImpl(listOf(loopStatement.initializer.detached(), whileStatement)))
}
private fun createWhileBody(loopStatement: JKJavaForLoopStatement): JKStatement {
if (loopStatement.updater is JKEmptyStatement) return loopStatement::body.detached()
val continueStatementConverter = object : RecursiveApplicableConversionBase() {
override fun applyToElement(element: JKTreeElement): JKTreeElement {
if (element !is JKJavaContinueStatement) return recurse(element)
//TODO ??? if (statement.findContinuedStatement()?.toContinuedLoop() != this@ForConverter.statement) return null
val statements = listOf(loopStatement.updater, element)
return recurse(JKBlockStatementImpl(JKBlockImpl(statements)))
}
}
val body = loopStatement.body
if (body is JKBlockStatement) {
val initializer = loopStatement.initializer
val hasNameConflict =
initializer is JKDeclarationStatement && initializer.declaredStatements.any { loopVar ->
loopVar is JKLocalVariable && body.statements.any { statement ->
statement is JKDeclarationStatement && statement.declaredStatements.any {
it is JKLocalVariable && it.name == loopVar.name
}
}
}
val statements =
if (hasNameConflict) {
listOf(continueStatementConverter.applyToElement(body) as JKStatement, loopStatement.updater)
} else {
body.block.statements + loopStatement.updater
}
return JKBlockStatementImpl(JKBlockImpl(statements.map { it.detached() }))
} else {
val statements =
listOf(continueStatementConverter.applyToElement(body) as JKStatement, loopStatement::updater.detached())
return JKBlockStatementImpl(JKBlockImpl(statements.map { it.detached() }))
}
}
private fun convertToForeach(loopStatement: JKJavaForLoopStatement): JKKtForInStatement? {
val loopVar =
(loopStatement.initializer as? JKDeclarationStatement)?.declaredStatements?.singleOrNull() as? JKLocalVariable ?: return null
val loopVarPsi = loopVar.psi<PsiLocalVariable>() ?: return null
val condition = loopStatement.condition as? JKBinaryExpression ?: return null
if (!loopVarPsi.hasWriteAccesses(referenceSearcher, loopStatement.body.psi())
&& !loopVarPsi.hasWriteAccesses(referenceSearcher, loopStatement.condition.psi())
) {
val left = condition.left as? JKFieldAccessExpression ?: return null
val right = condition.right
if (right.psi<PsiExpression>()?.type in listOf(PsiType.DOUBLE, PsiType.FLOAT, PsiType.CHAR)) return null
if (left.identifier.target != loopVar) return null
val start = loopVar.initializer
val operationType =
(loopStatement.updater as? JKExpressionStatement)?.expression?.isVariableIncrementOrDecrement(loopVar)
val reversed = when ((operationType as? JKJavaOperatorImpl)?.token) {
JavaTokenType.PLUSPLUS -> false
JavaTokenType.MINUSMINUS -> true
else -> return null
}
val inclusive = when ((condition.operator as? JKJavaOperatorImpl)?.token ?: (condition.operator as? JKKtOperatorImpl)?.token) {
JavaTokenType.LT, KtTokens.LT -> if (reversed) return null else false
JavaTokenType.LE, KtTokens.LTEQ -> if (reversed) return null else true
JavaTokenType.GT, KtTokens.GT -> if (reversed) false else return null
JavaTokenType.GE, KtTokens.GTEQ -> if (reversed) true else return null
JavaTokenType.NE, KtTokens.EXCLEQ -> false
else -> return null
}
val range = forIterationRange(start, right, reversed, inclusive, loopVarPsi)
//TODO for loop explicitType
// val explicitType = if (context.converter.settings.specifyLocalVariableTypeByDefault)
// JKJavaPrimitiveTypeImpl.INT
// else null
return JKKtForInStatementImpl(
loopVar.name.detached(),
range.detached(),
loopStatement.body.detached()
)
}
return null
}
private fun forIterationRange(
start: JKExpression,
bound: JKExpression,
reversed: Boolean,
inclusiveComparison: Boolean,
psiContext: PsiElement
): JKExpression {
indicesIterationRange(start, bound, reversed, inclusiveComparison)?.also { return it }
if (start.parent != null) start.detach(start.parent!!)
if (bound.parent != null) bound.detach(bound.parent!!)
return when {
reversed -> downToExpression(
start,
convertBound(bound, if (inclusiveComparison) 0 else +1),
context,
psiContext
)
bound !is JKKtLiteralExpression && !inclusiveComparison ->
untilToExpression(
start,
convertBound(bound, 0),
context,
psiContext
)
else -> JKBinaryExpressionImpl(
start,
convertBound(bound, if (inclusiveComparison) 0 else -1),
JKKtOperatorImpl.tokenToOperator[KtTokens.RANGE]!!
)
}
}
private fun convertBound(bound: JKExpression, correction: Int): JKExpression {
if (correction == 0) return bound
if (bound is JKLiteralExpression && bound.type == JKLiteralExpression.LiteralType.INT) {
val value = bound.literal.toInt()
return JKKtLiteralExpressionImpl((value + correction).toString(), bound.type)
}
val sign = if (correction > 0) KtTokens.PLUS else KtTokens.MINUS
return JKBinaryExpressionImpl(
bound,
JKKtLiteralExpressionImpl(Math.abs(correction).toString(), JKLiteralExpression.LiteralType.INT),
JKKtOperatorImpl.tokenToOperator[sign]!!
)
}
private fun indicesIterationRange(
start: JKExpression,
bound: JKExpression,
reversed: Boolean,
inclusiveComparison: Boolean
): JKExpression? {
val collectionSizeExpression =
if (reversed) {
if (!inclusiveComparison) return null
if ((bound as? JKLiteralExpression)?.literal?.toIntOrNull() != 0) return null
if (start !is JKBinaryExpression) return null
if ((start.operator as? JKKtOperatorImpl)?.token != KtTokens.MINUS) return null
if ((start.right as? JKLiteralExpression)?.literal?.toIntOrNull() != 1) return null
start.left
} else {
if (inclusiveComparison) return null
if ((start as? JKLiteralExpression)?.literal?.toIntOrNull() != 0) return null
bound
} as? JKQualifiedExpression ?: return null
val indices = indicesByCollectionSize(collectionSizeExpression)
?: indicesByArrayLength(collectionSizeExpression)
?: return null
val psiContext = collectionSizeExpression.psi<PsiExpression>() ?: return null
return if (reversed) {
val reversedSymbol = context.symbolProvider.provideDirectSymbol(
multiResolveFqName(ClassId.fromString("kotlin/collections/reversed"), psiContext).first()
) as JKMethodSymbol
JKQualifiedExpressionImpl(
indices.detached(),
JKKtQualifierImpl.DOT,
JKJavaMethodCallExpressionImpl(reversedSymbol, JKExpressionListImpl())
)
} else indices
}
private fun indicesByCollectionSize(javaSizeCall: JKQualifiedExpression): JKQualifiedExpression? {
val methodCall = javaSizeCall.selector as? JKMethodCallExpression ?: return null
//TODO check if receiver type is Collection
if (methodCall.identifier.name == "size" && methodCall.arguments.expressions.isEmpty()) {
return toIndicesCall(javaSizeCall)
}
return null
}
private fun indicesByArrayLength(javaSizeCall: JKQualifiedExpression): JKQualifiedExpression? {
val methodCall = javaSizeCall.selector as? JKFieldAccessExpression ?: return null
val receiverType = javaSizeCall.receiver.type(context)
//TODO check if receiver type is kotlin.array
if (methodCall.identifier.name == "length" && receiverType is JKJavaArrayType) {
return toIndicesCall(javaSizeCall)
}
return null
}
private fun toIndicesCall(javaSizeCall: JKQualifiedExpression): JKQualifiedExpression? {
val psiContext = javaSizeCall.psi<PsiExpression>() ?: return null
val indiciesSymbol = context.symbolProvider.provideDirectSymbol(
multiResolveFqName(ClassId.fromString("kotlin/collections/indices"), psiContext).first()
) as JKMultiversePropertySymbol
javaSizeCall.selector = JKFieldAccessExpressionImpl(indiciesSymbol)
return javaSizeCall
}
private fun JKExpression.isVariableIncrementOrDecrement(variable: JKLocalVariable): JKOperator? {
val pair = when (this) {
is JKPostfixExpression -> operator to expression
is JKPrefixExpression -> operator to expression
else -> return null
}
if ((pair.second as? JKFieldAccessExpression)?.identifier?.target != variable) return null
return pair.first
}
private inline fun <reified ElementType : PsiElement> JKElement.psi() =
context.backAnnotator(this) as? ElementType
}
@@ -0,0 +1,57 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.j2k.conversions
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.j2k.ConversionContext
import org.jetbrains.kotlin.j2k.tree.JKExpression
import org.jetbrains.kotlin.j2k.tree.JKKtOperatorExpression
import org.jetbrains.kotlin.j2k.tree.impl.JKKtOperatorExpressionImpl
import org.jetbrains.kotlin.j2k.tree.impl.JKMethodSymbol
import org.jetbrains.kotlin.name.ClassId
fun untilToExpression(
from: JKExpression,
to: JKExpression,
conversionContext: ConversionContext,
psiContext: PsiElement
): JKKtOperatorExpression =
rangeExpression(
from,
to,
"until",
conversionContext,
psiContext
)
fun downToExpression(
from: JKExpression,
to: JKExpression,
conversionContext: ConversionContext,
psiContext: PsiElement
): JKKtOperatorExpression =
rangeExpression(
from,
to,
"downTo",
conversionContext,
psiContext
)
fun rangeExpression(
from: JKExpression,
to: JKExpression,
operatorName: String,
conversionContext: ConversionContext,
psiContext: PsiElement
): JKKtOperatorExpressionImpl {
val symbol = conversionContext.symbolProvider.provideDirectSymbol(
multiResolveFqName(ClassId.fromString("kotlin/ranges/$operatorName"), psiContext).first()
) as JKMethodSymbol
if (from.parent != null) from.detach(from.parent!!)
if (to.parent != null) to.detach(to.parent!!)
return JKKtOperatorExpressionImpl(from, symbol, to)
}
@@ -70,6 +70,7 @@ class JKJavaOperatorImpl private constructor(val token: IElementType) : JKOperat
JavaTokenType.MINUSMINUS -> "--"
JavaTokenType.PLUSPLUS -> "++"
JavaTokenType.EXCL -> "!"
JavaTokenType.ASTERISKEQ -> "*="
else -> TODO("TODO $token")
}
@@ -375,4 +375,10 @@ class JKDelegationConstructorCallImpl(
class JKFieldAccessExpressionImpl(override var identifier: JKFieldSymbol) : JKFieldAccessExpression, JKElementBase() {
override fun <R, D> accept(visitor: JKVisitor<R, D>, data: D): R = visitor.visitFieldAccessExpression(this, data)
}
}
val JKStatement.statements: List<JKStatement>
get() =
when (this) {
is JKBlockStatement -> block.statements
else -> listOf(this)
}
@@ -22,6 +22,7 @@ import org.jetbrains.kotlin.j2k.tree.*
import org.jetbrains.kotlin.j2k.tree.visitors.JKVisitor
import org.jetbrains.kotlin.lexer.KtSingleValueToken
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.utils.addToStdlib.assertedCast
import org.jetbrains.kotlin.utils.addToStdlib.cast
class JKKtPropertyImpl(
@@ -265,4 +266,27 @@ fun JKClass.getOrCreateInitDeclaration(): JKKtInitDeclaration {
val newDeclaration = JKKtInitDeclarationImpl(JKBlockImpl())
declarationList += newDeclaration
return newDeclaration
}
class JKKtForInStatementImpl(variableIdentifier: JKNameIdentifier, iterationExpression: JKExpression, body: JKStatement) :
JKKtForInStatement,
JKBranchElementBase() {
override var variableIdentifier: JKNameIdentifier by child(variableIdentifier)
override var iterationExpression: JKExpression by child(iterationExpression)
override var body: JKStatement by child(body)
override fun <R, D> accept(visitor: JKVisitor<R, D>, data: D): R = visitor.visitKtForInStatement(this, data)
}
class JKKtOperatorExpressionImpl(
receiver: JKExpression,
override var identifier: JKMethodSymbol,
argument: JKExpression
) : JKKtOperatorExpression, JKBranchElementBase() {
override var receiver: JKExpression by child(receiver)
override var argument: JKExpression by child(argument)
override fun <R, D> accept(visitor: JKVisitor<R, D>, data: D): R = visitor.visitKtOperatorExpression(this, data)
}
class JKJavaContinueStatementImpl() : JKJavaContinueStatement, JKElementBase() {
override fun <R, D> accept(visitor: JKVisitor<R, D>, data: D): R = visitor.visitJavaContinueStatement(this, data)
}
@@ -17,6 +17,7 @@ import org.jetbrains.kotlin.j2k.tree.JKMethod
import org.jetbrains.kotlin.j2k.tree.JKTreeElement
import org.jetbrains.kotlin.psi.KtClassOrObject
import org.jetbrains.kotlin.psi.KtNamedFunction
import org.jetbrains.kotlin.psi.KtProperty
interface JKSymbol {
val target: Any
@@ -120,13 +121,21 @@ class JKMultiverseFieldSymbol(override val target: PsiField) : JKFieldSymbol {
get() = target.name // TODO("Fix this")
}
class JKMultiversePropertySymbol(override val target: KtProperty) : JKFieldSymbol {
override val name: String
get() = target.name!!
override val declaredIn: JKSymbol
get() = TODO("not implemented") //To change initializer of created properties use File | Settings | File Templates.
override val fqName: String
get() = target.name!! // TODO("Fix this")
}
class JKUnresolvedField(override val target: String) : JKFieldSymbol {
constructor(target: PsiReference) : this(target.canonicalText)
override val declaredIn: JKSymbol
get() = TODO("not implemented") //To change initializer of created properties use File | Settings | File Templates.
override val fqName: String = target
override val name: String
get() = target
}
@@ -107,3 +107,4 @@ interface JKJavaLabelSwitchCase : JKJavaSwitchCase {
var label: JKExpression
}
interface JKJavaContinueStatement: JKStatement
@@ -16,6 +16,8 @@
package org.jetbrains.kotlin.j2k.tree
import org.jetbrains.kotlin.j2k.tree.impl.JKMethodSymbol
interface JKKtProperty : JKField {
val getter: JKBlock
val setter: JKBlock
@@ -83,4 +85,16 @@ interface JKKtIsExpression : JKExpression {
interface JKKtInitDeclaration : JKDeclaration {
var block: JKBlock
}
interface JKKtForInStatement : JKStatement {
var variableIdentifier: JKNameIdentifier
var iterationExpression: JKExpression
var body: JKStatement
}
interface JKKtOperatorExpression : JKExpression {
var receiver: JKExpression
var identifier: JKMethodSymbol
var argument: JKExpression
}
@@ -81,3 +81,6 @@ inline fun <reified T> JKElement.getParentOfType(): T? {
p = p.parent
}
}
fun <T : JKElement> T.detached() =
also { if (parent != null) detach(parent!!) }
@@ -69,6 +69,7 @@ interface JKVisitor<out R, in D> {
fun visitJavaInstanceOfExpression(javaInstanceOfExpression: JKJavaInstanceOfExpression, data: D): R = visitExpression(javaInstanceOfExpression, data)
fun visitJavaPolyadicExpression(javaPolyadicExpression: JKJavaPolyadicExpression, data: D): R = visitExpression(javaPolyadicExpression, data)
fun visitJavaAssignmentExpression(javaAssignmentExpression: JKJavaAssignmentExpression, data: D): R = visitExpression(javaAssignmentExpression, data)
fun visitJavaContinueStatement(javaContinueStatement: JKJavaContinueStatement, data: D): R = visitStatement(javaContinueStatement, data)
fun visitJavaSwitchStatement(javaSwitchStatement: JKJavaSwitchStatement, data: D): R = visitStatement(javaSwitchStatement, data)
fun visitJavaSwitchCase(javaSwitchCase: JKJavaSwitchCase, data: D): R = visitTreeElement(javaSwitchCase, data)
fun visitJavaDefaultSwitchCase(javaDefaultSwitchCase: JKJavaDefaultSwitchCase, data: D): R = visitJavaSwitchCase(javaDefaultSwitchCase, data)
@@ -90,4 +91,6 @@ interface JKVisitor<out R, in D> {
fun visitKtValueWhenLabel(ktValueWhenLabel: JKKtValueWhenLabel, data: D): R = visitKtWhenLabel(ktValueWhenLabel, data)
fun visitKtIsExpression(ktIsExpression: JKKtIsExpression, data: D): R = visitExpression(ktIsExpression, data)
fun visitKtInitDeclaration(ktInitDeclaration: JKKtInitDeclaration, data: D): R = visitDeclaration(ktInitDeclaration, data)
fun visitKtForInStatement(ktForInStatement: JKKtForInStatement, data: D): R = visitStatement(ktForInStatement, data)
fun visitKtOperatorExpression(ktOperatorExpression: JKKtOperatorExpression, data: D): R = visitExpression(ktOperatorExpression, data)
}
@@ -143,6 +143,8 @@ interface JKVisitorVoid : JKVisitor<Unit, Nothing?> {
override fun visitJavaDefaultSwitchCase(javaDefaultSwitchCase: JKJavaDefaultSwitchCase, data: Nothing?) = visitJavaDefaultSwitchCase(javaDefaultSwitchCase)
fun visitJavaLabelSwitchCase(javaLabelSwitchCase: JKJavaLabelSwitchCase) = visitJavaSwitchCase(javaLabelSwitchCase, null)
override fun visitJavaLabelSwitchCase(javaLabelSwitchCase: JKJavaLabelSwitchCase, data: Nothing?) = visitJavaLabelSwitchCase(javaLabelSwitchCase)
fun visitJavaContinueStatement(javaContinueStatement: JKJavaContinueStatement) = visitStatement(javaContinueStatement, null)
override fun visitJavaContinueStatement(javaContinueStatement: JKJavaContinueStatement, data: Nothing?) = visitJavaContinueStatement(javaContinueStatement)
fun visitKtProperty(ktProperty: JKKtProperty) = visitField(ktProperty, null)
override fun visitKtProperty(ktProperty: JKKtProperty, data: Nothing?) = visitKtProperty(ktProperty)
fun visitKtFunction(ktFunction: JKKtFunction) = visitMethod(ktFunction, null)
@@ -177,4 +179,8 @@ interface JKVisitorVoid : JKVisitor<Unit, Nothing?> {
override fun visitKtIsExpression(ktIsExpression: JKKtIsExpression, data: Nothing?) = visitKtIsExpression(ktIsExpression)
fun visitKtInitDeclaration(ktInitDeclaration: JKKtInitDeclaration) = visitDeclaration(ktInitDeclaration, null)
override fun visitKtInitDeclaration(ktInitDeclaration: JKKtInitDeclaration, data: Nothing?) = visitKtInitDeclaration(ktInitDeclaration)
fun visitKtForInStatement(ktForInStatement: JKKtForInStatement) = visitStatement(ktForInStatement, null)
override fun visitKtForInStatement(ktForInStatement: JKKtForInStatement, data: Nothing?) = visitKtForInStatement(ktForInStatement)
fun visitKtOperatorExpression(ktOperatorExpression: JKKtOperatorExpression) = visitExpression(ktOperatorExpression, null)
override fun visitKtOperatorExpression(ktOperatorExpression: JKKtOperatorExpression, data: Nothing?) = visitKtOperatorExpression(ktOperatorExpression)
}