[kotlin] J2K: DefaultArgumentsConversion cleanup

GitOrigin-RevId: 6e2859184c16288b873ab4ba89a2a04184f3e5ed
This commit is contained in:
Alexey Belkov
2024-03-28 11:43:59 +00:00
committed by intellij-monorepo-bot
parent a170069ea0
commit b80659810f
@@ -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<PsiMethod>()?.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<JKMethod>().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<JKClass>()?.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<Pair<JKExpression, JKParameter>> =
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<PsiMethod>()?.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<JKArgument>): 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<JKArgument>): 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<JKClass>()?.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)
}
}
}
}