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 constructors = psiClass.getConstructors()
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) {
0 -> null
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
*/
val parameterDefaults: List<Expression>?)
val parameterDefaults: List<PsiExpression>?)
private fun choosePrimaryConstructor(): PsiMethod? {
val toTargetConstructorMap = HashMap<PsiMethod, TargetConstructorInfo>()
@@ -96,7 +96,7 @@ class ConstructorConverter(private val psiClass: PsiClass, private val converter
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(...)"
val parameters = constructor.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
}
val result = ArrayList<Expression>(args.size - parameters.size)
val result = ArrayList<PsiExpression>(args.size - parameters.size)
for (i in (parameters.size..args.size-1)) {
result.add(converter.convertExpression(args[i]))
result.add(args[i])
}
return result
}
@@ -139,7 +139,7 @@ class ConstructorConverter(private val psiClass: PsiClass, private val converter
for (i in (0..defaults.size-1)) {
val default = defaults[defaults.size - i - 1]
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 {
lastParamDefaults.add(default)
@@ -150,17 +150,8 @@ class ConstructorConverter(private val psiClass: PsiClass, private val converter
}
}
public fun baseClassParams(): PsiExpressionList? {
if (primaryConstructor == null) return null
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 var baseClassParams: List<Expression> = listOf()
private set
public fun convertConstructor(constructor: PsiMethod,
annotations: Annotations,
@@ -168,7 +159,7 @@ class ConstructorConverter(private val psiClass: PsiClass, private val converter
membersToRemove: MutableSet<PsiMember>,
postProcessBody: (Block) -> Block): Member? {
if (constructor == primaryConstructor) {
return convertPrimaryConstructor(constructor, annotations, modifiers, membersToRemove, postProcessBody)
return convertPrimaryConstructor(annotations, modifiers, membersToRemove, postProcessBody)
}
else {
if (constructor in constructorsToDrop) return null
@@ -203,19 +194,18 @@ class ConstructorConverter(private val psiClass: PsiClass, private val converter
}
}
private fun convertPrimaryConstructor(constructor: PsiMethod,
annotations: Annotations,
private fun convertPrimaryConstructor(annotations: Annotations,
modifiers: Modifiers,
membersToRemove: MutableSet<PsiMember>,
postProcessBody: (Block) -> Block): PrimaryConstructor {
val params = constructor.getParameterList().getParameters()
val params = primaryConstructor!!.getParameterList().getParameters()
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 statementsToRemove = HashSet<PsiStatement>()
val usageReplacementMap = HashMap<PsiVariable, String>()
for (parameter in params) {
val (field, initializationStatement) = findBackingFieldForConstructorParameter(parameter, constructor) ?: continue
val (field, initializationStatement) = findBackingFieldForConstructorParameter(parameter, primaryConstructor) ?: continue
val fieldType = typeConverter.convertVariableType(field)
val parameterType = typeConverter.convertVariableType(parameter)
@@ -253,10 +243,41 @@ class ConstructorConverter(private val psiClass: PsiClass, private val converter
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 parameter = params[i]
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)) {
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 },
defaultValue).assignPrototypes(listOf(parameter, field), CommentsAndSpacesInheritance(blankLinesBefore = false))
}
}).assignPrototype(constructor.getParameterList())
return PrimaryConstructor(annotations, modifiers, parameterList, block).assignPrototype(constructor)
}).assignPrototype(primaryConstructor.getParameterList())
return PrimaryConstructor(annotations, modifiers, parameterList, block).assignPrototype(primaryConstructor)
}
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)
}
val baseClassParams = convertExpressions(constructorConverter.baseClassParams()?.getExpressions() ?: array())
Class(name, annotations, modifiers, typeParameters, extendsTypes, baseClassParams, implementsTypes, classBody)
Class(name, annotations, modifiers, typeParameters, extendsTypes, constructorConverter.baseClassParams, implementsTypes, classBody)
}
}.assignPrototype(psiClass)
}
@@ -856,6 +856,16 @@ public class JavaToKotlinConverterTestGenerated extends AbstractJavaToKotlinConv
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")
public void testGenericIdentifier() throws Exception {
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())