diff --git a/j2k/src/org/jetbrains/jet/j2k/ConstructorConverter.kt b/j2k/src/org/jetbrains/jet/j2k/ConstructorConverter.kt index 1cdcbcbabdf..02e04ef1707 100644 --- a/j2k/src/org/jetbrains/jet/j2k/ConstructorConverter.kt +++ b/j2k/src/org/jetbrains/jet/j2k/ConstructorConverter.kt @@ -51,6 +51,19 @@ class ConstructorConverter(private val psiClass: PsiClass, private val converter val parameterDefaults: List?) private fun choosePrimaryConstructor(): PsiMethod? { + val toTargetConstructorMap = buildToTargetConstructorMap() + + val candidates = constructors.filter { it !in toTargetConstructorMap } + if (candidates.size != 1) return null // there should be only one constructor which does not call other constructor + val primary = candidates.single() + if (toTargetConstructorMap.values().any() { it.constructor != primary }) return null // all other constructors call our candidate (directly or indirectly) + + dropConstructorsForDefaultValues(primary, toTargetConstructorMap) + + return primary + } + + private fun buildToTargetConstructorMap(): Map { val toTargetConstructorMap = HashMap() for (constructor in constructors) { val firstStatement = constructor.getBody()?.getStatements()?.firstOrNull() @@ -85,15 +98,7 @@ class ConstructorConverter(private val psiClass: PsiClass, private val converter } } } - - val candidates = constructors.filter { it !in toTargetConstructorMap } - if (candidates.size != 1) return null // there should be only one constructor which does not call other constructor - val primary = candidates.single() - if (toTargetConstructorMap.values().any() { it.constructor != primary }) return null // all other constructors call our candidate (directly or indirectly) - - dropConstructorsForDefaultValues(primary, toTargetConstructorMap) - - return primary + return toTargetConstructorMap } private fun calcTargetParameterDefaults(constructor: PsiMethod, target: PsiMethod, targetCall: PsiMethodCallExpression): List? { @@ -307,7 +312,7 @@ class ConstructorConverter(private val psiClass: PsiClass, private val converter return generateArtificialPrimaryConstructor(classBody) } else { - val updatedFunctions = replaceConstructorCallsInFactoryFunctions(classBody.factoryFunctions) + val updatedFunctions = processFactoryFunctionsWithConstructorCall(classBody.factoryFunctions) return ClassBody(classBody.primaryConstructorSignature, classBody.members, classBody.classObjectMembers, updatedFunctions, classBody.lBrace, classBody.rBrace) } } @@ -320,7 +325,7 @@ class ConstructorConverter(private val psiClass: PsiClass, private val converter for (function in classBody.factoryFunctions) { val body = function.body!! // 2 cases: secondary constructor either calls another constructor or does not call any - val newStatements = replaceConstructorCallInFactoryFunction(body) ?: + val newStatements = processFactoryFunctionWithConstructorCall(body) ?: insertCallToArtificialPrimary(body, fieldsToInitialize) val newBody = Block(newStatements, LBrace().assignNoPrototype(), RBrace().assignNoPrototype()).assignNoPrototype() updatedFactoryFunctions.add(function.withBody(newBody)) @@ -340,10 +345,10 @@ class ConstructorConverter(private val psiClass: PsiClass, private val converter return ClassBody(constructorSignature, updatedMembers, classBody.classObjectMembers, updatedFactoryFunctions, classBody.lBrace, classBody.rBrace) } - private fun replaceConstructorCallsInFactoryFunctions(functions: List): List { + private fun processFactoryFunctionsWithConstructorCall(functions: List): List { return functions.map { function -> val body = function.body!! - val statements = replaceConstructorCallInFactoryFunction(body) + val statements = processFactoryFunctionWithConstructorCall(body) if (statements != null) { function.withBody(Block(statements, body.lBrace, body.rBrace).assignPrototypesFrom(body)) } @@ -353,7 +358,7 @@ class ConstructorConverter(private val psiClass: PsiClass, private val converter } } - private fun replaceConstructorCallInFactoryFunction(body: Block): List? { + private fun processFactoryFunctionWithConstructorCall(body: Block): List? { val statements = ArrayList(body.statements) // searching for other constructor call in form "this(...)" diff --git a/j2k/src/org/jetbrains/jet/j2k/Converter.kt b/j2k/src/org/jetbrains/jet/j2k/Converter.kt index 0616226e592..2feaeb5aa99 100644 --- a/j2k/src/org/jetbrains/jet/j2k/Converter.kt +++ b/j2k/src/org/jetbrains/jet/j2k/Converter.kt @@ -149,10 +149,8 @@ public class Converter private(val project: Project, val settings: ConverterSett else if (member is FactoryFunction) { factoryFunctions.add(member) } - else if (useClassObject && - psiMember.hasModifierProperty(PsiModifier.STATIC) && - // we generate nested classes with factory functions into class object as a workaround until secondary constructors supported by Kotlin - (psiMember !is PsiClass || (member as Class).body.factoryFunctions.isNotEmpty())) { + else if (useClassObject + && (if (member is Class) shouldGenerateIntoClassObject(member) else psiMember.hasModifierProperty(PsiModifier.STATIC))) { classObjectMembers.add(member) } else { @@ -169,8 +167,7 @@ public class Converter private(val project: Project, val settings: ConverterSett private fun shouldGenerateClassObject(psiClass: PsiClass, convertedMembers: Map): Boolean { if (psiClass.isEnum()) return false - // we generate nested classes with factory functions into class object as a workaround until secondary constructors supported by Kotlin - if (convertedMembers.values().any { it is Class && !it.modifiers.contains(Modifier.INNER) && it.body.factoryFunctions.isNotEmpty() }) return true + if (convertedMembers.values().any { it is Class && shouldGenerateIntoClassObject(it) }) return true val members = convertedMembers.keySet().filter { !it.isConstructor() } val classObjectMembers = members.filter { it !is PsiClass && it.hasModifierProperty(PsiModifier.STATIC) } @@ -183,6 +180,10 @@ public class Converter private(val project: Project, val settings: ConverterSett } } + // we generate nested classes with factory functions into class object as a workaround until secondary constructors supported by Kotlin + private fun shouldGenerateIntoClassObject(nestedClass: Class) + = !nestedClass.modifiers.contains(Modifier.INNER) && nestedClass.body.factoryFunctions.isNotEmpty() + private fun convertMember(member: PsiMember, membersToRemove: MutableSet, constructorConverter: ConstructorConverter?): Member? = when (member) { is PsiMethod -> convertMethod(member, membersToRemove, constructorConverter) is PsiField -> convertField(member) diff --git a/j2k/src/org/jetbrains/jet/j2k/visitors/ExpressionVisitor.kt b/j2k/src/org/jetbrains/jet/j2k/visitors/ExpressionVisitor.kt index d2e0734efa0..d054c3beec0 100644 --- a/j2k/src/org/jetbrains/jet/j2k/visitors/ExpressionVisitor.kt +++ b/j2k/src/org/jetbrains/jet/j2k/visitors/ExpressionVisitor.kt @@ -227,95 +227,23 @@ open class ExpressionVisitor(private val converter: Converter) : JavaElementVisi } } - if (target is PsiMethod && isObjectEquals(target) && arguments.size == 1) { - val qualifier = methodExpr.getQualifierExpression() - if (qualifier != null && qualifier !is PsiSuperExpression) { - result = BinaryExpression(converter.convertExpression(qualifier), converter.convertExpression(arguments.single()), "==") - return + if (target is PsiMethod) { + val specialMethod = SpecialMethod.values().firstOrNull { it.matches(target) } + if (specialMethod != null && arguments.size == specialMethod.parameterCount) { + val converted = specialMethod.convertCall(methodExpr.getQualifierExpression(), arguments, converter) + if (converted != null) { + result = converted + return + } } } - if (target is PsiMethod && isObjectsEquals(target) && arguments.size == 2) { - result = BinaryExpression(converter.convertExpression(arguments[0]), converter.convertExpression(arguments[1]), "==") - return - } - - //TODO: type arguments maybe required if we are in initializer of variable with no explicit type - if (target is PsiMethod && isCollectionsEmptyList(target) && arguments.size == 0) { - result = MethodCallExpression.build(null, "listOf", listOf(), listOf(), false) - return - } - - //TODO: type arguments maybe required if we are in initializer of variable with no explicit type - if (target is PsiMethod && isCollectionsEmptySet(target) && arguments.size == 0) { - result = MethodCallExpression.build(null, "setOf", listOf(), listOf(), false) - return - } - - //TODO: type arguments maybe required if we are in initializer of variable with no explicit type - if (target is PsiMethod && isCollectionsEmptyMap(target) && arguments.size == 0) { - result = MethodCallExpression.build(null, "mapOf", listOf(), listOf(), false) - return - } - - if (target is PsiMethod && isCollectionsSingletonList(target) && arguments.size == 1) { - result = MethodCallExpression.build(null, "listOf", listOf(converter.convertExpression(arguments.single())), listOf(), false) - return - } - - if (target is PsiMethod && isCollectionsSingleton(target) && arguments.size == 1) { - result = MethodCallExpression.build(null, "setOf", listOf(converter.convertExpression(arguments.single())), listOf(), false) - return - } - result = MethodCallExpression(converter.convertExpression(methodExpr), convertArguments(expression), typeArguments, isNullable) } - private fun isObjectEquals(method: PsiMethod): Boolean { - return method.getName() == "equals" && - method.getParameterList().getParameters().size == 1 && - method.getParameterList().getParameters().single().getType().getCanonicalText() == JAVA_LANG_OBJECT - } - - private fun isObjectsEquals(method: PsiMethod): Boolean { - return method.getName() == "equals" && - method.getParameterList().getParameters().size == 2 && - method.getContainingClass()?.getQualifiedName() == "java.util.Objects" - } - - private fun isCollectionsEmptyList(method: PsiMethod): Boolean { - return method.getName() == "emptyList" && - method.getParameterList().getParameters().size == 0 && - method.getContainingClass()?.getQualifiedName() == "java.util.Collections" - } - - private fun isCollectionsEmptySet(method: PsiMethod): Boolean { - return method.getName() == "emptySet" && - method.getParameterList().getParameters().size == 0 && - method.getContainingClass()?.getQualifiedName() == "java.util.Collections" - } - - private fun isCollectionsEmptyMap(method: PsiMethod): Boolean { - return method.getName() == "emptyMap" && - method.getParameterList().getParameters().size == 0 && - method.getContainingClass()?.getQualifiedName() == "java.util.Collections" - } - - private fun isCollectionsSingletonList(method: PsiMethod): Boolean { - return method.getName() == "singletonList" && - method.getParameterList().getParameters().size == 1 && - method.getContainingClass()?.getQualifiedName() == "java.util.Collections" - } - - private fun isCollectionsSingleton(method: PsiMethod): Boolean { - return method.getName() == "singleton" && - method.getParameterList().getParameters().size == 1 && - method.getContainingClass()?.getQualifiedName() == "java.util.Collections" - } - override fun visitNewExpression(expression: PsiNewExpression) { if (expression.getArrayInitializer() != null) { result = converter.convertExpression(expression.getArrayInitializer()) diff --git a/j2k/src/org/jetbrains/jet/j2k/visitors/ForConverter.kt b/j2k/src/org/jetbrains/jet/j2k/visitors/ForConverter.kt new file mode 100644 index 00000000000..91d22cdf762 --- /dev/null +++ b/j2k/src/org/jetbrains/jet/j2k/visitors/ForConverter.kt @@ -0,0 +1,194 @@ +/* + * Copyright 2010-2014 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.jet.j2k.visitors + +import com.intellij.psi.PsiForStatement +import org.jetbrains.jet.j2k.ast.ForeachStatement +import com.intellij.psi.PsiDeclarationStatement +import org.jetbrains.jet.j2k.singleOrNull2 +import com.intellij.psi.PsiLocalVariable +import org.jetbrains.jet.j2k.hasWriteAccesses +import com.intellij.psi.PsiBinaryExpression +import com.intellij.psi.JavaTokenType +import com.intellij.psi.PsiReferenceExpression +import com.intellij.psi.PsiExpressionStatement +import org.jetbrains.jet.j2k.ast.PrimitiveType +import org.jetbrains.jet.j2k.ast.Identifier +import org.jetbrains.jet.j2k.ast.assignNoPrototype +import org.jetbrains.jet.j2k.ast.declarationIdentifier +import org.jetbrains.jet.j2k.isInSingleLine +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiVariable +import com.intellij.psi.PsiPostfixExpression +import com.intellij.psi.PsiPrefixExpression +import com.intellij.psi.PsiExpression +import com.intellij.psi.tree.IElementType +import org.jetbrains.jet.j2k.ast.Expression +import com.intellij.psi.PsiLiteralExpression +import com.intellij.psi.PsiMethodCallExpression +import com.intellij.psi.PsiElementFactory +import com.intellij.psi.CommonClassNames +import org.jetbrains.jet.j2k.ast.QualifiedExpression +import com.intellij.psi.PsiArrayType +import org.jetbrains.jet.j2k.ast.BinaryExpression +import org.jetbrains.jet.j2k.ast.LiteralExpression +import org.jetbrains.jet.j2k.ast.RangeExpression +import org.jetbrains.jet.j2k.Converter +import com.intellij.psi.PsiBlockStatement +import com.intellij.psi.PsiNamedElement +import org.jetbrains.jet.j2k.ast.Block +import org.jetbrains.jet.j2k.ast.LBrace +import org.jetbrains.jet.j2k.ast.RBrace +import org.jetbrains.jet.j2k.ast.assignPrototypesFrom +import org.jetbrains.jet.j2k.ast.WhileStatement +import org.jetbrains.jet.j2k.ast.MethodCallExpression +import org.jetbrains.jet.j2k.ast.LambdaExpression +import org.jetbrains.jet.j2k.ast.Statement + +class ForConverter(private val statement: PsiForStatement, private val converter: Converter) { + private val initialization = statement.getInitialization() + private val update = statement.getUpdate() + private val condition = statement.getCondition() + private val body = statement.getBody() + + public fun execute(): Statement { + val foreach = convertToForeach() + if (foreach != null) return foreach + + val initialization = statement.getInitialization() + val update = statement.getUpdate() + val condition = statement.getCondition() + val body = statement.getBody() + + val initializationConverted = converter.convertStatement(initialization) + val updateConverted = converter.convertStatement(update) + + val whileBody = if (updateConverted.isEmpty) { + converter.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 statements = listOf(converter.convertStatement(body), updateConverted) + Block(statements, LBrace().assignNoPrototype(), RBrace().assignNoPrototype(), true).assignNoPrototype() + } + else { + val block = converter.convertBlock(body.getCodeBlock(), true) + Block(block.statements + listOf(updateConverted), block.lBrace, block.rBrace, true).assignPrototypesFrom(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) return whileStatement + + val statements = listOf(initializationConverted, whileStatement) + val block = Block(statements, LBrace().assignNoPrototype(), RBrace().assignNoPrototype()).assignNoPrototype() + return MethodCallExpression.build(null, "run", listOf(), listOf(), false, LambdaExpression(null, block)) + } + + private fun convertToForeach(): ForeachStatement? { + if (initialization is PsiDeclarationStatement) { + val loopVar = initialization.getDeclaredElements().singleOrNull2() as? PsiLocalVariable + if (loopVar != null + && !loopVar.hasWriteAccesses(body) + && !loopVar.hasWriteAccesses(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 explicitType = if (converter.settings.specifyLocalVariableTypeByDefault) + PrimitiveType(Identifier("Int").assignNoPrototype()).assignNoPrototype() + else + null + return ForeachStatement(loopVar.declarationIdentifier(), explicitType, range, converter.convertStatementOrBlock(body), statement.isInSingleLine()) + } + } + } + } + return null + } + + private fun PsiElement.isVariablePlusPlus(variable: PsiVariable): 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 + } + + 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) + } + +} \ No newline at end of file diff --git a/j2k/src/org/jetbrains/jet/j2k/visitors/SpecialMethod.kt b/j2k/src/org/jetbrains/jet/j2k/visitors/SpecialMethod.kt new file mode 100644 index 00000000000..ee20fadf886 --- /dev/null +++ b/j2k/src/org/jetbrains/jet/j2k/visitors/SpecialMethod.kt @@ -0,0 +1,79 @@ +/* + * Copyright 2010-2014 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.jet.j2k.visitors + +import com.intellij.psi.PsiMethod +import com.intellij.psi.PsiExpression +import org.jetbrains.jet.j2k.Converter +import org.jetbrains.jet.j2k.ast.BinaryExpression +import org.jetbrains.jet.j2k.ast.Expression +import org.jetbrains.jet.j2k.ast.MethodCallExpression +import com.intellij.psi.CommonClassNames.JAVA_LANG_OBJECT +import com.intellij.psi.PsiSuperExpression + +enum class SpecialMethod(val qualifiedClassName: String?, val methodName: String, val parameterCount: Int) { + OBJECT_EQUALS: SpecialMethod(null, "equals", 1) { + override fun matches(method: PsiMethod) + = super.matches(method) && method.getParameterList().getParameters().single().getType().getCanonicalText() == JAVA_LANG_OBJECT + + override fun convertCall(qualifier: PsiExpression?, arguments: Array, converter: Converter): Expression? { + if (qualifier == null || qualifier is PsiSuperExpression) return null + return BinaryExpression(converter.convertExpression(qualifier), converter.convertExpression(arguments.single()), "==") + } + } + + OBJECTS_EQUALS: SpecialMethod("java.util.Objects", "equals", 2) { + override fun convertCall(qualifier: PsiExpression?, arguments: Array, converter: Converter) + = BinaryExpression(converter.convertExpression(arguments[0]), converter.convertExpression(arguments[1]), "==") + } + + //TODO: type arguments maybe required if we are in initializer of variable with no explicit type + COLLECTIONS_EMPTY_LIST: SpecialMethod("java.util.Collections", "emptyList", 0) { + override fun convertCall(qualifier: PsiExpression?, arguments: Array, converter: Converter) + = MethodCallExpression.build(null, "listOf", listOf(), listOf(), false) + } + + //TODO: type arguments maybe required if we are in initializer of variable with no explicit type + COLLECTIONS_EMPTY_SET: SpecialMethod("java.util.Collections", "emptySet", 0) { + override fun convertCall(qualifier: PsiExpression?, arguments: Array, converter: Converter) + = MethodCallExpression.build(null, "setOf", listOf(), listOf(), false) + } + + //TODO: type arguments maybe required if we are in initializer of variable with no explicit type + COLLECTIONS_EMPTY_MAP: SpecialMethod("java.util.Collections", "emptyMap", 0) { + override fun convertCall(qualifier: PsiExpression?, arguments: Array, converter: Converter) + = MethodCallExpression.build(null, "mapOf", listOf(), listOf(), false) + } + + COLLECTIONS_SINGLETON_LIST: SpecialMethod("java.util.Collections", "singletonList", 1) { + override fun convertCall(qualifier: PsiExpression?, arguments: Array, converter: Converter) + = MethodCallExpression.build(null, "listOf", listOf(converter.convertExpression(arguments.single())), listOf(), false) + } + + COLLECTIONS_SINGLETON: SpecialMethod("java.util.Collections", "singleton", 1) { + override fun convertCall(qualifier: PsiExpression?, arguments: Array, converter: Converter) + = MethodCallExpression.build(null, "setOf", listOf(converter.convertExpression(arguments.single())), listOf(), false) + } + + open fun matches(method: PsiMethod): Boolean { + if (method.getName() != methodName) return false + if (qualifiedClassName != null && method.getContainingClass()?.getQualifiedName() != qualifiedClassName) return false + return method.getParameterList().getParametersCount() == parameterCount + } + + abstract fun convertCall(qualifier: PsiExpression?, arguments: Array, converter: Converter): Expression? +} \ No newline at end of file diff --git a/j2k/src/org/jetbrains/jet/j2k/visitors/StatementVisitor.kt b/j2k/src/org/jetbrains/jet/j2k/visitors/StatementVisitor.kt index eda3dd5585e..9a5922c1d41 100644 --- a/j2k/src/org/jetbrains/jet/j2k/visitors/StatementVisitor.kt +++ b/j2k/src/org/jetbrains/jet/j2k/visitors/StatementVisitor.kt @@ -19,12 +19,8 @@ package org.jetbrains.jet.j2k.visitors import com.intellij.psi.* import org.jetbrains.jet.j2k.Converter import org.jetbrains.jet.j2k.ast.* -import org.jetbrains.jet.j2k.countWriteAccesses import java.util.ArrayList -import org.jetbrains.jet.j2k.hasWriteAccesses import org.jetbrains.jet.j2k.isInSingleLine -import com.intellij.psi.tree.IElementType -import org.jetbrains.jet.j2k.singleOrNull2 open class StatementVisitor(public val converter: Converter) : JavaElementVisitor() { public var result: Statement = Statement.Empty @@ -86,7 +82,7 @@ open class StatementVisitor(public val converter: Converter) : JavaElementVisito converter.convertExpression(condition, condition.getType()) else converter.convertExpression(condition) - result = DoWhileStatement(expression, convertStatementOrBlock(statement.getBody()), statement.isInSingleLine()) + result = DoWhileStatement(expression, converter.convertStatementOrBlock(statement.getBody()), statement.isInSingleLine()) } override fun visitExpressionStatement(statement: PsiExpressionStatement) { @@ -98,124 +94,7 @@ open class StatementVisitor(public val converter: Converter) : JavaElementVisito } override fun visitForStatement(statement: PsiForStatement) { - val initialization = statement.getInitialization() - val update = statement.getUpdate() - val condition = statement.getCondition() - val body = statement.getBody() - - if (initialization is PsiDeclarationStatement) { - val loopVar = initialization.getDeclaredElements().singleOrNull2() as? PsiLocalVariable - if (loopVar != null - && !loopVar.hasWriteAccesses(body) - && !loopVar.hasWriteAccesses(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 explicitType = if (converter.settings.specifyLocalVariableTypeByDefault) PrimitiveType(Identifier("Int").assignNoPrototype()).assignNoPrototype() else null - result = ForeachStatement(loopVar.declarationIdentifier(), explicitType, range, convertStatementOrBlock(body), statement.isInSingleLine()) - return - } - } - } - } - - 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 statements = listOf(converter.convertStatement(body), updateConverted) - Block(statements, LBrace().assignNoPrototype(), RBrace().assignNoPrototype(), true).assignNoPrototype() - } - else { - val block = converter.convertBlock(body.getCodeBlock(), true) - Block(block.statements + listOf(updateConverted), block.lBrace, block.rBrace, true).assignPrototypesFrom(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 PsiElement.isVariablePlusPlus(variable: PsiVariable): 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 - } - - 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) + result = ForConverter(statement, converter).execute() } override fun visitForeachStatement(statement: PsiForeachStatement) { @@ -225,7 +104,7 @@ open class StatementVisitor(public val converter: Converter) : JavaElementVisito result = ForeachStatement(iterationParameter.declarationIdentifier(), if (converter.settings.specifyLocalVariableTypeByDefault) converter.typeConverter.convertVariableType(iterationParameter) else null, iterator, - convertStatementOrBlock(statement.getBody()), + converter.convertStatementOrBlock(statement.getBody()), statement.isInSingleLine()) } @@ -233,8 +112,8 @@ open class StatementVisitor(public val converter: Converter) : JavaElementVisito val condition = statement.getCondition() val expression = converter.convertExpression(condition, PsiType.BOOLEAN) result = IfStatement(expression, - convertStatementOrBlock(statement.getThenBranch()), - convertStatementOrBlock(statement.getElseBranch()), + converter.convertStatementOrBlock(statement.getThenBranch()), + converter.convertStatementOrBlock(statement.getElseBranch()), statement.isInSingleLine()) } @@ -328,7 +207,7 @@ open class StatementVisitor(public val converter: Converter) : JavaElementVisito converter.convertExpression(condition, condition!!.getType()) else converter.convertExpression(condition) - result = WhileStatement(expression, convertStatementOrBlock(statement.getBody()), statement.isInSingleLine()) + result = WhileStatement(expression, converter.convertStatementOrBlock(statement.getBody()), statement.isInSingleLine()) } override fun visitReturnStatement(statement: PsiReturnStatement) { @@ -344,11 +223,12 @@ open class StatementVisitor(public val converter: Converter) : JavaElementVisito override fun visitEmptyStatement(statement: PsiEmptyStatement) { result = Statement.Empty } - - private fun convertStatementOrBlock(statement: PsiStatement?): Statement { - return if (statement is PsiBlockStatement) - converter.convertBlock(statement.getCodeBlock()) - else - converter.convertStatement(statement) - } } + +fun Converter.convertStatementOrBlock(statement: PsiStatement?): Statement { + return if (statement is PsiBlockStatement) + convertBlock(statement.getCodeBlock()) + else + convertStatement(statement) +} + diff --git a/j2k/tests/test/org/jetbrains/jet/j2k/test/JavaToKotlinConverterTestGenerated.java b/j2k/tests/test/org/jetbrains/jet/j2k/test/JavaToKotlinConverterTestGenerated.java index 68536b6a2f2..1b7499558bc 100644 --- a/j2k/tests/test/org/jetbrains/jet/j2k/test/JavaToKotlinConverterTestGenerated.java +++ b/j2k/tests/test/org/jetbrains/jet/j2k/test/JavaToKotlinConverterTestGenerated.java @@ -811,6 +811,11 @@ public class JavaToKotlinConverterTestGenerated extends AbstractJavaToKotlinConv JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), "org.jetbrains.jet.generators.tests.TestsPackage", new File("j2k/tests/testData/ast/constructors"), Pattern.compile("^(.+)\\.java$"), true); } + @TestMetadata("artificialPrimary.java") + public void testArtificialPrimary() throws Exception { + doTest("j2k/tests/testData/ast/constructors/artificialPrimary.java"); + } + @TestMetadata("chain.java") public void testChain() throws Exception { doTest("j2k/tests/testData/ast/constructors/chain.java"); diff --git a/j2k/tests/testData/ast/constructors/artificialPrimary.java b/j2k/tests/testData/ast/constructors/artificialPrimary.java new file mode 100644 index 00000000000..f617f1045c0 --- /dev/null +++ b/j2k/tests/testData/ast/constructors/artificialPrimary.java @@ -0,0 +1,14 @@ +//file +public class Test { + private final String s; + boolean b; + double d; + + public Test() { + b = true; + } + + public Test(String s) { + this.s = s; + } +} diff --git a/j2k/tests/testData/ast/constructors/artificialPrimary.kt b/j2k/tests/testData/ast/constructors/artificialPrimary.kt new file mode 100644 index 00000000000..cacf0801ad2 --- /dev/null +++ b/j2k/tests/testData/ast/constructors/artificialPrimary.kt @@ -0,0 +1,14 @@ +public fun Test(): Test { + val __ = Test(null) + __.b = true + return __ +} + +public fun Test(s: String): Test { + return Test(s) +} + +public class Test(private val s: String) { + var b: Boolean = false + var d: Double = 0.toDouble() +} \ No newline at end of file