blob: e9fd7721bc1c1dd1f2b2effb5ecd47889ed41a31 [file] [log] [blame]
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.j2k
import com.intellij.psi.*
import com.intellij.psi.tree.IElementType
import com.intellij.util.IncorrectOperationException
import org.jetbrains.kotlin.j2k.ast.*
import org.jetbrains.kotlin.psi.psiUtil.parents
import org.jetbrains.kotlin.psi.psiUtil.siblings
class ForConverter(
private val statement: PsiForStatement,
private val codeConverter: CodeConverter
) {
private val referenceSearcher = codeConverter.converter.referenceSearcher
private val settings = codeConverter.settings
private val project = codeConverter.converter.project
private val initialization = statement.initialization
private val update = statement.update
private val condition = statement.condition
private val body = statement.body
fun execute(): Statement {
val foreach = convertToForeach()
if (foreach != null) return foreach
val initialization = statement.initialization
val update = statement.update
val condition = statement.condition
val body = statement.body
val initializationConverted = codeConverter.convertStatement(initialization)
val updateConverted = codeConverter.convertStatement(update)
val whileBody = if (updateConverted.isEmpty) {
codeConverter.convertStatementOrBlock(body)
}
else {
// we should process all continue-statements because we need to add update statement(s) before them
val codeConverterToUse = codeConverter.withSpecialStatementConverter(object : SpecialStatementConverter {
override fun convertStatement(statement: PsiStatement, codeConverter: CodeConverter): Statement? {
if (statement !is PsiContinueStatement) return null
if (statement.findContinuedStatement()?.toContinuedLoop() != this@ForConverter.statement) return null
val continueConverted = this@ForConverter.codeConverter.convertStatement(statement)
val statements = listOf(updateConverted, continueConverted)
return if (statement.parent is PsiCodeBlock) {
// generate fictive statement which will generate multiple statements
object : Statement() {
override fun generateCode(builder: CodeBuilder) {
builder.append(statements, "\n")
}
}
}
else {
Block.of(statements)
}
}
})
if (body is PsiBlockStatement) {
val nameConflict = initialization is PsiDeclarationStatement && initialization.declaredElements.any { loopVar ->
loopVar is PsiNamedElement && body.codeBlock.statements.any { statement ->
statement is PsiDeclarationStatement && statement.declaredElements.any {
it is PsiNamedElement && it.name == loopVar.name
}
}
}
if (nameConflict) {
val statements = listOf(codeConverterToUse.convertStatement(body), updateConverted)
Block.of(statements).assignNoPrototype()
}
else {
val block = codeConverterToUse.convertBlock(body.codeBlock, true)
Block(block.statements + listOf(updateConverted), block.lBrace, block.rBrace, true).assignPrototypesFrom(block)
}
}
else {
val statements = listOf(codeConverterToUse.convertStatement(body), updateConverted)
Block.of(statements).assignNoPrototype()
}
}
val whileStatement = WhileStatement(
if (condition != null) codeConverter.convertExpression(condition) else LiteralExpression("true").assignNoPrototype(),
whileBody,
statement.isInSingleLine()).assignNoPrototype()
if (initializationConverted.isEmpty) return whileStatement
val kind = when {
statement.parents.filter { it !is PsiLabeledStatement }.first() !is PsiCodeBlock ->
WhileWithInitializationPseudoStatement.Kind.WITH_BLOCK
hasNameConflict() ->
WhileWithInitializationPseudoStatement.Kind.WITH_RUN_BLOCK
else ->
WhileWithInitializationPseudoStatement.Kind.SIMPLE
}
return WhileWithInitializationPseudoStatement(initializationConverted, whileStatement, kind)
}
class WhileWithInitializationPseudoStatement(
val initialization: Statement,
val loop: Statement,
val kind: WhileWithInitializationPseudoStatement.Kind
) : Statement() {
enum class Kind {
SIMPLE,
WITH_BLOCK,
WITH_RUN_BLOCK
}
private val statements = listOf(initialization, loop)
override fun generateCode(builder: CodeBuilder) {
if (kind == Kind.SIMPLE) {
builder.append(statements, "\n")
}
else {
val block = Block.of(statements).assignNoPrototype()
if (kind == Kind.WITH_BLOCK) {
block.generateCode(builder)
}
else {
val argumentList = ArgumentList.withNoPrototype(LambdaExpression(null, block))
val call = MethodCallExpression.buildNonNull(null, "run", argumentList)
call.generateCode(builder)
}
}
}
}
private fun convertToForeach(): ForeachStatement? {
if (initialization is PsiDeclarationStatement) {
val loopVar = initialization.declaredElements.singleOrNull() as? PsiLocalVariable ?: return null
if (!loopVar.hasWriteAccesses(referenceSearcher, body)
&& !loopVar.hasWriteAccesses(referenceSearcher, condition)
&& condition is PsiBinaryExpression) {
val left = condition.lOperand as? PsiReferenceExpression ?: return null
val right = condition.rOperand ?: return null
if (right.type == PsiType.DOUBLE || right.type == PsiType.FLOAT || right.type == PsiType.CHAR) {
return null
}
if (left.resolve() == loopVar) {
val start = loopVar.initializer ?: return null
val operationType = (update as? PsiExpressionStatement)?.expression?.isVariableIncrementOrDecrement(loopVar)
val reversed = when (operationType) {
JavaTokenType.PLUSPLUS -> false
JavaTokenType.MINUSMINUS -> true
else -> return null
}
val inclusive = when (condition.operationTokenType) {
JavaTokenType.LT -> if (reversed) return null else false
JavaTokenType.LE -> if (reversed) return null else true
JavaTokenType.GT -> if (reversed) false else return null
JavaTokenType.GE -> if (reversed) true else return null
JavaTokenType.NE -> false
else -> return null
}
val range = forIterationRange(start, right, reversed, inclusive).assignNoPrototype()
val explicitType = if (settings.specifyLocalVariableTypeByDefault)
PrimitiveType(Identifier.withNoPrototype("Int")).assignNoPrototype()
else
null
return ForeachStatement(loopVar.declarationIdentifier(), explicitType, range, codeConverter.convertStatementOrBlock(body), statement.isInSingleLine())
}
}
}
return null
}
private fun PsiElement.isVariableIncrementOrDecrement(variable: PsiVariable): IElementType? {
//TODO: simplify code when KT-5453 fixed
val pair = when (this) {
is PsiPostfixExpression -> operationTokenType to operand
is PsiPrefixExpression -> operationTokenType to operand
else -> return null
}
if ((pair.second as? PsiReferenceExpression)?.resolve() != variable) return null
return pair.first
}
private fun forIterationRange(start: PsiExpression, bound: PsiExpression, reversed: Boolean, inclusiveComparison: Boolean): Expression {
val indicesRange = indicesIterationRange(start, bound, reversed, inclusiveComparison)
if (indicesRange != null) return indicesRange
val startConverted = codeConverter.convertExpression(start)
return when {
reversed -> DownToExpression(startConverted, convertBound(bound, if (inclusiveComparison) 0 else +1))
bound !is PsiLiteralExpression && !inclusiveComparison -> UntilExpression(startConverted, convertBound(bound, 0))
else -> RangeExpression(startConverted, convertBound(bound, if (inclusiveComparison) 0 else -1))
}
}
private fun indicesIterationRange(start: PsiExpression, bound: PsiExpression, reversed: Boolean, inclusiveComparison: Boolean): Expression? {
val collectionSize = if (reversed) {
if (!inclusiveComparison) return null
if ((bound as? PsiLiteralExpression)?.value != 0) return null
if (start !is PsiBinaryExpression) return null
if (start.operationTokenType != JavaTokenType.MINUS) return null
if ((start.rOperand as? PsiLiteralExpression)?.value != 1) return null
start.lOperand
}
else {
if (inclusiveComparison) return null
if ((start as? PsiLiteralExpression)?.value != 0) return null
bound
}
var indices: Expression? = null
// check if it's iteration through list indices
if (collectionSize is PsiMethodCallExpression && collectionSize.argumentList.expressions.isEmpty()) {
val methodExpr = collectionSize.methodExpression
if (methodExpr.referenceName == "size") {
val qualifier = methodExpr.qualifierExpression
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.type
if (qualifierType != null && collectionType.isAssignableFrom(qualifierType)) {
indices = QualifiedExpression(codeConverter.convertExpression(qualifier), Identifier.withNoPrototype("indices", isNullable = false), null)
}
}
}
}
// 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.referenceName == "length") {
val qualifier = collectionSize.qualifierExpression
if (qualifier is PsiReferenceExpression && qualifier.type is PsiArrayType) {
indices = QualifiedExpression(codeConverter.convertExpression(qualifier), Identifier.withNoPrototype("indices", isNullable = false), null)
}
}
if (indices == null) return null
return if (reversed)
MethodCallExpression.buildNonNull(indices.assignNoPrototype(), "reversed")
else
indices
}
private fun convertBound(bound: PsiExpression, correction: Int): Expression {
if (correction == 0) {
return codeConverter.convertExpression(bound)
}
if (bound is PsiLiteralExpression) {
val value = bound.value
if (value is Int) {
return LiteralExpression((value + correction).toString()).assignPrototype(bound)
}
}
val converted = codeConverter.convertExpression(bound)
val sign = if (correction > 0) JavaTokenType.PLUS else JavaTokenType.MINUS
return BinaryExpression(converted, LiteralExpression(Math.abs(correction).toString()).assignNoPrototype(), Operator(sign).assignPrototype(bound)).assignNoPrototype()
}
private fun PsiStatement.toContinuedLoop(): PsiLoopStatement? {
return when (this) {
is PsiLoopStatement -> this
is PsiLabeledStatement -> this.statement?.toContinuedLoop()
else -> null
}
}
private fun hasNameConflict(): Boolean {
val names = statement.initialization?.declaredVariableNames() ?: return false
if (names.isEmpty()) return false
val factory = PsiElementFactory.SERVICE.getInstance(project)
for (name in names) {
val refExpr = try {
factory.createExpressionFromText(name, statement) as? PsiReferenceExpression ?: return true
}
catch(e: IncorrectOperationException) {
return true
}
if (refExpr.resolve() != null) return true
}
var hasConflict = false
for (laterStatement in statement.siblings(forward = true, withItself = false)) {
laterStatement.accept(object: JavaRecursiveElementVisitor() {
override fun visitDeclarationStatement(statement: PsiDeclarationStatement) {
super.visitDeclarationStatement(statement)
if (statement.declaredVariableNames().any { it in names }) {
hasConflict = true
}
}
})
}
return hasConflict
}
private fun PsiStatement.declaredVariableNames(): Collection<String> {
val declarationStatement = this as? PsiDeclarationStatement ?: return listOf()
return declarationStatement.declaredElements
.filterIsInstance<PsiVariable>()
.map { it.name!! }
}
}