diff --git a/j2k/src/org/jetbrains/jet/j2k/ConstructorConverter.kt b/j2k/src/org/jetbrains/jet/j2k/ConstructorConverter.kt index 0180f9e6606..acba0c87286 100644 --- a/j2k/src/org/jetbrains/jet/j2k/ConstructorConverter.kt +++ b/j2k/src/org/jetbrains/jet/j2k/ConstructorConverter.kt @@ -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() - private val lastParamDefaults = ArrayList() // defaults for a few last parameters of primary constructor in reverse order + private val lastParamDefaults = ArrayList() // 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?) + val parameterDefaults: List?) private fun choosePrimaryConstructor(): PsiMethod? { val toTargetConstructorMap = HashMap() @@ -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? { + private fun calcTargetParameterDefaults(constructor: PsiMethod, target: PsiMethod, targetCall: PsiMethodCallExpression): List? { 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(args.size - parameters.size) + val result = ArrayList(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 = 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, 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, postProcessBody: (Block) -> Block): PrimaryConstructor { - val params = constructor.getParameterList().getParameters() + val params = primaryConstructor!!.getParameterList().getParameters() val parameterToField = HashMap>() - val body = constructor.getBody() + val body = primaryConstructor.getBody() + val usageReplacementMap = HashMap() val block = if (body != null) { val statementsToRemove = HashSet() - val usageReplacementMap = HashMap() 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? { diff --git a/j2k/src/org/jetbrains/jet/j2k/Converter.kt b/j2k/src/org/jetbrains/jet/j2k/Converter.kt index b7b285e2e76..a54583f3eb9 100644 --- a/j2k/src/org/jetbrains/jet/j2k/Converter.kt +++ b/j2k/src/org/jetbrains/jet/j2k/Converter.kt @@ -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) } diff --git a/j2k/tests/test/org/jetbrains/jet/j2k/test/JavaToKotlinConverterTestGenerated.java b/j2k/tests/test/org/jetbrains/jet/j2k/test/JavaToKotlinConverterTestGenerated.java index 6e64d30c34a..266b3920c5c 100644 --- a/j2k/tests/test/org/jetbrains/jet/j2k/test/JavaToKotlinConverterTestGenerated.java +++ b/j2k/tests/test/org/jetbrains/jet/j2k/test/JavaToKotlinConverterTestGenerated.java @@ -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"); diff --git a/j2k/tests/testData/ast/constructors/fieldsInitializedFromParams8.java b/j2k/tests/testData/ast/constructors/fieldsInitializedFromParams8.java new file mode 100644 index 00000000000..819d2a4ec82 --- /dev/null +++ b/j2k/tests/testData/ast/constructors/fieldsInitializedFromParams8.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; + } +} diff --git a/j2k/tests/testData/ast/constructors/fieldsInitializedFromParams8.kt b/j2k/tests/testData/ast/constructors/fieldsInitializedFromParams8.kt new file mode 100644 index 00000000000..718f8e5127d --- /dev/null +++ b/j2k/tests/testData/ast/constructors/fieldsInitializedFromParams8.kt @@ -0,0 +1,3 @@ +class Base(o: Any, l: Int) + +class C(private val string: String) : Base(string, string.length()) \ No newline at end of file diff --git a/j2k/tests/testData/ast/constructors/fieldsInitializedFromParams9.java b/j2k/tests/testData/ast/constructors/fieldsInitializedFromParams9.java new file mode 100644 index 00000000000..11674dd0814 --- /dev/null +++ b/j2k/tests/testData/ast/constructors/fieldsInitializedFromParams9.java @@ -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()); + } +} diff --git a/j2k/tests/testData/ast/constructors/fieldsInitializedFromParams9.kt b/j2k/tests/testData/ast/constructors/fieldsInitializedFromParams9.kt new file mode 100644 index 00000000000..136d947610d --- /dev/null +++ b/j2k/tests/testData/ast/constructors/fieldsInitializedFromParams9.kt @@ -0,0 +1 @@ +class C(private val string: String, a: Int = string.length()) \ No newline at end of file