diff --git a/j2k/src/org/jetbrains/kotlin/j2k/ClassBodyConverter.kt b/j2k/src/org/jetbrains/kotlin/j2k/ClassBodyConverter.kt index 9650fd3ba31..47b3b136c9b 100644 --- a/j2k/src/org/jetbrains/kotlin/j2k/ClassBodyConverter.kt +++ b/j2k/src/org/jetbrains/kotlin/j2k/ClassBodyConverter.kt @@ -49,8 +49,12 @@ class ClassBodyConverter(private val psiClass: PsiClass, public fun convertBody(): ClassBody { processAccessorsToDrop() + val overloadReducer = OverloadReducer(psiClass.getMethods().filter { it !in membersToRemove } /* do not allow OverloadReducer to use accessors converted to properties */, + isOpenClass, + converter.referenceSearcher) + val constructorConverter = if (psiClass.getName() != null && !isObject) - ConstructorConverter(psiClass, converter, fieldCorrections) + ConstructorConverter(psiClass, converter, fieldCorrections, overloadReducer) else null @@ -59,8 +63,9 @@ class ClassBodyConverter(private val psiClass: PsiClass, if (element is PsiMember) { if (element is PsiAnnotationMethod) continue // converted in convertAnnotationType() if (isObject && element.isConstructor()) continue // no constructor in object + if (element is PsiMethod && overloadReducer.shouldDropMethod(element)) continue - val converted = converter.convertMember(element, membersToRemove, constructorConverter) + val converted = converter.convertMember(element, membersToRemove, constructorConverter, overloadReducer) if (converted != null) { convertedMembers.put(element, converted) } @@ -88,7 +93,7 @@ class ClassBodyConverter(private val psiClass: PsiClass, } } - return ClassBody(null, null, convertedMembers.values().toList(), emptyList(), lBrace, rBrace) + return ClassBody(null, null, convertedMembers.values().toList(), emptyList(), lBrace, rBrace, overloadReducer) } val useCompanionObject = shouldGenerateCompanionObject(convertedMembers) @@ -123,14 +128,15 @@ class ClassBodyConverter(private val psiClass: PsiClass, primaryConstructorSignature = null // no "()" after class name is needed in this case } - return ClassBody(primaryConstructorSignature, constructorConverter?.baseClassParams, members, companionObjectMembers, lBrace, rBrace) + return ClassBody(primaryConstructorSignature, constructorConverter?.baseClassParams, members, companionObjectMembers, lBrace, rBrace, overloadReducer) } private fun Converter.convertMember(member: PsiMember, membersToRemove: MutableSet, - constructorConverter: ConstructorConverter?): Member? { + constructorConverter: ConstructorConverter?, + overloadReducer: OverloadReducer): Member? { return when (member) { - is PsiMethod -> convertMethod(member, membersToRemove, constructorConverter, isOpenClass) + is PsiMethod -> convertMethod(member, membersToRemove, constructorConverter, overloadReducer, isOpenClass) is PsiField -> convertField(member, fieldCorrections[member]) is PsiClass -> convertClass(member) is PsiClassInitializer -> convertInitializer(member) diff --git a/j2k/src/org/jetbrains/kotlin/j2k/ConstructorConverter.kt b/j2k/src/org/jetbrains/kotlin/j2k/ConstructorConverter.kt index 57addf55f6a..2bba338966d 100644 --- a/j2k/src/org/jetbrains/kotlin/j2k/ConstructorConverter.kt +++ b/j2k/src/org/jetbrains/kotlin/j2k/ConstructorConverter.kt @@ -27,9 +27,10 @@ import java.util.HashSet class ConstructorConverter( private val psiClass: PsiClass, private val converter: Converter, - private val fieldCorrections: Map + private val fieldCorrections: Map, + private val overloadReducer: OverloadReducer ) { - private val constructors = psiClass.getConstructors() + private val constructors = psiClass.getConstructors().asList() private val toTargetConstructorMap = buildToTargetConstructorMap() @@ -43,29 +44,12 @@ class ConstructorConverter( 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 primary = candidates.single() - if (toTargetConstructorMap.values().any() { it.constructor != primary }) return null // all other constructors call our candidate (directly or indirectly) + if (toTargetConstructorMap.values().any() { it != primary }) return null // all other constructors call our candidate (directly or indirectly) return primary } - private class TargetConstructorInfo( - /** - * Target constructor (one which is finally invoked by this one) - */ - val constructor: PsiMethod, - /** - * Is not null if this constructor is equivalent to the target constructor with a few last parameters having default values - */ - val parameterDefaults: List?) - - private val constructorsToDrop = HashSet() - private val lastParamDefaults = HashMap>() // defaults for a few last parameters in reverse order - - init { - dropConstructorsForDefaultValues() - } - - private fun buildToTargetConstructorMap(): Map { - val toTargetConstructorMap = HashMap() + private fun buildToTargetConstructorMap(): Map { + val toTargetConstructorMap = HashMap() for (constructor in constructors) { val firstStatement = constructor.getBody()?.getStatements()?.firstOrNull() val methodCall = (firstStatement as? PsiExpressionStatement)?.getExpression() as? PsiMethodCallExpression @@ -74,25 +58,11 @@ class ConstructorConverter( if (refExpr.getCanonicalText() == "this") { val target = refExpr.resolve() as? PsiMethod if (target != null && target.isConstructor()) { - var parameterDefaults = calcTargetParameterDefaults(constructor, target, methodCall) - - val finalTargetInfo = toTargetConstructorMap[target] - if (finalTargetInfo != null && parameterDefaults != null) { - parameterDefaults = if (finalTargetInfo.parameterDefaults != null) - parameterDefaults!! + finalTargetInfo.parameterDefaults - else - null - } - val finalTarget = finalTargetInfo?.constructor ?: target - - toTargetConstructorMap[constructor] = TargetConstructorInfo(finalTarget, parameterDefaults) + val finalTarget = toTargetConstructorMap[target] ?: target + toTargetConstructorMap[constructor] = finalTarget for (entry in toTargetConstructorMap.entrySet()) { - if (entry.value.constructor == constructor) { - val newParameterDefaults = if (parameterDefaults != null) - entry.value.parameterDefaults?.plus(parameterDefaults!!) - else - null - entry.setValue(TargetConstructorInfo(finalTarget, newParameterDefaults)) + if (entry.value == constructor) { + entry.setValue(finalTarget) } } } @@ -102,61 +72,6 @@ class ConstructorConverter( return toTargetConstructorMap } - 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() - if (parameters.size() >= targetParameters.size()) return null - val args = targetCall.getArgumentList().getExpressions() - if (args.size() != targetParameters.size()) return null // incorrect code - - for (i in parameters.indices) { - val parameter = parameters[i] - val targetParameter = targetParameters[i] - if (parameter.getName() != targetParameter.getName() || parameter.getType() != targetParameter.getType()) return null - val arg = args[i] - if (arg !is PsiReferenceExpression || arg.resolve() != parameter) return null - } - - return args.drop(parameters.size()) - } - - private fun dropConstructorsForDefaultValues() { - val dropCandidates = toTargetConstructorMap - .filter { - it.value.parameterDefaults != null - && it.key.accessModifier() == it.value.constructor.accessModifier() - && it.key.getModifierList().getAnnotations().isEmpty() /* do not drop constructors with annotations */ - } - .map { it.key } - .sortBy { -it.getParameterList().getParametersCount() } // we will try to drop them starting from ones with more parameters - - @DropCandidatesLoop - for (constructor in dropCandidates) { - val paramCount = constructor.getParameterList().getParametersCount() - val targetInfo = toTargetConstructorMap[constructor]!! - val targetConstructor = targetInfo.constructor - val targetParamCount = targetConstructor.getParameterList().getParametersCount() - assert(paramCount < targetParamCount) - val defaults = targetInfo.parameterDefaults!! - assert(defaults.size() == targetParamCount - paramCount) - - val targetDefaults = lastParamDefaults.getOrPut(targetConstructor, { ArrayList() }) - - for (i in defaults.indices) { - val default = defaults[defaults.size() - i - 1] - if (i < targetDefaults.size()) { // default for this parameter has already been assigned - if (targetDefaults[i].getText() != default.getText()) continue@DropCandidatesLoop - } - else { - targetDefaults.add(default) - } - } - - constructorsToDrop.add(constructor) - } - } - public var baseClassParams: List>? = if (constructors.isEmpty()) emptyList() else null private set @@ -169,10 +84,9 @@ class ConstructorConverter( convertPrimaryConstructor(annotations, modifiers, membersToRemove, postProcessBody) } else { - if (constructor in constructorsToDrop) return null + if (overloadReducer.shouldDropMethod(constructor)) return null - val params = convertParameterList(constructor, converter, - { parameter, default -> converter.convertParameter(parameter, defaultValue = default) }) + val params = converter.convertParameterList(constructor, overloadReducer) val thisOrSuper = findThisOrSuperCall(constructor) val thisOrSuperDeferred = if (thisOrSuper != null) @@ -193,14 +107,6 @@ class ConstructorConverter( SecondaryConstructor(annotations, modifiers, params, converter.deferredElement(::convertBody), thisOrSuperDeferred) } - - if (result.parameterList.parameters.any { it.defaultValue != null }) { - result.annotations += Annotations(listOf(Annotation(Identifier("overloads").assignNoPrototype(), - listOf(), - brackets = result is PrimaryConstructor, - newLineAfter = false).assignNoPrototype())).assignNoPrototype() - } - return result } @@ -288,53 +194,34 @@ class ConstructorConverter( baseClassParams = emptyList() } - val parameterList = convertParameterList(primaryConstructor, - correctedConverter, - { parameter, default -> - if (!parameterToField.containsKey(parameter)) { - correctedConverter.convertParameter(parameter, defaultValue = default) - } - else { - val (field, type) = parameterToField[parameter]!! - val fieldCorrection = fieldCorrections[field] - val name = fieldCorrection?.identifier ?: field.declarationIdentifier() - val accessModifiers = if (fieldCorrection != null) - Modifiers(listOf()).with(fieldCorrection.access).assignNoPrototype() - else - converter.convertModifiers(field).filter { it in ACCESS_MODIFIERS } - Parameter(name, - type, - if (isVal(converter.referenceSearcher, field)) Parameter.VarValModifier.Val else Parameter.VarValModifier.Var, - converter.convertAnnotations(parameter) + converter.convertAnnotations(field), - accessModifiers, - default).assignPrototypes(listOf(parameter, field), CommentsAndSpacesInheritance(blankLinesBefore = false)) - } - }, - correctCodeConverter = { correct() }) + val parameterList = correctedConverter.convertParameterList( + primaryConstructor, + overloadReducer, + { parameter, default -> + if (!parameterToField.containsKey(parameter)) { + correctedConverter.convertParameter(parameter, defaultValue = default) + } + else { + val (field, type) = parameterToField[parameter]!! + val fieldCorrection = fieldCorrections[field] + val name = fieldCorrection?.identifier ?: field.declarationIdentifier() + val accessModifiers = if (fieldCorrection != null) + Modifiers(listOf()).with(fieldCorrection.access).assignNoPrototype() + else + converter.convertModifiers(field).filter { it in ACCESS_MODIFIERS } + Parameter(name, + type, + if (isVal(converter.referenceSearcher, field)) Parameter.VarValModifier.Val else Parameter.VarValModifier.Var, + converter.convertAnnotations(parameter) + converter.convertAnnotations(field), + accessModifiers, + default).assignPrototypes(listOf(parameter, field), CommentsAndSpacesInheritance(blankLinesBefore = false)) + } + }, + correctCodeConverter = { correct() }) return PrimaryConstructor(annotations, modifiers, parameterList, converter.deferredElement(bodyGenerator)).assignPrototype(primaryConstructor) } - private fun convertParameterList( - constructor: PsiMethod, - converter: Converter, - convertParameter: (parameter: PsiParameter, default: DeferredElement?) -> Parameter, - correctCodeConverter: CodeConverter.() -> CodeConverter = { this } - ): ParameterList { - val parameterList = constructor.getParameterList() - val params = parameterList.getParameters() - val defaults = lastParamDefaults[constructor] ?: emptyList() - return ParameterList(params.indices.map { i -> - val parameter = params[i] - val indexFromEnd = params.size() - i - 1 - val defaultValue = if (indexFromEnd < defaults.size()) - converter.deferredElement { codeConverter -> codeConverter.correctCodeConverter().convertExpression(defaults[indexFromEnd], parameter.getType()) } - else - null - convertParameter(parameter, defaultValue) - }).assignPrototype(parameterList) - } - private fun findBackingFieldForConstructorParameter(parameter: PsiParameter, constructor: PsiMethod): Pair? { val body = constructor.getBody() ?: return null diff --git a/j2k/src/org/jetbrains/kotlin/j2k/Converter.kt b/j2k/src/org/jetbrains/kotlin/j2k/Converter.kt index 6ba6768cf28..6f723c567d0 100644 --- a/j2k/src/org/jetbrains/kotlin/j2k/Converter.kt +++ b/j2k/src/org/jetbrains/kotlin/j2k/Converter.kt @@ -16,7 +16,6 @@ package org.jetbrains.kotlin.j2k -import com.google.common.collect.Multimap import com.intellij.openapi.project.Project import com.intellij.psi.* import com.intellij.psi.CommonClassNames.* @@ -32,6 +31,7 @@ import org.jetbrains.kotlin.j2k.usageProcessing.UsageProcessing import org.jetbrains.kotlin.j2k.usageProcessing.UsageProcessingExpressionConverter import org.jetbrains.kotlin.types.expressions.OperatorConventions.* import java.util.ArrayList +import java.util.HashMap class Converter private( private val elementToConvert: PsiElement, @@ -62,7 +62,9 @@ class Converter private( public fun create(elementToConvert: PsiElement, settings: ConverterSettings, inConversionScope: (PsiElement) -> Boolean, referenceSearcher: ReferenceSearcher, resolverForConverter: ResolverForConverter, usageProcessingsCollector: (UsageProcessing) -> Unit): Converter { - return Converter(elementToConvert, settings, inConversionScope, referenceSearcher, resolverForConverter, CommonState(usageProcessingsCollector), PersonalState(null)) + return Converter(elementToConvert, settings, inConversionScope, + CachingReferenceSearcher(referenceSearcher), + resolverForConverter, CommonState(usageProcessingsCollector), PersonalState(null)) } } @@ -98,7 +100,7 @@ class Converter private( private fun convertTopElement(element: PsiElement): Element? = when (element) { is PsiJavaFile -> convertFile(element) is PsiClass -> convertClass(element) - is PsiMethod -> convertMethod(element, null, null, false) + is PsiMethod -> convertMethod(element, null, null, null, false) is PsiField -> convertField(element, null) is PsiStatement -> createDefaultCodeConverter().convertStatement(element) is PsiExpression -> createDefaultCodeConverter().convertExpression(element) @@ -274,7 +276,8 @@ class Converter private( // to convert fields and nested types - they are not allowed in Kotlin but we convert them and let user refactor code var classBody = ClassBodyConverter(psiClass, this, isOpenClass = false, isObject = false).convertBody() - classBody = ClassBody(constructorSignature, classBody.baseClassParams, classBody.members, classBody.companionObjectMembers, classBody.lBrace, classBody.rBrace) + classBody = ClassBody(constructorSignature, classBody.baseClassParams, classBody.members, + classBody.companionObjectMembers, classBody.lBrace, classBody.rBrace, classBody.overloadReducer) val annotationAnnotation = Annotation(Identifier("annotation").assignNoPrototype(), listOf(), false, false).assignNoPrototype() return Class(psiClass.declarationIdentifier(), @@ -348,10 +351,13 @@ class Converter private( return if (convertedType == initializerType) null else convertedType } - public fun convertMethod(method: PsiMethod, - membersToRemove: MutableSet?, - constructorConverter: ConstructorConverter?, - isInOpenClass: Boolean): Member? { + public fun convertMethod( + method: PsiMethod, + membersToRemove: MutableSet?, + constructorConverter: ConstructorConverter?, + overloadReducer: OverloadReducer?, + isInOpenClass: Boolean + ): FunctionLike? { val returnType = typeConverter.convertMethodReturnType(method) val annotations = convertAnnotations(method) + convertThrows(method) @@ -401,7 +407,8 @@ class Converter private( } } - var params = convertParameterList(method.getParameterList()) + var params = convertParameterList(method, overloadReducer) + val typeParameterList = convertTypeParameterList(method.getTypeParameterList()) var body = deferredElement { codeConverter: CodeConverter -> val body = codeConverter.withMethodReturnType(method.getReturnType()).convertBlock(method.getBody()) @@ -410,7 +417,17 @@ class Converter private( Function(method.declarationIdentifier(), annotations, modifiers, returnType, typeParameterList, params, body, containingClass?.isInterface() ?: false) } - return function?.assignPrototype(method) + if (function == null) return null + + if (function.parameterList.parameters.any { it.defaultValue != null }) { + function.annotations += Annotations( + listOf(Annotation(Identifier("overloads").assignNoPrototype(), + listOf(), + brackets = function is PrimaryConstructor, + newLineAfter = false).assignNoPrototype())).assignNoPrototype() + } + + return function.assignPrototype(method) } /** @@ -507,14 +524,13 @@ class Converter private( private fun convertToNotNullableTypes(types: Array): List = types.map { typeConverter.convertType(it, Nullability.NotNull) } - private fun convertParameterList(parameterList: PsiParameterList): ParameterList - = ParameterList(parameterList.getParameters().map { convertParameter(it) }).assignPrototype(parameterList) - - public fun convertParameter(parameter: PsiParameter, - nullability: Nullability = Nullability.Default, - varValModifier: Parameter.VarValModifier = Parameter.VarValModifier.None, - modifiers: Modifiers = Modifiers.Empty, - defaultValue: DeferredElement? = null): Parameter { + public fun convertParameter( + parameter: PsiParameter, + nullability: Nullability = Nullability.Default, + varValModifier: Parameter.VarValModifier = Parameter.VarValModifier.None, + modifiers: Modifiers = Modifiers.Empty, + defaultValue: DeferredElement? = null + ): Parameter { var type = typeConverter.convertVariableType(parameter) when (nullability) { Nullability.NotNull -> type = type.toNotNullType() @@ -555,6 +571,27 @@ class Converter private( val annotation = Annotation(Identifier("throws").assignNoPrototype(), arguments, false, true) return Annotations(listOf(annotation.assignPrototype(throwsList))).assignPrototype(throwsList) } + + private class CachingReferenceSearcher(private val searcher: ReferenceSearcher) : ReferenceSearcher by searcher { + private val hasInheritorsCached = HashMap() + private val hasOverridesCached = HashMap() + + override fun hasInheritors(`class`: PsiClass): Boolean { + val cached = hasInheritorsCached[`class`] + if (cached != null) return cached + val result = searcher.hasInheritors(`class`) + hasInheritorsCached[`class`] = result + return result + } + + override fun hasOverrides(method: PsiMethod): Boolean { + val cached = hasOverridesCached[method] + if (cached != null) return cached + val result = searcher.hasOverrides(method) + hasOverridesCached[method] = result + return result + } + } } val PRIMITIVE_TYPE_CONVERSIONS: Map = mapOf( diff --git a/j2k/src/org/jetbrains/kotlin/j2k/OverloadReducer.kt b/j2k/src/org/jetbrains/kotlin/j2k/OverloadReducer.kt new file mode 100644 index 00000000000..97a1f9ac88a --- /dev/null +++ b/j2k/src/org/jetbrains/kotlin/j2k/OverloadReducer.kt @@ -0,0 +1,195 @@ +/* + * Copyright 2010-2015 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.j2k + +import com.intellij.psi.* +import org.jetbrains.kotlin.j2k.ast.* +import java.util.ArrayList +import java.util.HashMap +import java.util.HashSet + +class OverloadReducer( + private val methods: Collection, + private val isOpenClass: Boolean, + private val referenceSearcher: ReferenceSearcher +) { + private val methodToLastParameterDefaults = HashMap>() + private val methodsToDrop = HashSet() + + init { + val map = buildOverloadEquivalenceMap() + dropOverloadsForDefaultValues(map) + } + + public fun shouldDropMethod(method: PsiMethod): Boolean = method in methodsToDrop + + public fun parameterDefault(method: PsiMethod, parameterIndex: Int): PsiExpression? { + val defaults = methodToLastParameterDefaults[method] ?: return null + val index = method.getParameterList().getParametersCount() - parameterIndex - 1 + return if (index < defaults.size()) defaults[index] else null + } + + private class EquivalentOverloadInfo( + /** + * Target method (one which is finally invoked by this one) + */ + val method: PsiMethod, + /** + * This method is equivalent to the target method with a few last parameters having default values + */ + val parameterDefaults: List) + + private fun buildOverloadEquivalenceMap(): Map { + val overloadGroups = methods + .groupBy { listOf(if (it.isConstructor()) null else it.getName(), + it.accessModifier(), + it.getReturnType(), + it.hasModifierProperty(PsiModifier.STATIC), + getAnnotationsFingerprint(it)) } + .values() + .filter { it.size() > 1 } + .map { it.filterNot { shouldSkipOverload(it) } } + .filter { it.size() > 1 } + + val map = HashMap() + for (group in overloadGroups) { + for (method in group) { + val overloadInfo = findEquivalentOverload(method, group) ?: continue + + val furtherOverloadInfo = map[overloadInfo.method] + val resultOverloadInfo = if (furtherOverloadInfo != null) { + EquivalentOverloadInfo(furtherOverloadInfo.method, overloadInfo.parameterDefaults + furtherOverloadInfo.parameterDefaults) + } + else { + overloadInfo + } + + map[method] = resultOverloadInfo + for (entry in map.entrySet()) { + if (entry.value.method == method) { + val newParameterDefaults = entry.value.parameterDefaults + resultOverloadInfo.parameterDefaults + entry.setValue(EquivalentOverloadInfo(resultOverloadInfo.method, newParameterDefaults)) + } + } + + } + } + + return map + } + + private fun shouldSkipOverload(method: PsiMethod): Boolean { + if (method.isConstructor()) return false + if (method.hasModifierProperty(PsiModifier.ABSTRACT)) return true + if (method.hasModifierProperty(PsiModifier.NATIVE)) return true + if (method.hasModifierProperty(PsiModifier.SYNCHRONIZED)) return true + if (method.getHierarchicalMethodSignature().getSuperSignatures().isNotEmpty()) return true + if (isOpenClass && referenceSearcher.hasOverrides(method)) return true + return false + } + + private fun findEquivalentOverload(method: PsiMethod, overloads: Collection): EquivalentOverloadInfo? { + val statement = method.getBody()?.getStatements()?.singleOrNull() ?: return null + + val methodCall = when (statement) { + is PsiExpressionStatement -> statement.getExpression() + is PsiReturnStatement -> statement.getReturnValue() + else -> null + } as? PsiMethodCallExpression ?: return null + + val expectedMethodName = if (method.isConstructor()) "this" else method.getName() + val refExpr = methodCall.getMethodExpression() + if (refExpr.isQualified() || refExpr.getReferenceName() != expectedMethodName) return null + + val target = refExpr.resolve() as? PsiMethod ?: return null + if (target !in overloads) return null + + val parameterDefaults = calcTargetParameterDefaults(method, target, methodCall) ?: return null + return EquivalentOverloadInfo(target, parameterDefaults) + } + + private fun calcTargetParameterDefaults(method: PsiMethod, target: PsiMethod, targetCall: PsiMethodCallExpression): List? { + val parameters = method.getParameterList().getParameters() + val targetParameters = target.getParameterList().getParameters() + if (parameters.size() >= targetParameters.size()) return null + val args = targetCall.getArgumentList().getExpressions() + if (args.size() != targetParameters.size()) return null // incorrect code + + for (i in parameters.indices) { + val parameter = parameters[i] + val targetParameter = targetParameters[i] + if (parameter.getName() != targetParameter.getName() || parameter.getType() != targetParameter.getType()) return null + val arg = args[i] + if (arg !is PsiReferenceExpression || arg.resolve() != parameter) return null + } + + return args.drop(parameters.size()) + } + + private fun dropOverloadsForDefaultValues(equivalenceMap: Map) { + val dropCandidates = equivalenceMap + .map { it.key } + .sortBy { -it.getParameterList().getParametersCount() } // we will try to drop them starting from ones with more parameters + + @DropCandidatesLoop + for (method in dropCandidates) { + val paramCount = method.getParameterList().getParametersCount() + val targetInfo = equivalenceMap[method]!! + val targetParamCount = targetInfo.method.getParameterList().getParametersCount() + assert(paramCount < targetParamCount) + val defaults = targetInfo.parameterDefaults + assert(defaults.size() == targetParamCount - paramCount) + + val targetDefaults = methodToLastParameterDefaults.getOrPut(targetInfo.method, { ArrayList() }) + + for (i in defaults.indices) { + val default = defaults[defaults.size() - i - 1] + if (i < targetDefaults.size()) { // default for this parameter has already been assigned + if (targetDefaults[i].getText() != default.getText()) continue@DropCandidatesLoop + } + else { + targetDefaults.add(default) + } + } + + methodsToDrop.add(method) + } + } + + private fun getAnnotationsFingerprint(method: PsiMethod): Any { + return method.getModifierList().getAnnotations().map { it.getText() } + } +} + +public fun Converter.convertParameterList( + method: PsiMethod, + overloadReducer: OverloadReducer?, + convertParameter: (parameter: PsiParameter, default: DeferredElement?) -> Parameter = { parameter, default -> convertParameter(parameter, defaultValue = default) }, + correctCodeConverter: CodeConverter.() -> CodeConverter = { this } +): ParameterList { + val parameterList = method.getParameterList() + val params = parameterList.getParameters() + return ParameterList(params.indices.map { i -> + val parameter = params[i] + val defaultValue = overloadReducer?.parameterDefault(method, i) + val defaultValueConverted = if (defaultValue != null) + deferredElement { codeConverter -> codeConverter.correctCodeConverter().convertExpression(defaultValue, parameter.getType()) } + else + null + convertParameter(parameter, defaultValueConverted) + }).assignPrototype(parameterList) +} diff --git a/j2k/src/org/jetbrains/kotlin/j2k/ast/ClassBody.kt b/j2k/src/org/jetbrains/kotlin/j2k/ast/ClassBody.kt index 130138df8e8..09349732fbe 100644 --- a/j2k/src/org/jetbrains/kotlin/j2k/ast/ClassBody.kt +++ b/j2k/src/org/jetbrains/kotlin/j2k/ast/ClassBody.kt @@ -16,7 +16,11 @@ package org.jetbrains.kotlin.j2k.ast -import org.jetbrains.kotlin.j2k.* +import com.intellij.psi.PsiExpression +import com.intellij.psi.PsiMethod +import org.jetbrains.kotlin.j2k.CodeBuilder +import org.jetbrains.kotlin.j2k.OverloadReducer +import org.jetbrains.kotlin.j2k.append abstract class Member(var annotations: Annotations, val modifiers: Modifiers) : Element() @@ -26,7 +30,8 @@ class ClassBody ( val members: List, val companionObjectMembers: List, val lBrace: LBrace, - val rBrace: RBrace) { + val rBrace: RBrace, + val overloadReducer: OverloadReducer) { fun append(builder: CodeBuilder) { val membersFiltered = members.filter { !it.isEmpty } diff --git a/j2k/src/org/jetbrains/kotlin/j2k/ast/Constructors.kt b/j2k/src/org/jetbrains/kotlin/j2k/ast/Constructors.kt index e965f0ac312..4188a97d04a 100644 --- a/j2k/src/org/jetbrains/kotlin/j2k/ast/Constructors.kt +++ b/j2k/src/org/jetbrains/kotlin/j2k/ast/Constructors.kt @@ -22,9 +22,9 @@ import com.intellij.util.IncorrectOperationException abstract class Constructor( annotations: Annotations, modifiers: Modifiers, - public val parameterList: ParameterList, - protected val body: DeferredElement -) : Member(annotations, modifiers) + parameterList: ParameterList, + body: DeferredElement +) : FunctionLike(annotations, modifiers, parameterList, body) class PrimaryConstructor( annotations: Annotations, @@ -36,14 +36,14 @@ class PrimaryConstructor( override fun generateCode(builder: CodeBuilder) { throw IncorrectOperationException() } public fun initializer(): Initializer - = Initializer(body, Modifiers.Empty).assignPrototypesFrom(this, CommentsAndSpacesInheritance(commentsBefore = false)) + = Initializer(body!!, Modifiers.Empty).assignPrototypesFrom(this, CommentsAndSpacesInheritance(commentsBefore = false)) public fun createSignature(converter: Converter): PrimaryConstructorSignature { val signature = PrimaryConstructorSignature(annotations, modifiers, parameterList) // assign prototypes later because we don't know yet whether the body is empty or not converter.addPostUnfoldDeferredElementsAction { - val inheritance = CommentsAndSpacesInheritance(blankLinesBefore = false, commentsAfter = body.isEmpty, commentsInside = body.isEmpty) + val inheritance = CommentsAndSpacesInheritance(blankLinesBefore = false, commentsAfter = body!!.isEmpty, commentsInside = body.isEmpty) signature.assignPrototypesFrom(this, inheritance) } @@ -89,7 +89,7 @@ class SecondaryConstructor( builder append " : " append thisOrSuperCall } - builder append " " append body + builder append " " append body!! } } diff --git a/j2k/src/org/jetbrains/kotlin/j2k/ast/Function.kt b/j2k/src/org/jetbrains/kotlin/j2k/ast/Function.kt index 334017a5598..c35ab8df8b3 100644 --- a/j2k/src/org/jetbrains/kotlin/j2k/ast/Function.kt +++ b/j2k/src/org/jetbrains/kotlin/j2k/ast/Function.kt @@ -18,16 +18,23 @@ package org.jetbrains.kotlin.j2k.ast import org.jetbrains.kotlin.j2k.* +abstract class FunctionLike( + annotations: Annotations, + modifiers: Modifiers, + val parameterList: ParameterList, + val body: DeferredElement? +) : Member(annotations, modifiers) + class Function( val name: Identifier, annotations: Annotations, modifiers: Modifiers, val returnType: Type, val typeParameterList: TypeParameterList, - val parameterList: ParameterList, - val body: DeferredElement?, + parameterList: ParameterList, + body: DeferredElement?, val isInTrait: Boolean -) : Member(annotations, modifiers) { +) : FunctionLike(annotations, modifiers, parameterList, body) { private fun presentationModifiers(): Modifiers { var modifiers = this.modifiers diff --git a/j2k/testData/fileOrElement/constructors/secondaryConstructorWithDefaultValues2.java b/j2k/testData/fileOrElement/constructors/secondaryConstructorWithDefaultValues2.java new file mode 100644 index 00000000000..33d5b89feb0 --- /dev/null +++ b/j2k/testData/fileOrElement/constructors/secondaryConstructorWithDefaultValues2.java @@ -0,0 +1,17 @@ +class A { + private String s = ""; + private int x = 0; + + public A() { + } + + public A(int p, String s){ + this(p, s, 1); + } + + public A(int p, String s, int x) { + this(); + this.s = s; + this.x = x; + } +} \ No newline at end of file diff --git a/j2k/testData/fileOrElement/constructors/secondaryConstructorWithDefaultValues2.kt b/j2k/testData/fileOrElement/constructors/secondaryConstructorWithDefaultValues2.kt new file mode 100644 index 00000000000..7c7b05476ed --- /dev/null +++ b/j2k/testData/fileOrElement/constructors/secondaryConstructorWithDefaultValues2.kt @@ -0,0 +1,11 @@ +// ERROR: Val cannot be reassigned +// ERROR: Val cannot be reassigned +class A() { + private val s = "" + private val x = 0 + + overloads public constructor(p: Int, s: String, x: Int = 1) : this() { + this.s = s + this.x = x + } +} diff --git a/j2k/testData/fileOrElement/overloads/Abstract.java b/j2k/testData/fileOrElement/overloads/Abstract.java new file mode 100644 index 00000000000..85acf01fd2e --- /dev/null +++ b/j2k/testData/fileOrElement/overloads/Abstract.java @@ -0,0 +1,7 @@ +abstract class A { + public abstract void foo(int p); + + public void foo(){ + foo(1); + } +} \ No newline at end of file diff --git a/j2k/testData/fileOrElement/overloads/Abstract.kt b/j2k/testData/fileOrElement/overloads/Abstract.kt new file mode 100644 index 00000000000..94bfd432178 --- /dev/null +++ b/j2k/testData/fileOrElement/overloads/Abstract.kt @@ -0,0 +1,7 @@ +abstract class A { + public abstract fun foo(p: Int) + + public fun foo() { + foo(1) + } +} diff --git a/j2k/testData/fileOrElement/overloads/AccessorToDrop.java b/j2k/testData/fileOrElement/overloads/AccessorToDrop.java new file mode 100644 index 00000000000..3515ecbdb7e --- /dev/null +++ b/j2k/testData/fileOrElement/overloads/AccessorToDrop.java @@ -0,0 +1,15 @@ +class A { + private int field = 0; + + public int getField() { + return field; + } + + public void setField(int field) { + this.field = field; + } + + public void setField() { + setField(0); + } +} \ No newline at end of file diff --git a/j2k/testData/fileOrElement/overloads/AccessorToDrop.kt b/j2k/testData/fileOrElement/overloads/AccessorToDrop.kt new file mode 100644 index 00000000000..91861fa6054 --- /dev/null +++ b/j2k/testData/fileOrElement/overloads/AccessorToDrop.kt @@ -0,0 +1,7 @@ +class A { + public var field: Int = 0 + + public fun setField() { + field = 0 + } +} diff --git a/j2k/testData/fileOrElement/overloads/Annotations.java b/j2k/testData/fileOrElement/overloads/Annotations.java new file mode 100644 index 00000000000..9e99b28a54e --- /dev/null +++ b/j2k/testData/fileOrElement/overloads/Annotations.java @@ -0,0 +1,44 @@ +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +class A { + @NotNull + Object foo(String s) { + System.out.println("s = " + s); + return ""; + } + + @NotNull + Object foo() { + return foo(null); + } + + @Nullable + Object bar(String s) { + System.out.println("s = " + s); + return s == null ? "" : null; + } + + @NotNull + Object bar() { + return bar(null); + } + + public @Nullable Object bar1(String s) { + System.out.println("s = " + s); + return s == null ? "" : null; + } + + public @NotNull Object bar1() { + return bar1(null); + } + + @Deprecated + public void f() { + f(1); + } + + public void f(int p) { + System.out.println("p = " + p); + } +} \ No newline at end of file diff --git a/j2k/testData/fileOrElement/overloads/Annotations.kt b/j2k/testData/fileOrElement/overloads/Annotations.kt new file mode 100644 index 00000000000..471537a403a --- /dev/null +++ b/j2k/testData/fileOrElement/overloads/Annotations.kt @@ -0,0 +1,35 @@ +// ERROR: Type mismatch: inferred type is kotlin.Any? but kotlin.Any was expected +// ERROR: Type mismatch: inferred type is kotlin.Any? but kotlin.Any was expected +class A { + overloads fun foo(s: String? = null): Any { + println("s = " + s!!) + return "" + } + + fun bar(s: String?): Any? { + println("s = " + s!!) + return if (s == null) "" else null + } + + fun bar(): Any { + return bar(null) + } + + public fun bar1(s: String?): Any? { + println("s = " + s!!) + return if (s == null) "" else null + } + + public fun bar1(): Any { + return bar1(null) + } + + deprecated("") + public fun f() { + f(1) + } + + public fun f(p: Int) { + println("p = " + p) + } +} diff --git a/j2k/testData/fileOrElement/overloads/Override.java b/j2k/testData/fileOrElement/overloads/Override.java new file mode 100644 index 00000000000..f87da465ec6 --- /dev/null +++ b/j2k/testData/fileOrElement/overloads/Override.java @@ -0,0 +1,46 @@ +interface I { + void foo(int i, char c, String s); + void bar(); +} + +class A implements I{ + public void foo(int i, char c, String s) { + System.out.println("foo" + i + c + s); + } + + public void foo(int i, char c) { + foo(i, c, ""); + } + + public void foo(int i) { + foo(i, 'a', ""); + } + + public void bar() { + bar(1); + } + + public void bar(int i) {} + + public void x() { + x(1); + } + + public void x(int i) {} + + public void y() { + y(1); + } + + public void y(int i) {} +} + +class B extends A { + public void x() { + super.x(); + } + + public void y(int i) { + super.y(i); + } +} \ No newline at end of file diff --git a/j2k/testData/fileOrElement/overloads/Override.kt b/j2k/testData/fileOrElement/overloads/Override.kt new file mode 100644 index 00000000000..00b881380d4 --- /dev/null +++ b/j2k/testData/fileOrElement/overloads/Override.kt @@ -0,0 +1,49 @@ +trait I { + public fun foo(i: Int, c: Char, s: String) + public fun bar() +} + +open class A : I { + override fun foo(i: Int, c: Char, s: String) { + println("foo" + i + c + s) + } + + public fun foo(i: Int, c: Char) { + foo(i, c, "") + } + + public fun foo(i: Int) { + foo(i, 'a', "") + } + + override fun bar() { + bar(1) + } + + public fun bar(i: Int) { + } + + public open fun x() { + x(1) + } + + public fun x(i: Int) { + } + + public fun y() { + y(1) + } + + public open fun y(i: Int) { + } +} + +class B : A() { + override fun x() { + super.x() + } + + override fun y(i: Int) { + super.y(i) + } +} diff --git a/j2k/testData/fileOrElement/overloads/Simple.java b/j2k/testData/fileOrElement/overloads/Simple.java new file mode 100644 index 00000000000..7adcadb8a72 --- /dev/null +++ b/j2k/testData/fileOrElement/overloads/Simple.java @@ -0,0 +1,22 @@ +class A { + void foo(int i, char c, String s) { + System.out.println("foo" + i + c + s); + } + + void foo(int i, char c) { + foo(i, c, ""); + } + + void foo(int i) { + foo(i, 'a', ""); + } + + int bar(String s) { + System.out.println("s = " + s); + return 0; + } + + int bar() { + return bar(null); + } +} \ No newline at end of file diff --git a/j2k/testData/fileOrElement/overloads/Simple.kt b/j2k/testData/fileOrElement/overloads/Simple.kt new file mode 100644 index 00000000000..053214b0660 --- /dev/null +++ b/j2k/testData/fileOrElement/overloads/Simple.kt @@ -0,0 +1,10 @@ +class A { + overloads fun foo(i: Int, c: Char = 'a', s: String = "") { + println("foo" + i + c + s) + } + + overloads fun bar(s: String? = null): Int { + println("s = " + s!!) + return 0 + } +} diff --git a/j2k/testData/fileOrElement/overloads/Synchronized.java b/j2k/testData/fileOrElement/overloads/Synchronized.java new file mode 100644 index 00000000000..0ad17431896 --- /dev/null +++ b/j2k/testData/fileOrElement/overloads/Synchronized.java @@ -0,0 +1,12 @@ +class A { + public void foo(int p) { + System.out.println("p = [" + p + "]"); + } + + public synchronized void foo(){ + foo(calcSomething()); + } + + // this method should be invoked under synchronized block! + private int calcSomething() { return 0; } +} \ No newline at end of file diff --git a/j2k/testData/fileOrElement/overloads/Synchronized.kt b/j2k/testData/fileOrElement/overloads/Synchronized.kt new file mode 100644 index 00000000000..eabd97dc020 --- /dev/null +++ b/j2k/testData/fileOrElement/overloads/Synchronized.kt @@ -0,0 +1,14 @@ +class A { + public fun foo(p: Int) { + println("p = [" + p + "]") + } + + synchronized public fun foo() { + foo(calcSomething()) + } + + // this method should be invoked under synchronized block! + private fun calcSomething(): Int { + return 0 + } +} diff --git a/j2k/tests/org/jetbrains/kotlin/j2k/JavaToKotlinConverterForWebDemoTestGenerated.java b/j2k/tests/org/jetbrains/kotlin/j2k/JavaToKotlinConverterForWebDemoTestGenerated.java index 26d47283b56..57953f606aa 100644 --- a/j2k/tests/org/jetbrains/kotlin/j2k/JavaToKotlinConverterForWebDemoTestGenerated.java +++ b/j2k/tests/org/jetbrains/kotlin/j2k/JavaToKotlinConverterForWebDemoTestGenerated.java @@ -78,6 +78,7 @@ import java.util.regex.Pattern; JavaToKotlinConverterForWebDemoTestGenerated.NewClassExpression.class, JavaToKotlinConverterForWebDemoTestGenerated.Nullability.class, JavaToKotlinConverterForWebDemoTestGenerated.ObjectLiteral.class, + JavaToKotlinConverterForWebDemoTestGenerated.Overloads.class, JavaToKotlinConverterForWebDemoTestGenerated.PackageStatement.class, JavaToKotlinConverterForWebDemoTestGenerated.ParenthesizedExpression.class, JavaToKotlinConverterForWebDemoTestGenerated.PolyadicExpression.class, @@ -1313,6 +1314,12 @@ public class JavaToKotlinConverterForWebDemoTestGenerated extends AbstractJavaTo doTest(fileName); } + @TestMetadata("secondaryConstructorWithDefaultValues2.java") + public void testSecondaryConstructorWithDefaultValues2() throws Exception { + String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/constructors/secondaryConstructorWithDefaultValues2.java"); + doTest(fileName); + } + @TestMetadata("secondaryConstructorsInInnerClass.java") public void testSecondaryConstructorsInInnerClass() throws Exception { String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/constructors/secondaryConstructorsInInnerClass.java"); @@ -3489,6 +3496,51 @@ public class JavaToKotlinConverterForWebDemoTestGenerated extends AbstractJavaTo } } + @TestMetadata("j2k/testData/fileOrElement/overloads") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class Overloads extends AbstractJavaToKotlinConverterForWebDemoTest { + @TestMetadata("Abstract.java") + public void testAbstract() throws Exception { + String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/overloads/Abstract.java"); + doTest(fileName); + } + + @TestMetadata("AccessorToDrop.java") + public void testAccessorToDrop() throws Exception { + String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/overloads/AccessorToDrop.java"); + doTest(fileName); + } + + public void testAllFilesPresentInOverloads() throws Exception { + JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("j2k/testData/fileOrElement/overloads"), Pattern.compile("^(.+)\\.java$"), true); + } + + @TestMetadata("Annotations.java") + public void testAnnotations() throws Exception { + String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/overloads/Annotations.java"); + doTest(fileName); + } + + @TestMetadata("Override.java") + public void testOverride() throws Exception { + String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/overloads/Override.java"); + doTest(fileName); + } + + @TestMetadata("Simple.java") + public void testSimple() throws Exception { + String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/overloads/Simple.java"); + doTest(fileName); + } + + @TestMetadata("Synchronized.java") + public void testSynchronized() throws Exception { + String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/overloads/Synchronized.java"); + doTest(fileName); + } + } + @TestMetadata("j2k/testData/fileOrElement/packageStatement") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) diff --git a/j2k/tests/org/jetbrains/kotlin/j2k/JavaToKotlinConverterSingleFileTestGenerated.java b/j2k/tests/org/jetbrains/kotlin/j2k/JavaToKotlinConverterSingleFileTestGenerated.java index 54419b2504e..5eb1a7f34e5 100644 --- a/j2k/tests/org/jetbrains/kotlin/j2k/JavaToKotlinConverterSingleFileTestGenerated.java +++ b/j2k/tests/org/jetbrains/kotlin/j2k/JavaToKotlinConverterSingleFileTestGenerated.java @@ -78,6 +78,7 @@ import java.util.regex.Pattern; JavaToKotlinConverterSingleFileTestGenerated.NewClassExpression.class, JavaToKotlinConverterSingleFileTestGenerated.Nullability.class, JavaToKotlinConverterSingleFileTestGenerated.ObjectLiteral.class, + JavaToKotlinConverterSingleFileTestGenerated.Overloads.class, JavaToKotlinConverterSingleFileTestGenerated.PackageStatement.class, JavaToKotlinConverterSingleFileTestGenerated.ParenthesizedExpression.class, JavaToKotlinConverterSingleFileTestGenerated.PolyadicExpression.class, @@ -1313,6 +1314,12 @@ public class JavaToKotlinConverterSingleFileTestGenerated extends AbstractJavaTo doTest(fileName); } + @TestMetadata("secondaryConstructorWithDefaultValues2.java") + public void testSecondaryConstructorWithDefaultValues2() throws Exception { + String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/constructors/secondaryConstructorWithDefaultValues2.java"); + doTest(fileName); + } + @TestMetadata("secondaryConstructorsInInnerClass.java") public void testSecondaryConstructorsInInnerClass() throws Exception { String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/constructors/secondaryConstructorsInInnerClass.java"); @@ -3489,6 +3496,51 @@ public class JavaToKotlinConverterSingleFileTestGenerated extends AbstractJavaTo } } + @TestMetadata("j2k/testData/fileOrElement/overloads") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class Overloads extends AbstractJavaToKotlinConverterSingleFileTest { + @TestMetadata("Abstract.java") + public void testAbstract() throws Exception { + String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/overloads/Abstract.java"); + doTest(fileName); + } + + @TestMetadata("AccessorToDrop.java") + public void testAccessorToDrop() throws Exception { + String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/overloads/AccessorToDrop.java"); + doTest(fileName); + } + + public void testAllFilesPresentInOverloads() throws Exception { + JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("j2k/testData/fileOrElement/overloads"), Pattern.compile("^(.+)\\.java$"), true); + } + + @TestMetadata("Annotations.java") + public void testAnnotations() throws Exception { + String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/overloads/Annotations.java"); + doTest(fileName); + } + + @TestMetadata("Override.java") + public void testOverride() throws Exception { + String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/overloads/Override.java"); + doTest(fileName); + } + + @TestMetadata("Simple.java") + public void testSimple() throws Exception { + String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/overloads/Simple.java"); + doTest(fileName); + } + + @TestMetadata("Synchronized.java") + public void testSynchronized() throws Exception { + String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/overloads/Synchronized.java"); + doTest(fileName); + } + } + @TestMetadata("j2k/testData/fileOrElement/packageStatement") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class)