Java to Kotlin converter: more correct detection of primary constructor + constructor chains are supported (not completely correct code generated yet)

This commit is contained in:
Valentin Kipyatkov
2014-06-26 18:51:41 +04:00
parent 9d1452bbbc
commit f698ca69d7
8 changed files with 206 additions and 84 deletions
@@ -164,26 +164,81 @@ class ConstructorConverter(private val converter: Converter) {
public fun postProcessConstructors(classBody: ClassBody, psiClass: PsiClass): ClassBody { public fun postProcessConstructors(classBody: ClassBody, psiClass: PsiClass): ClassBody {
if (psiClass.getPrimaryConstructor() == null && psiClass.getConstructors().size > 1) { if (psiClass.getPrimaryConstructor() == null && psiClass.getConstructors().size > 1) {
return generateArtificialPrimaryConstructor(psiClass.declarationIdentifier(), classBody) return generateArtificialPrimaryConstructor(psiClass.getName()!!, classBody)
} }
else { else {
correctFactoryFunctions(classBody, psiClass.getName()!!) replaceConstructorCallsInFactoryFunctions(classBody, psiClass.getName()!!)
return classBody return classBody
} }
} }
private fun generateArtificialPrimaryConstructor(className: Identifier, classBody: ClassBody): ClassBody { private fun generateArtificialPrimaryConstructor(className: String, classBody: ClassBody): ClassBody {
assert(classBody.primaryConstructorSignature == null) assert(classBody.primaryConstructorSignature == null)
val fieldsToInitialize = classBody.members.filterIsInstance(javaClass<Field>()).filter { it.isVal } val fieldsToInitialize = classBody.members.filterIsInstance(javaClass<Field>()).filter { it.isVal }
val initializers = HashMap<Field, Expression?>()
for (factoryFunction in classBody.factoryFunctions()) { for (factoryFunction in classBody.factoryFunctions()) {
val body = factoryFunction.body!!
// 2 cases: secondary constructor either calls another constructor or does not call any
val newStatements = replaceConstructorCallInFactoryFunction(body, className) ?:
insertCallToArtificialPrimary(body, className, fieldsToInitialize)
factoryFunction.body = Block(newStatements, LBrace().assignNoPrototype(), RBrace().assignNoPrototype()).assignNoPrototype()
}
val parameters = fieldsToInitialize.map { field ->
val varValModifier = if (field.isVal) Parameter.VarValModifier.Val else Parameter.VarValModifier.Var
Parameter(field.identifier, field.`type`, varValModifier, field.annotations, field.modifiers.filter { it in ACCESS_MODIFIERS }).assignPrototypesFrom(field)
}
val modifiers = Modifiers(listOf(Modifier.PRIVATE)).assignNoPrototype()
val parameterList = ParameterList(parameters).assignNoPrototype()
val constructorSignature = PrimaryConstructorSignature(modifiers, parameterList).assignNoPrototype()
val updatedMembers = classBody.members.filter { !fieldsToInitialize.contains(it) }
return ClassBody(constructorSignature, updatedMembers, classBody.classObjectMembers, classBody.lBrace, classBody.rBrace)
}
private fun replaceConstructorCallsInFactoryFunctions(classBody: ClassBody, className: String) {
for (factoryFunction in classBody.factoryFunctions()) {
val body = factoryFunction.body!!
val statements = replaceConstructorCallInFactoryFunction(body, className)
if (statements != null) {
factoryFunction.body = Block(statements, body.lBrace, body.rBrace).assignPrototypesFrom(body)
}
}
}
private fun replaceConstructorCallInFactoryFunction(body: Block, className: String): List<Statement>? {
val statements = ArrayList(body.statements)
// searching for other constructor call in form "this(...)"
// it's not necessary the first statement because of statements inserted for writable parameters
for (i in statements.indices) {
val statement = statements[i]
if (statement is MethodCallExpression) {
if ((statement.methodExpression as? Identifier)?.name == "this") {
val constructorCall = MethodCallExpression.buildNotNull(null, className, statement.arguments).assignPrototypesFrom(statement)
if (i == statements.lastIndex) { // constructor call is the last statement - no intermediate variable needed
statements[i] = ReturnStatement(constructorCall).assignNoPrototype()
return statements
}
val localVar = LocalVariable(tempValIdentifier(), Annotations.Empty, Modifiers.Empty, null, constructorCall, true).assignNoPrototype()
statements[i] = DeclarationStatement(listOf(localVar)).assignNoPrototype()
statements.add(ReturnStatement(tempValIdentifier()).assignNoPrototype())
return statements
}
}
}
return null
}
private fun insertCallToArtificialPrimary(body: Block, className: String, fieldsToInitialize: Collection<Field>): List<Statement> {
val initializers = HashMap<Field, Expression?>()
for (field in fieldsToInitialize) { for (field in fieldsToInitialize) {
initializers.put(field, getDefaultInitializer(field)) initializers.put(field, getDefaultInitializer(field))
} }
val statements = ArrayList<Statement>() val statements = ArrayList<Statement>()
for (statement in factoryFunction.body!!.statements) { for (statement in body.statements) {
var keepStatement = true var keepStatement = true
if (statement is AssignmentExpression) { if (statement is AssignmentExpression) {
val assignee = statement.left val assignee = statement.left
@@ -206,7 +261,7 @@ class ConstructorConverter(private val converter: Converter) {
} }
val arguments = fieldsToInitialize.map { initializers[it] ?: LiteralExpression("null").assignNoPrototype() } val arguments = fieldsToInitialize.map { initializers[it] ?: LiteralExpression("null").assignNoPrototype() }
val initializer = MethodCallExpression.buildNotNull(null, className.name, arguments).assignNoPrototype() val initializer = MethodCallExpression.buildNotNull(null, className, arguments).assignNoPrototype()
if (statements.isNotEmpty()) { if (statements.isNotEmpty()) {
val localVar = LocalVariable(tempValIdentifier(), val localVar = LocalVariable(tempValIdentifier(),
Annotations.Empty, Annotations.Empty,
@@ -220,52 +275,6 @@ class ConstructorConverter(private val converter: Converter) {
else { else {
statements.add(ReturnStatement(initializer).assignNoPrototype()) statements.add(ReturnStatement(initializer).assignNoPrototype())
} }
factoryFunction.body = Block(statements, LBrace().assignNoPrototype(), RBrace().assignNoPrototype()).assignNoPrototype()
}
val parameters = fieldsToInitialize.map { field ->
val varValModifier = if (field.isVal) Parameter.VarValModifier.Val else Parameter.VarValModifier.Var
Parameter(field.identifier, field.`type`, varValModifier, field.annotations, field.modifiers.filter { it in ACCESS_MODIFIERS }).assignPrototypesFrom(field)
}
val modifiers = Modifiers(listOf(Modifier.PRIVATE)).assignNoPrototype()
val parameterList = ParameterList(parameters).assignNoPrototype()
val constructorSignature = PrimaryConstructorSignature(modifiers, parameterList).assignNoPrototype()
val updatedMembers = classBody.members.filter { !fieldsToInitialize.contains(it) }
return ClassBody(constructorSignature, updatedMembers, classBody.classObjectMembers, classBody.lBrace, classBody.rBrace)
}
private fun correctFactoryFunctions(classBody: ClassBody, className: String) {
for (factoryFunction in classBody.factoryFunctions()) {
val body = factoryFunction.body!!
val statements = correctFactoryFunctionStatements(body, className)
factoryFunction.body = Block(statements, body.lBrace, body.rBrace).assignPrototypesFrom(body)
}
}
private fun correctFactoryFunctionStatements(body: Block, className: String): List<Statement> {
val statements = ArrayList(body.statements)
// searching for other constructor call in form "this(...)"
// it's not necessary the first statement because of statements inserted for writable parameters
for (i in statements.indices) {
val statement = statements[i]
if (statement is MethodCallExpression) {
if ((statement.methodExpression as? Identifier)?.name == "this") {
val constructorCall = MethodCallExpression.buildNotNull(null, className, statement.arguments).assignPrototypesFrom(statement)
if (i == statements.lastIndex) { // constructor call is the last statement - no intermediate variable needed
statements[i] = ReturnStatement(constructorCall).assignNoPrototype()
return statements
}
val localVar = LocalVariable(tempValIdentifier(), Annotations.Empty, Modifiers.Empty, null, constructorCall, true).assignNoPrototype()
statements[i] = DeclarationStatement(listOf(localVar)).assignNoPrototype()
break
}
}
}
statements.add(ReturnStatement(tempValIdentifier()).assignNoPrototype())
return statements return statements
} }
@@ -17,7 +17,7 @@
package org.jetbrains.jet.j2k package org.jetbrains.jet.j2k
import com.intellij.psi.* import com.intellij.psi.*
import java.util.LinkedHashSet import java.util.HashMap
fun PsiMethod.isPrimaryConstructor(): Boolean { fun PsiMethod.isPrimaryConstructor(): Boolean {
if (!isConstructor()) return false if (!isConstructor()) return false
@@ -28,33 +28,39 @@ fun PsiMethod.isPrimaryConstructor(): Boolean {
fun PsiClass.getPrimaryConstructor(): PsiMethod? { fun PsiClass.getPrimaryConstructor(): PsiMethod? {
val constructors = getConstructors() val constructors = getConstructors()
return when (constructors.size) { when (constructors.size) {
0 -> null 0 -> return null
1 -> constructors.single() 1 -> return constructors.single()
else -> { else -> {
// if there is more than one constructor then choose one invoked by all others val toTargetConstructorMap = HashMap<PsiMethod, PsiMethod>()
//TODO: logic is incorrect - there can be a constructor which does not call any other for (constructor in constructors) {
class Visitor() : JavaRecursiveElementVisitor() { val firstStatement = constructor.getBody()?.getStatements()?.firstOrNull()
//TODO: skip all non-constructor members (optimization) val refExpr = ((firstStatement as? PsiExpressionStatement)
private val invokedConstructors = LinkedHashSet<PsiMethod>() ?.getExpression() as? PsiMethodCallExpression)
?.getMethodExpression()
override fun visitReferenceExpression(expression: PsiReferenceExpression) { if (refExpr != null && refExpr.getCanonicalText() == "this") {
expression.getReferences() val target = refExpr.resolve() as? PsiMethod
.filter { it.getCanonicalText() == "this" } if (target != null && target.isConstructor()) {
.map { it.resolve() } val finalTarget = toTargetConstructorMap[target] ?: target!!/*TODO: see KT-5335*/
.filterIsInstance(javaClass<PsiMethod>()) toTargetConstructorMap[constructor] = finalTarget
.filterTo(invokedConstructors) { it.isConstructor() } for (entry in toTargetConstructorMap.entrySet()) {
if (entry.getValue() == constructor) {
entry.setValue(finalTarget)
}
}
}
}
} }
val primaryConstructor: PsiMethod? val candidates = constructors.filter { it !in toTargetConstructorMap }
get() = if (invokedConstructors.size == 1) invokedConstructors.single() else null if (candidates.size != 1) return null // there should be only one constructor which does not call other constructor
} val candidate = candidates.single()
return if (toTargetConstructorMap.values().all { it == candidate } /* all other constructors call our candidate (directly or indirectly)*/)
val visitor = Visitor() candidate
accept(visitor) else
visitor.primaryConstructor null
} }
} }
} }
@@ -82,7 +88,4 @@ fun PsiMethodCallExpression.isSuperConstructorCall(): Boolean {
return false return false
} }
fun PsiReferenceExpression.isThisConstructorCall(): Boolean
= getReferences().filter { it.getCanonicalText() == "this" }.map { it.resolve() }.any { it is PsiMethod && it.isConstructor() }
fun PsiElement.isConstructor(): Boolean = this is PsiMethod && this.isConstructor() fun PsiElement.isConstructor(): Boolean = this is PsiMethod && this.isConstructor()
@@ -267,7 +267,7 @@ open class ExpressionVisitor(private val converter: Converter,
var arguments = expression.getArgumentList()?.getExpressions() ?: array() var arguments = expression.getArgumentList()?.getExpressions() ?: array()
val constructor = expression.resolveMethod() val constructor = expression.resolveMethod()
if (constructor != null && !constructor.isPrimaryConstructor() && converter.conversionScope.contains(constructor)) { if (constructor != null && converter.conversionScope.contains(constructor) && !constructor.isPrimaryConstructor()) {
//TODO: handle anonymous class! //TODO: handle anonymous class!
// non-primary constructor converted to factory method in class object // non-primary constructor converted to factory method in class object
val reference = expression.getClassReference() val reference = expression.getClassReference()
@@ -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); JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), "org.jetbrains.jet.generators.tests.TestsPackage", new File("j2k/tests/testData/ast/constructors"), Pattern.compile("^(.+)\\.java$"), true);
} }
@TestMetadata("chain.java")
public void testChain() throws Exception {
doTest("j2k/tests/testData/ast/constructors/chain.java");
}
@TestMetadata("customerBuilder.java") @TestMetadata("customerBuilder.java")
public void testCustomerBuilder() throws Exception { public void testCustomerBuilder() throws Exception {
doTest("j2k/tests/testData/ast/constructors/customerBuilder.java"); doTest("j2k/tests/testData/ast/constructors/customerBuilder.java");
@@ -866,6 +871,11 @@ public class JavaToKotlinConverterTestGenerated extends AbstractJavaToKotlinConv
doTest("j2k/tests/testData/ast/constructors/methodCallInFactoryFun.java"); doTest("j2k/tests/testData/ast/constructors/methodCallInFactoryFun.java");
} }
@TestMetadata("noPrimary.java")
public void testNoPrimary() throws Exception {
doTest("j2k/tests/testData/ast/constructors/noPrimary.java");
}
@TestMetadata("parameterModification.java") @TestMetadata("parameterModification.java")
public void testParameterModification() throws Exception { public void testParameterModification() throws Exception {
doTest("j2k/tests/testData/ast/constructors/parameterModification.java"); doTest("j2k/tests/testData/ast/constructors/parameterModification.java");
@@ -0,0 +1,23 @@
//file
class C {
C(int arg1, int arg2, int arg3) {
}
C(int arg1, int arg2) {
this(arg1, arg2, 0);
System.out.println();
}
C(int arg1) {
this(arg1, 0);
System.out.println();
}
}
public class User {
public static void main() {
C c1 = new C(1, 2, 3);
C c2 = new C(5, 6);
C c3 = new C(7);
}
}
@@ -0,0 +1,26 @@
class C(arg1: Int, arg2: Int, arg3: Int) {
class object {
fun create(arg1: Int, arg2: Int): C {
val __ = C(arg1, arg2, 0)
System.out.println()
return __
}
fun create(arg1: Int): C {
val __ = C(arg1, 0)
System.out.println()
return __
}
}
}
public class User {
class object {
public fun main() {
val c1 = C(1, 2, 3)
val c2 = C.create(5, 6)
val c3 = C.create(7)
}
}
}
@@ -0,0 +1,22 @@
//file
class C {
C(int arg1, int arg2, int arg3) {
}
C(int arg1, int arg2) {
this(arg1, arg2, 0);
System.out.println();
}
C(int arg) {
System.out.println(arg);
}
}
public class User {
public static void main() {
C c1 = new C(1, 2, 3);
C c2 = new C(5, 6);
C c3 = new C(7);
}
}
@@ -0,0 +1,29 @@
class C private() {
class object {
fun create(arg1: Int, arg2: Int, arg3: Int): C {
return C()
}
fun create(arg1: Int, arg2: Int): C {
val __ = C(arg1, arg2, 0)
System.out.println()
return __
}
fun create(arg: Int): C {
val __ = C()
System.out.println(arg)
return __
}
}
}
public class User {
class object {
public fun main() {
val c1 = C.create(1, 2, 3)
val c2 = C.create(5, 6)
val c3 = C.create(7)
}
}
}