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:
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user