From b80659810f4939e75becf659131607772f7ae619 Mon Sep 17 00:00:00 2001 From: Alexey Belkov Date: Wed, 27 Mar 2024 17:22:24 +0400 Subject: [PATCH] [kotlin] J2K: DefaultArgumentsConversion cleanup GitOrigin-RevId: 6e2859184c16288b873ab4ba89a2a04184f3e5ed --- .../conversions/DefaultArgumentsConversion.kt | 294 +++++++++--------- 1 file changed, 151 insertions(+), 143 deletions(-) diff --git a/plugins/kotlin/j2k/shared/src/org/jetbrains/kotlin/nj2k/conversions/DefaultArgumentsConversion.kt b/plugins/kotlin/j2k/shared/src/org/jetbrains/kotlin/nj2k/conversions/DefaultArgumentsConversion.kt index 81167591c1c1..c60802b552b2 100644 --- a/plugins/kotlin/j2k/shared/src/org/jetbrains/kotlin/nj2k/conversions/DefaultArgumentsConversion.kt +++ b/plugins/kotlin/j2k/shared/src/org/jetbrains/kotlin/nj2k/conversions/DefaultArgumentsConversion.kt @@ -2,7 +2,6 @@ package org.jetbrains.kotlin.nj2k.conversions - import com.intellij.psi.PsiMethod import org.jetbrains.kotlin.analysis.api.KtAnalysisSession import org.jetbrains.kotlin.load.java.propertyNameByGetMethodName @@ -11,169 +10,117 @@ import org.jetbrains.kotlin.nj2k.* import org.jetbrains.kotlin.nj2k.symbols.JKSymbol import org.jetbrains.kotlin.nj2k.symbols.JKUniverseMethodSymbol import org.jetbrains.kotlin.nj2k.tree.* +import org.jetbrains.kotlin.nj2k.tree.JKClass.ClassKind.ANNOTATION +import org.jetbrains.kotlin.nj2k.tree.Modality.ABSTRACT +import org.jetbrains.kotlin.nj2k.tree.OtherModifier.* +import org.jetbrains.kotlin.nj2k.tree.Visibility.INTERNAL +import org.jetbrains.kotlin.nj2k.tree.Visibility.PUBLIC import org.jetbrains.kotlin.nj2k.types.JKNoType +/** + * This conversion tries to merge sets of overloaded methods or constructors into a single method/constructor + * with default parameters and `JvmOverloads` annotation. + */ class DefaultArgumentsConversion(context: NewJ2kConverterContext) : RecursiveConversion(context) { - - private fun JKMethod.canNotBeMerged(): Boolean = - modality == Modality.ABSTRACT - || hasOtherModifier(OtherModifier.OVERRIDE) - || hasOtherModifier(OtherModifier.NATIVE) - || hasOtherModifier(OtherModifier.SYNCHRONIZED) - || psi()?.let { context.converter.referenceSearcher.hasOverrides(it) } == true - || hasAnnotations - || name.value.canBeGetterOrSetterName() - context(KtAnalysisSession) override fun applyToElement(element: JKTreeElement): JKTreeElement { if (element !is JKClassBody) return recurse(element) - val methods = element.declarations.filterIsInstance().sortedBy { it.parameters.size } - - checkMethod@ for (method in methods) { - val block = method.block as? JKBlock ?: continue - val singleStatement = block.statements.singleOrNull() ?: continue - if (method.canNotBeMerged()) continue - - val call = lookupCall(singleStatement) ?: continue - val callee = call.identifier as? JKUniverseMethodSymbol ?: continue - val calledMethod = callee.target - if (calledMethod.parent != method.parent - || callee.name != method.name.value - || calledMethod.returnType.type != method.returnType.type - || call.arguments.arguments.size <= method.parameters.size - || call.arguments.arguments.size < calledMethod.parameters.size //calledMethod has varargs param or call expr has errors - || calledMethod.parameters.any(JKParameter::isVarArgs) - ) { - continue - } - - - if (calledMethod.visibility != method.visibility) continue@checkMethod - if (calledMethod.canNotBeMerged()) continue - - for (i in method.parameters.indices) { - val parameter = method.parameters[i] - val targetParameter = calledMethod.parameters[i] - val argument = call.arguments.arguments[i].value - if (parameter.name.value != targetParameter.name.value) continue@checkMethod - if (parameter.type.type != targetParameter.type.type) continue@checkMethod - if (argument !is JKFieldAccessExpression || argument.identifier.target != parameter) continue@checkMethod - if (parameter.initializer !is JKStubExpression - && targetParameter.initializer !is JKStubExpression - && !areTheSameExpressions(targetParameter.initializer, parameter.initializer) - ) continue@checkMethod - } - - for (i in method.parameters.indices) { - val parameter = method.parameters[i] - val targetParameter = calledMethod.parameters[i] - if (parameter.initializer !is JKStubExpression - && targetParameter.initializer is JKStubExpression - ) { - targetParameter.initializer = parameter.initializer.copyTreeAndDetach() - } - } - - - - for (index in (method.parameters.lastIndex + 1)..calledMethod.parameters.lastIndex) { - val calleeExpression = call.arguments.arguments[index].value - val defaultArgument = calledMethod.parameters[index].initializer.takeIf { it !is JKStubExpression } ?: continue - if (!areTheSameExpressions(calleeExpression, defaultArgument)) continue@checkMethod - } - - - call.arguments.invalidate() - val defaults = call.arguments.arguments - .map { it::value.detached() } - .zip(calledMethod.parameters) - .drop(method.parameters.size) - - fun JKSymbol.isNeedThisReceiver(): Boolean { - val parameters = defaults.map { it.second } - val declarations = element.declarations - val propertyNameByGetMethodName = propertyNameByGetMethodName(Name.identifier(this.name))?.asString() - return parameters.any { it.name.value == this.name || it.name.value == propertyNameByGetMethodName } - && declarations.any { it == this.target } - } - - fun remapParameterSymbol(on: JKTreeElement): JKTreeElement { - if (on is JKQualifiedExpression && on.receiver is JKThisExpression) { - return on - } - if (on is JKFieldAccessExpression) { - val target = on.identifier.target - if (target is JKParameter && target.parent == method) { - val newSymbol = - symbolProvider.provideUniverseSymbol(calledMethod.parameters[method.parameters.indexOf(target)]) - return JKFieldAccessExpression(newSymbol) - } - if (on.identifier.isNeedThisReceiver()) { - return JKQualifiedExpression(JKThisExpression(JKLabelEmpty(), JKNoType), JKFieldAccessExpression(on.identifier)) - } - } - if (on is JKCallExpression && on.identifier.isNeedThisReceiver()) { - return JKQualifiedExpression(JKThisExpression(JKLabelEmpty(), JKNoType), applyRecursive(on, ::remapParameterSymbol)) - } - return applyRecursive(on, ::remapParameterSymbol) - } - - for ((defaultValue, parameter) in defaults) { - parameter.initializer = remapParameterSymbol(defaultValue) as JKExpression - } - element.declarations -= method - calledMethod.withFormattingFrom(method) + for (method in methods) { + processMethod(method, element) } - if (element.parentOfType()?.classKind != JKClass.ClassKind.ANNOTATION) { - for (method in element.declarations) { - if (method !is JKMethod) continue - if (method.hasParametersWithDefaultValues() - && (method.visibility == Visibility.PUBLIC || method.visibility == Visibility.INTERNAL) - ) { - method.annotationList.annotations += jvmAnnotation("JvmOverloads", symbolProvider) - } - } - } - + addJvmOverloadsAnnotations(element) return recurse(element) } - private fun areTheSameExpressions(first: JKElement, second: JKElement): Boolean { + private fun processMethod(method: JKMethod, classBody: JKClassBody) { + val singleStatement = method.block.statements.singleOrNull() ?: return + val callExpression = extractCallExpression(singleStatement) ?: return + val calledMethod = (callExpression.identifier as? JKUniverseMethodSymbol)?.target ?: return + val arguments = callExpression.arguments.arguments + + if (!checkSignatureCompatibility(method, calledMethod, arguments)) return + if (method.canNotBeMerged() || calledMethod.canNotBeMerged()) return + migrateParameterDefaultValues(method, calledMethod) + if (!checkArgumentsMatchParameterDefaultValues(method, calledMethod, arguments)) return + callExpression.arguments.invalidate() + + val defaultPairs: List> = + arguments.map { it::value.detached() }.zip(calledMethod.parameters).drop(method.parameters.size) + + fun JKSymbol.needsExplicitThisReceiver(): Boolean { + val parameters = defaultPairs.map { it.second } + val propertyNameByGetMethodName = propertyNameByGetMethodName(Name.identifier(this.name))?.asString() + return parameters.any { it.name.value == this.name || it.name.value == propertyNameByGetMethodName } + && classBody.declarations.any { it == this.target } + } + + fun remapParameterSymbol(on: JKTreeElement): JKTreeElement { + when { + on is JKQualifiedExpression && on.receiver is JKThisExpression -> + return on + + on is JKFieldAccessExpression -> { + val target = on.identifier.target + if (target is JKParameter && target.parent == method) { + val newSymbol = symbolProvider.provideUniverseSymbol(calledMethod.parameters[method.parameters.indexOf(target)]) + return JKFieldAccessExpression(newSymbol) + } else if (on.identifier.needsExplicitThisReceiver()) { + return JKQualifiedExpression(JKThisExpression(JKLabelEmpty(), JKNoType), JKFieldAccessExpression(on.identifier)) + } + } + + on is JKCallExpression && on.identifier.needsExplicitThisReceiver() -> { + val selector = applyRecursive(on, ::remapParameterSymbol) + return JKQualifiedExpression(JKThisExpression(JKLabelEmpty(), JKNoType), selector) + } + } + + return applyRecursive(on, ::remapParameterSymbol) + } + + for ((defaultValue, parameter) in defaultPairs) { + parameter.initializer = remapParameterSymbol(defaultValue) as JKExpression + } + + classBody.declarations -= method + calledMethod.withFormattingFrom(method) + } + + private fun JKMethod.canNotBeMerged(): Boolean = + modality == ABSTRACT + || hasOtherModifier(OVERRIDE) + || hasOtherModifier(NATIVE) + || hasOtherModifier(SYNCHRONIZED) + || hasAnnotations + || name.value.canBeGetterOrSetterName() + || psi()?.let { context.converter.referenceSearcher.hasOverrides(it) } == true + + private fun areEqualElements(first: JKElement, second: JKElement): Boolean { if (first::class != second::class) return false if (first is JKNameIdentifier && second is JKNameIdentifier) return first.value == second.value if (first is JKLiteralExpression && second is JKLiteralExpression) return first.literal == second.literal if (first is JKFieldAccessExpression && second is JKFieldAccessExpression && first.identifier != second.identifier) return false if (first is JKCallExpression && second is JKCallExpression && first.identifier != second.identifier) return false - return if (first is JKTreeElement && second is JKTreeElement) { - first.children.zip(second.children) { childOfFirst, childOfSecond -> - when { - childOfFirst is JKTreeElement && childOfSecond is JKTreeElement -> { - areTheSameExpressions( - childOfFirst, - childOfSecond - ) - } + if (first !is JKTreeElement || second !is JKTreeElement) return false - childOfFirst is List<*> && childOfSecond is List<*> -> { - childOfFirst.zip(childOfSecond) { child1, child2 -> - areTheSameExpressions( - child1 as JKElement, - child2 as JKElement - ) - }.fold(true, Boolean::and) - } + return first.children.zip(second.children) { childOfFirst, childOfSecond -> + when { + childOfFirst is JKTreeElement && childOfSecond is JKTreeElement -> + areEqualElements(childOfFirst, childOfSecond) - else -> false + childOfFirst is List<*> && childOfSecond is List<*> -> { + childOfFirst.zip(childOfSecond) { child1, child2 -> + areEqualElements(child1 as JKElement, child2 as JKElement) + }.fold(true, Boolean::and) } - }.fold(true, Boolean::and) - } else false + + else -> false + } + }.fold(true, Boolean::and) } - private fun JKMethod.hasParametersWithDefaultValues() = - parameters.any { it.initializer !is JKStubExpression } - - private fun lookupCall(statement: JKStatement): JKCallExpression? { + private fun extractCallExpression(statement: JKStatement): JKCallExpression? { val expression = when (statement) { is JKExpressionStatement -> statement.expression is JKReturnStatement -> statement.expression @@ -189,4 +136,65 @@ class DefaultArgumentsConversion(context: NewJ2kConverterContext) : RecursiveCon else -> null } } + + private fun checkSignatureCompatibility(method: JKMethod, calledMethod: JKMethod, arguments: List): Boolean { + if (calledMethod.visibility != method.visibility) return false + if (calledMethod.parent != method.parent) return false + if (calledMethod.name.value != method.name.value) return false + if (calledMethod.returnType.type != method.returnType.type) return false + if (arguments.size <= method.parameters.size) return false + // `calledMethod` has a varargs parameter or call expression has errors + if (arguments.size < calledMethod.parameters.size) return false + if (calledMethod.parameters.any(JKParameter::isVarArgs)) return false + + for (i in method.parameters.indices) { + val parameter = method.parameters[i] + val targetParameter = calledMethod.parameters[i] + val argument = arguments[i].value + + if (parameter.name.value != targetParameter.name.value) return false + if (parameter.type.type != targetParameter.type.type) return false + if (argument !is JKFieldAccessExpression || argument.identifier.target != parameter) return false + + // parameter default values must be identical + if (parameter.initializer !is JKStubExpression + && targetParameter.initializer !is JKStubExpression + && !areEqualElements(targetParameter.initializer, parameter.initializer) + ) return false + } + + return true + } + + private fun migrateParameterDefaultValues(method: JKMethod, calledMethod: JKMethod) { + for (i in method.parameters.indices) { + val parameter = method.parameters[i] + val targetParameter = calledMethod.parameters[i] + if (parameter.initializer !is JKStubExpression && targetParameter.initializer is JKStubExpression) { + targetParameter.initializer = parameter.initializer.copyTreeAndDetach() + } + } + } + + private fun checkArgumentsMatchParameterDefaultValues(method: JKMethod, calledMethod: JKMethod, arguments: List): Boolean { + for (index in (method.parameters.lastIndex + 1)..calledMethod.parameters.lastIndex) { + val argumentValue = arguments[index].value + val defaultArgument = calledMethod.parameters[index].initializer.takeIf { it !is JKStubExpression } ?: continue + if (!areEqualElements(argumentValue, defaultArgument)) return false + } + return true + } + + private fun addJvmOverloadsAnnotations(classBody: JKClassBody) { + fun JKMethod.hasParametersWithDefaultValues(): Boolean = + parameters.any { it.initializer !is JKStubExpression } + + if (classBody.parentOfType()?.classKind == ANNOTATION) return + for (method in classBody.declarations) { + if (method !is JKMethod) continue + if (method.hasParametersWithDefaultValues() && (method.visibility == PUBLIC || method.visibility == INTERNAL)) { + method.annotationList.annotations += jvmAnnotation("JvmOverloads", symbolProvider) + } + } + } }