Java to Kotlin converter: fixed two similar bugs

This commit is contained in:
Valentin Kipyatkov
2014-06-27 20:25:20 +04:00
parent 806cc1e434
commit f8261a8b6e
7 changed files with 88 additions and 29 deletions
@@ -33,7 +33,7 @@ class ConstructorConverter(private val psiClass: PsiClass, private val converter
private val className = psiClass.getName()!! private val className = psiClass.getName()!!
private val constructors = psiClass.getConstructors() private val constructors = psiClass.getConstructors()
private val constructorsToDrop = HashSet<PsiMethod>() private val constructorsToDrop = HashSet<PsiMethod>()
private val lastParamDefaults = ArrayList<Expression>() // defaults for a few last parameters of primary constructor in reverse order private val lastParamDefaults = ArrayList<PsiExpression>() // defaults for a few last parameters of primary constructor in reverse order
private val primaryConstructor: PsiMethod? = when (constructors.size) { private val primaryConstructor: PsiMethod? = when (constructors.size) {
0 -> null 0 -> null
1 -> constructors.single() 1 -> constructors.single()
@@ -48,7 +48,7 @@ class ConstructorConverter(private val psiClass: PsiClass, private val converter
/** /**
* Is not null if this constructor is equivalent to the target constructor with a few last parameters having default values * Is not null if this constructor is equivalent to the target constructor with a few last parameters having default values
*/ */
val parameterDefaults: List<Expression>?) val parameterDefaults: List<PsiExpression>?)
private fun choosePrimaryConstructor(): PsiMethod? { private fun choosePrimaryConstructor(): PsiMethod? {
val toTargetConstructorMap = HashMap<PsiMethod, TargetConstructorInfo>() val toTargetConstructorMap = HashMap<PsiMethod, TargetConstructorInfo>()
@@ -96,7 +96,7 @@ class ConstructorConverter(private val psiClass: PsiClass, private val converter
return primary return primary
} }
private fun calcTargetParameterDefaults(constructor: PsiMethod, target: PsiMethod, targetCall: PsiMethodCallExpression): List<Expression>? { private fun calcTargetParameterDefaults(constructor: PsiMethod, target: PsiMethod, targetCall: PsiMethodCallExpression): List<PsiExpression>? {
if (constructor.getBody()!!.getStatements().size != 1) return null // constructor's body should consist of only "this(...)" if (constructor.getBody()!!.getStatements().size != 1) return null // constructor's body should consist of only "this(...)"
val parameters = constructor.getParameterList().getParameters() val parameters = constructor.getParameterList().getParameters()
val targetParameters = target.getParameterList().getParameters() val targetParameters = target.getParameterList().getParameters()
@@ -113,9 +113,9 @@ class ConstructorConverter(private val psiClass: PsiClass, private val converter
if (arg.resolve() != parameter) return null if (arg.resolve() != parameter) return null
} }
val result = ArrayList<Expression>(args.size - parameters.size) val result = ArrayList<PsiExpression>(args.size - parameters.size)
for (i in (parameters.size..args.size-1)) { for (i in (parameters.size..args.size-1)) {
result.add(converter.convertExpression(args[i])) result.add(args[i])
} }
return result return result
} }
@@ -139,7 +139,7 @@ class ConstructorConverter(private val psiClass: PsiClass, private val converter
for (i in (0..defaults.size-1)) { for (i in (0..defaults.size-1)) {
val default = defaults[defaults.size - i - 1] val default = defaults[defaults.size - i - 1]
if (i < lastParamDefaults.size) { // default for this parameter has already been assigned if (i < lastParamDefaults.size) { // default for this parameter has already been assigned
if (lastParamDefaults[i].canonicalCode() != default.canonicalCode()) continue@DropCandidatesLoop if (lastParamDefaults[i].getText() != default.getText()) continue@DropCandidatesLoop
} }
else { else {
lastParamDefaults.add(default) lastParamDefaults.add(default)
@@ -150,17 +150,8 @@ class ConstructorConverter(private val psiClass: PsiClass, private val converter
} }
} }
public fun baseClassParams(): PsiExpressionList? { public var baseClassParams: List<Expression> = listOf()
if (primaryConstructor == null) return null private set
val statement = primaryConstructor.getBody()?.getStatements()?.firstOrNull()
val methodCall = (statement as? PsiExpressionStatement)?.getExpression() as? PsiMethodCallExpression
if (methodCall != null && methodCall.isSuperConstructorCall()) {
return methodCall.getArgumentList()
}
else {
return null
}
}
public fun convertConstructor(constructor: PsiMethod, public fun convertConstructor(constructor: PsiMethod,
annotations: Annotations, annotations: Annotations,
@@ -168,7 +159,7 @@ class ConstructorConverter(private val psiClass: PsiClass, private val converter
membersToRemove: MutableSet<PsiMember>, membersToRemove: MutableSet<PsiMember>,
postProcessBody: (Block) -> Block): Member? { postProcessBody: (Block) -> Block): Member? {
if (constructor == primaryConstructor) { if (constructor == primaryConstructor) {
return convertPrimaryConstructor(constructor, annotations, modifiers, membersToRemove, postProcessBody) return convertPrimaryConstructor(annotations, modifiers, membersToRemove, postProcessBody)
} }
else { else {
if (constructor in constructorsToDrop) return null if (constructor in constructorsToDrop) return null
@@ -203,19 +194,18 @@ class ConstructorConverter(private val psiClass: PsiClass, private val converter
} }
} }
private fun convertPrimaryConstructor(constructor: PsiMethod, private fun convertPrimaryConstructor(annotations: Annotations,
annotations: Annotations,
modifiers: Modifiers, modifiers: Modifiers,
membersToRemove: MutableSet<PsiMember>, membersToRemove: MutableSet<PsiMember>,
postProcessBody: (Block) -> Block): PrimaryConstructor { postProcessBody: (Block) -> Block): PrimaryConstructor {
val params = constructor.getParameterList().getParameters() val params = primaryConstructor!!.getParameterList().getParameters()
val parameterToField = HashMap<PsiParameter, Pair<PsiField, Type>>() val parameterToField = HashMap<PsiParameter, Pair<PsiField, Type>>()
val body = constructor.getBody() val body = primaryConstructor.getBody()
val usageReplacementMap = HashMap<PsiVariable, String>()
val block = if (body != null) { val block = if (body != null) {
val statementsToRemove = HashSet<PsiStatement>() val statementsToRemove = HashSet<PsiStatement>()
val usageReplacementMap = HashMap<PsiVariable, String>()
for (parameter in params) { for (parameter in params) {
val (field, initializationStatement) = findBackingFieldForConstructorParameter(parameter, constructor) ?: continue val (field, initializationStatement) = findBackingFieldForConstructorParameter(parameter, primaryConstructor) ?: continue
val fieldType = typeConverter.convertVariableType(field) val fieldType = typeConverter.convertVariableType(field)
val parameterType = typeConverter.convertVariableType(parameter) val parameterType = typeConverter.convertVariableType(parameter)
@@ -253,10 +243,41 @@ class ConstructorConverter(private val psiClass: PsiClass, private val converter
Block.Empty Block.Empty
} }
// we need to replace renamed parameter usages in base class constructor arguments and in default values
// we match simply by name because of default parameter values refer to parameters of other constructor
val byNameUsageReplacementMap = usageReplacementMap.map { it.key.getName() to it.value }.toMap()
val correctedConverter = converter.withExpressionVisitor {
object : ExpressionVisitor(it, mapOf()) {
override fun visitReferenceExpression(expression: PsiReferenceExpression) {
if (expression.getQualifier() == null) {
val replacement = byNameUsageReplacementMap[expression.getReferenceName()]
if (replacement != null) {
val target = expression.getReference()?.resolve()
if (target is PsiParameter && target.getDeclarationScope().isConstructor()) {
result = Identifier(replacement, typeConverter.variableNullability(target).isNullable(converter.settings))
return
}
}
}
super.visitReferenceExpression(expression)
}
}
}
val statement = primaryConstructor.getBody()?.getStatements()?.firstOrNull()
val methodCall = (statement as? PsiExpressionStatement)?.getExpression() as? PsiMethodCallExpression
if (methodCall != null && methodCall.isSuperConstructorCall()) {
baseClassParams = correctedConverter.convertExpressions(methodCall.getArgumentList().getExpressions())
}
val parameterList = ParameterList(params.indices.map { i -> val parameterList = ParameterList(params.indices.map { i ->
val parameter = params[i] val parameter = params[i]
val indexFromEnd = params.size - i - 1 val indexFromEnd = params.size - i - 1
val defaultValue = if (indexFromEnd < lastParamDefaults.size) lastParamDefaults[indexFromEnd] else null val defaultValue = if (indexFromEnd < lastParamDefaults.size)
correctedConverter.convertExpression(lastParamDefaults[indexFromEnd], parameter.getType())
else
null
if (!parameterToField.containsKey(parameter)) { if (!parameterToField.containsKey(parameter)) {
converter.convertParameter(parameter, defaultValue = defaultValue) converter.convertParameter(parameter, defaultValue = defaultValue)
} }
@@ -269,8 +290,8 @@ class ConstructorConverter(private val psiClass: PsiClass, private val converter
converter.convertModifiers(field).filter { it in ACCESS_MODIFIERS }, converter.convertModifiers(field).filter { it in ACCESS_MODIFIERS },
defaultValue).assignPrototypes(listOf(parameter, field), CommentsAndSpacesInheritance(blankLinesBefore = false)) defaultValue).assignPrototypes(listOf(parameter, field), CommentsAndSpacesInheritance(blankLinesBefore = false))
} }
}).assignPrototype(constructor.getParameterList()) }).assignPrototype(primaryConstructor.getParameterList())
return PrimaryConstructor(annotations, modifiers, parameterList, block).assignPrototype(constructor) return PrimaryConstructor(annotations, modifiers, parameterList, block).assignPrototype(primaryConstructor)
} }
private fun findBackingFieldForConstructorParameter(parameter: PsiParameter, constructor: PsiMethod): Pair<PsiField, PsiStatement>? { private fun findBackingFieldForConstructorParameter(parameter: PsiParameter, constructor: PsiMethod): Pair<PsiField, PsiStatement>? {
+1 -2
View File
@@ -206,8 +206,7 @@ public class Converter private(val project: Project, val settings: ConverterSett
modifiers = modifiers.with(Modifier.INNER) modifiers = modifiers.with(Modifier.INNER)
} }
val baseClassParams = convertExpressions(constructorConverter.baseClassParams()?.getExpressions() ?: array()) Class(name, annotations, modifiers, typeParameters, extendsTypes, constructorConverter.baseClassParams, implementsTypes, classBody)
Class(name, annotations, modifiers, typeParameters, extendsTypes, baseClassParams, implementsTypes, classBody)
} }
}.assignPrototype(psiClass) }.assignPrototype(psiClass)
} }
@@ -856,6 +856,16 @@ public class JavaToKotlinConverterTestGenerated extends AbstractJavaToKotlinConv
doTest("j2k/tests/testData/ast/constructors/fieldsInitializedFromParams7.java"); doTest("j2k/tests/testData/ast/constructors/fieldsInitializedFromParams7.java");
} }
@TestMetadata("fieldsInitializedFromParams8.java")
public void testFieldsInitializedFromParams8() throws Exception {
doTest("j2k/tests/testData/ast/constructors/fieldsInitializedFromParams8.java");
}
@TestMetadata("fieldsInitializedFromParams9.java")
public void testFieldsInitializedFromParams9() throws Exception {
doTest("j2k/tests/testData/ast/constructors/fieldsInitializedFromParams9.java");
}
@TestMetadata("genericIdentifier.java") @TestMetadata("genericIdentifier.java")
public void testGenericIdentifier() throws Exception { public void testGenericIdentifier() throws Exception {
doTest("j2k/tests/testData/ast/constructors/genericIdentifier.java"); doTest("j2k/tests/testData/ast/constructors/genericIdentifier.java");
@@ -0,0 +1,13 @@
//file
class Base {
Base(Object o, int l){}
}
class C extends Base {
private final String string;
public C(String s) {
super(s, s.length());
this.string = s;
}
}
@@ -0,0 +1,3 @@
class Base(o: Any, l: Int)
class C(private val string: String) : Base(string, string.length())
@@ -0,0 +1,12 @@
//file
class C {
private final String string;
public C(String s, int a) {
this.string = s;
}
public C(String s) {
this(s, s.length());
}
}
@@ -0,0 +1 @@
class C(private val string: String, a: Int = string.length())