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
@@ -28,10 +28,10 @@ class ConstructorConverter(private val converter: Converter) {
private val typeConverter = converter.typeConverter
public fun convertConstructor(constructor: PsiMethod,
annotations: Annotations,
modifiers: Modifiers,
membersToRemove: MutableSet<PsiMember>,
postProcessBody: (Block) -> Block): Member {
annotations: Annotations,
modifiers: Modifiers,
membersToRemove: MutableSet<PsiMember>,
postProcessBody: (Block) -> Block): Member {
if (constructor.isPrimaryConstructor()) {
return convertPrimaryConstructor(constructor, annotations, modifiers, membersToRemove, postProcessBody)
}
@@ -164,64 +164,24 @@ class ConstructorConverter(private val converter: Converter) {
public fun postProcessConstructors(classBody: ClassBody, psiClass: PsiClass): ClassBody {
if (psiClass.getPrimaryConstructor() == null && psiClass.getConstructors().size > 1) {
return generateArtificialPrimaryConstructor(psiClass.declarationIdentifier(), classBody)
return generateArtificialPrimaryConstructor(psiClass.getName()!!, classBody)
}
else {
correctFactoryFunctions(classBody, psiClass.getName()!!)
replaceConstructorCallsInFactoryFunctions(classBody, psiClass.getName()!!)
return classBody
}
}
private fun generateArtificialPrimaryConstructor(className: Identifier, classBody: ClassBody): ClassBody {
private fun generateArtificialPrimaryConstructor(className: String, classBody: ClassBody): ClassBody {
assert(classBody.primaryConstructorSignature == null)
val fieldsToInitialize = classBody.members.filterIsInstance(javaClass<Field>()).filter { it.isVal }
val initializers = HashMap<Field, Expression?>()
for (factoryFunction in classBody.factoryFunctions()) {
for (field in fieldsToInitialize) {
initializers.put(field, getDefaultInitializer(field))
}
val statements = ArrayList<Statement>()
for (statement in factoryFunction.body!!.statements) {
var keepStatement = true
if (statement is AssignmentExpression) {
val assignee = statement.left
if (assignee is QualifiedExpression && (assignee.qualifier as? Identifier)?.name == tempValName) {
val name = (assignee.identifier as Identifier).name
for (field in fieldsToInitialize) {
if (name == field.identifier.name) {
initializers.put(field, statement.right)
keepStatement = false
}
}
}
}
if (keepStatement) {
statements.add(statement)
}
}
val arguments = fieldsToInitialize.map { initializers[it] ?: LiteralExpression("null").assignNoPrototype() }
val initializer = MethodCallExpression.buildNotNull(null, className.name, arguments).assignNoPrototype()
if (statements.isNotEmpty()) {
val localVar = LocalVariable(tempValIdentifier(),
Annotations.Empty,
Modifiers.Empty,
null,
initializer,
true).assignNoPrototype()
statements.add(0, DeclarationStatement(listOf(localVar)).assignNoPrototype())
statements.add(ReturnStatement(tempValIdentifier()).assignNoPrototype())
}
else {
statements.add(ReturnStatement(initializer).assignNoPrototype())
}
factoryFunction.body = Block(statements, LBrace().assignNoPrototype(), RBrace().assignNoPrototype()).assignNoPrototype()
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 ->
@@ -236,15 +196,17 @@ class ConstructorConverter(private val converter: Converter) {
return ClassBody(constructorSignature, updatedMembers, classBody.classObjectMembers, classBody.lBrace, classBody.rBrace)
}
private fun correctFactoryFunctions(classBody: ClassBody, className: String) {
private fun replaceConstructorCallsInFactoryFunctions(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)
val statements = replaceConstructorCallInFactoryFunction(body, className)
if (statements != null) {
factoryFunction.body = Block(statements, body.lBrace, body.rBrace).assignPrototypesFrom(body)
}
}
}
private fun correctFactoryFunctionStatements(body: Block, className: String): List<Statement> {
private fun replaceConstructorCallInFactoryFunction(body: Block, className: String): List<Statement>? {
val statements = ArrayList(body.statements)
// searching for other constructor call in form "this(...)"
@@ -260,12 +222,59 @@ class ConstructorConverter(private val converter: Converter) {
}
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
}
}
}
statements.add(ReturnStatement(tempValIdentifier()).assignNoPrototype())
return null
}
private fun insertCallToArtificialPrimary(body: Block, className: String, fieldsToInitialize: Collection<Field>): List<Statement> {
val initializers = HashMap<Field, Expression?>()
for (field in fieldsToInitialize) {
initializers.put(field, getDefaultInitializer(field))
}
val statements = ArrayList<Statement>()
for (statement in body.statements) {
var keepStatement = true
if (statement is AssignmentExpression) {
val assignee = statement.left
if (assignee is QualifiedExpression && (assignee.qualifier as? Identifier)?.name == tempValName) {
val name = (assignee.identifier as Identifier).name
for (field in fieldsToInitialize) {
if (name == field.identifier.name) {
initializers.put(field, statement.right)
keepStatement = false
}
}
}
}
if (keepStatement) {
statements.add(statement)
}
}
val arguments = fieldsToInitialize.map { initializers[it] ?: LiteralExpression("null").assignNoPrototype() }
val initializer = MethodCallExpression.buildNotNull(null, className, arguments).assignNoPrototype()
if (statements.isNotEmpty()) {
val localVar = LocalVariable(tempValIdentifier(),
Annotations.Empty,
Modifiers.Empty,
null,
initializer,
true).assignNoPrototype()
statements.add(0, DeclarationStatement(listOf(localVar)).assignNoPrototype())
statements.add(ReturnStatement(tempValIdentifier()).assignNoPrototype())
}
else {
statements.add(ReturnStatement(initializer).assignNoPrototype())
}
return statements
}
@@ -17,7 +17,7 @@
package org.jetbrains.jet.j2k
import com.intellij.psi.*
import java.util.LinkedHashSet
import java.util.HashMap
fun PsiMethod.isPrimaryConstructor(): Boolean {
if (!isConstructor()) return false
@@ -28,33 +28,39 @@ fun PsiMethod.isPrimaryConstructor(): Boolean {
fun PsiClass.getPrimaryConstructor(): PsiMethod? {
val constructors = getConstructors()
return when (constructors.size) {
0 -> null
when (constructors.size) {
0 -> return null
1 -> constructors.single()
1 -> return constructors.single()
else -> {
// if there is more than one constructor then choose one invoked by all others
//TODO: logic is incorrect - there can be a constructor which does not call any other
class Visitor() : JavaRecursiveElementVisitor() {
//TODO: skip all non-constructor members (optimization)
private val invokedConstructors = LinkedHashSet<PsiMethod>()
override fun visitReferenceExpression(expression: PsiReferenceExpression) {
expression.getReferences()
.filter { it.getCanonicalText() == "this" }
.map { it.resolve() }
.filterIsInstance(javaClass<PsiMethod>())
.filterTo(invokedConstructors) { it.isConstructor() }
val toTargetConstructorMap = HashMap<PsiMethod, PsiMethod>()
for (constructor in constructors) {
val firstStatement = constructor.getBody()?.getStatements()?.firstOrNull()
val refExpr = ((firstStatement as? PsiExpressionStatement)
?.getExpression() as? PsiMethodCallExpression)
?.getMethodExpression()
if (refExpr != null && refExpr.getCanonicalText() == "this") {
val target = refExpr.resolve() as? PsiMethod
if (target != null && target.isConstructor()) {
val finalTarget = toTargetConstructorMap[target] ?: target!!/*TODO: see KT-5335*/
toTargetConstructorMap[constructor] = finalTarget
for (entry in toTargetConstructorMap.entrySet()) {
if (entry.getValue() == constructor) {
entry.setValue(finalTarget)
}
}
}
}
val primaryConstructor: PsiMethod?
get() = if (invokedConstructors.size == 1) invokedConstructors.single() else null
}
val visitor = Visitor()
accept(visitor)
visitor.primaryConstructor
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 candidate = candidates.single()
return if (toTargetConstructorMap.values().all { it == candidate } /* all other constructors call our candidate (directly or indirectly)*/)
candidate
else
null
}
}
}
@@ -82,7 +88,4 @@ fun PsiMethodCallExpression.isSuperConstructorCall(): Boolean {
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()
@@ -267,7 +267,7 @@ open class ExpressionVisitor(private val converter: Converter,
var arguments = expression.getArgumentList()?.getExpressions() ?: array()
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!
// non-primary constructor converted to factory method in class object
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);
}
@TestMetadata("chain.java")
public void testChain() throws Exception {
doTest("j2k/tests/testData/ast/constructors/chain.java");
}
@TestMetadata("customerBuilder.java")
public void testCustomerBuilder() throws Exception {
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");
}
@TestMetadata("noPrimary.java")
public void testNoPrimary() throws Exception {
doTest("j2k/tests/testData/ast/constructors/noPrimary.java");
}
@TestMetadata("parameterModification.java")
public void testParameterModification() throws Exception {
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)
}
}
}