Move JvmOverloads workaround for constructors to Psi model
Previously, the workaround for the Psi issue
https://youtrack.jetbrains.com/issue/KT-57537 was to emulate the
behavior of `@JvmOverloads` by creating overloads of the constructors,
one for each parameter with a default value. That was added to
`SignatureWriter` but that meant that it would not work with snapshots
as it relied on having a `PsiConstructorItem` when generating the
signature which is not true when using snapshots. It was also the wrong
place because a problem with the Psi model should be fixed in the Psi
model, and not leak through the model API.
This change moves the emulation of `@JvmOverloads` from
`SignatureWriter` to the Psi model where it belongs. As that is done
before any snapshot is taken that ensures that it will work properly
when using snapshots. That allowed the `shouldExpandOverloads()` to be
removed from the `CallableItem` and moved to be a private method in
`PsiCodebaseAssembler`.
Bug: 367623741
Test: ./gradlew
(cherry picked from https://android-review.googlesource.com/q/commit:e7edec3f7664cfc2c8a5bf3975081e9c4b9cb08a)
Merged-In: I73053d79ee1a8c12f1967d2349df6a09fffcaacc
Change-Id: I73053d79ee1a8c12f1967d2349df6a09fffcaacc
diff --git a/metalava-model-psi/src/main/java/com/android/tools/metalava/model/psi/PsiCallableItem.kt b/metalava-model-psi/src/main/java/com/android/tools/metalava/model/psi/PsiCallableItem.kt
index 4fd88ff..a70551b 100644
--- a/metalava-model-psi/src/main/java/com/android/tools/metalava/model/psi/PsiCallableItem.kt
+++ b/metalava-model-psi/src/main/java/com/android/tools/metalava/model/psi/PsiCallableItem.kt
@@ -21,8 +21,6 @@
import com.android.tools.metalava.model.type.MethodFingerprint
import com.intellij.psi.PsiMethod
import com.intellij.psi.PsiParameter
-import org.jetbrains.kotlin.name.JvmStandardClassIds
-import org.jetbrains.kotlin.psi.KtFunction
import org.jetbrains.uast.UMethod
internal interface PsiCallableItem : CallableItem, PsiItem {
@@ -31,16 +29,6 @@
val psiMethod: PsiMethod
- override fun shouldExpandOverloads(): Boolean {
- val ktFunction = (psiMethod as? UMethod)?.sourcePsi as? KtFunction ?: return false
- return modifiers.isActual() &&
- psiMethod.hasAnnotation(JvmStandardClassIds.JVM_OVERLOADS_FQ_NAME.asString()) &&
- // It is /technically/ invalid to have actual functions with default values, but
- // some places suppress the compiler error, so we should handle it here too.
- ktFunction.valueParameters.none { it.hasDefaultValue() } &&
- parameters().any { it.hasDefaultValue() }
- }
-
companion object {
/**
* Create a list of [PsiParameterItem]s.
@@ -55,8 +43,8 @@
psiMethod: PsiMethod,
containingCallable: PsiCallableItem,
enclosingTypeItemFactory: PsiTypeItemFactory,
+ psiParameters: List<PsiParameter> = psiMethod.psiParameters,
): List<PsiParameterItem> {
- val psiParameters = psiMethod.psiParameters
val fingerprint = MethodFingerprint(containingCallable.name(), psiParameters.size)
return psiParameters.mapIndexed { index, parameter ->
PsiParameterItem.create(
diff --git a/metalava-model-psi/src/main/java/com/android/tools/metalava/model/psi/PsiCodebaseAssembler.kt b/metalava-model-psi/src/main/java/com/android/tools/metalava/model/psi/PsiCodebaseAssembler.kt
index af778f7..ca2fb9e 100644
--- a/metalava-model-psi/src/main/java/com/android/tools/metalava/model/psi/PsiCodebaseAssembler.kt
+++ b/metalava-model-psi/src/main/java/com/android/tools/metalava/model/psi/PsiCodebaseAssembler.kt
@@ -27,6 +27,7 @@
import com.android.tools.metalava.model.ClassKind
import com.android.tools.metalava.model.ClassOrigin
import com.android.tools.metalava.model.ClassTypeItem
+import com.android.tools.metalava.model.ConstructorItem
import com.android.tools.metalava.model.Item
import com.android.tools.metalava.model.JAVA_PACKAGE_INFO
import com.android.tools.metalava.model.PackageItem
@@ -75,6 +76,8 @@
import org.jetbrains.kotlin.analysis.api.types.KaTypeNullability
import org.jetbrains.kotlin.asJava.classes.KtLightClassForFacade
import org.jetbrains.kotlin.name.FqName
+import org.jetbrains.kotlin.name.JvmStandardClassIds
+import org.jetbrains.kotlin.psi.KtFunction
import org.jetbrains.kotlin.psi.KtParameter
import org.jetbrains.kotlin.psi.KtProperty
import org.jetbrains.kotlin.psi.KtPropertyAccessor
@@ -82,6 +85,7 @@
import org.jetbrains.kotlin.psi.psiUtil.isPropertyParameter
import org.jetbrains.uast.UClass
import org.jetbrains.uast.UFile
+import org.jetbrains.uast.UMethod
import org.jetbrains.uast.UParameter
import org.jetbrains.uast.UastFacade
import org.jetbrains.uast.kotlin.BaseKotlinUastResolveProviderService
@@ -243,6 +247,11 @@
psiMethod,
classTypeItemFactory,
)
+ addOverloadedKotlinConstructorsIfNecessary(
+ classItem,
+ classTypeItemFactory,
+ constructor
+ )
classItem.addConstructor(constructor)
} else {
val method =
@@ -452,6 +461,73 @@
!psiClass.isEnum
}
+ /**
+ * Returns true if overloads of this constructor should be checked separately when checking the
+ * signature of this constructor.
+ *
+ * This works around the issue of actual callable not generating overloads for @JvmOverloads
+ * annotation when the default is specified on expect side
+ * (https://youtrack.jetbrains.com/issue/KT-57537).
+ */
+ private fun PsiConstructorItem.shouldExpandOverloads(): Boolean {
+ val ktFunction = (psiMethod as? UMethod)?.sourcePsi as? KtFunction ?: return false
+ return modifiers.isActual() &&
+ psiMethod.hasAnnotation(JvmStandardClassIds.JVM_OVERLOADS_FQ_NAME.asString()) &&
+ // It is /technically/ invalid to have actual functions with default values, but
+ // some places suppress the compiler error, so we should handle it here too.
+ ktFunction.valueParameters.none { it.hasDefaultValue() } &&
+ parameters().any { it.hasDefaultValue() }
+ }
+
+ /**
+ * Add overloads of [constructor] if necessary.
+ *
+ * Workaround for https://youtrack.jetbrains.com/issue/KT-57537.
+ *
+ * For each parameter with a default value in [constructor] this adds a [ConstructorItem] that
+ * excludes that parameter and all following parameters with default values.
+ */
+ private fun addOverloadedKotlinConstructorsIfNecessary(
+ classItem: PsiClassItem,
+ enclosingClassTypeItemFactory: PsiTypeItemFactory,
+ constructor: PsiConstructorItem,
+ ) {
+ if (!constructor.shouldExpandOverloads()) {
+ return
+ }
+
+ val parameters = constructor.parameters()
+
+ // Create an overload of the constructor for each parameter that has a default value. The
+ // constructor will exclude that parameter and all following parameters that have default
+ // values.
+ for (currentParameterIndex in parameters.indices) {
+ val currentParameter = parameters[currentParameterIndex]
+ // There is no need to create an overload if the parameter does not have default value.
+ if (!currentParameter.hasDefaultValue()) continue
+
+ // Create an overloaded constructor.
+ val overloadConstructor =
+ PsiConstructorItem.create(
+ codebase,
+ classItem,
+ constructor.psiMethod,
+ enclosingClassTypeItemFactory,
+ psiParametersGetter = {
+ parameters.mapIndexedNotNull { index, parameterItem ->
+ // Ignore the current parameter as well as any following parameters
+ // with default values.
+ if (index >= currentParameterIndex && parameterItem.hasDefaultValue())
+ null
+ else (parameterItem as PsiParameterItem).psiParameter
+ }
+ },
+ )
+
+ classItem.addConstructor(overloadConstructor)
+ }
+ }
+
private fun findOrCreateClass(qualifiedName: String): ClassItem? {
// Check to see if the class has already been seen and if so return it immediately.
codebase.findClass(qualifiedName)?.let {
diff --git a/metalava-model-psi/src/main/java/com/android/tools/metalava/model/psi/PsiConstructorItem.kt b/metalava-model-psi/src/main/java/com/android/tools/metalava/model/psi/PsiConstructorItem.kt
index fdcd1b8..0d2e3df 100644
--- a/metalava-model-psi/src/main/java/com/android/tools/metalava/model/psi/PsiConstructorItem.kt
+++ b/metalava-model-psi/src/main/java/com/android/tools/metalava/model/psi/PsiConstructorItem.kt
@@ -34,6 +34,7 @@
import com.intellij.psi.JavaPsiFacade
import com.intellij.psi.PsiClass
import com.intellij.psi.PsiMethod
+import com.intellij.psi.PsiParameter
import org.jetbrains.kotlin.psi.KtClassOrObject
import org.jetbrains.kotlin.psi.KtPrimaryConstructor
import org.jetbrains.uast.UMethod
@@ -78,6 +79,7 @@
containingClass: ClassItem,
psiMethod: PsiMethod,
enclosingClassTypeItemFactory: PsiTypeItemFactory,
+ psiParametersGetter: (PsiMethod) -> List<PsiParameter> = { it.psiParameters },
): PsiConstructorItem {
assert(psiMethod.isConstructor)
val name = psiMethod.name
@@ -119,6 +121,7 @@
psiMethod,
containingCallable as PsiCallableItem,
constructorTypeItemFactory,
+ psiParametersGetter(psiMethod),
)
},
returnType = containingClass.type(),
diff --git a/metalava-model/src/main/java/com/android/tools/metalava/model/CallableItem.kt b/metalava-model/src/main/java/com/android/tools/metalava/model/CallableItem.kt
index 54d66a2..ac6345f 100644
--- a/metalava-model/src/main/java/com/android/tools/metalava/model/CallableItem.kt
+++ b/metalava-model/src/main/java/com/android/tools/metalava/model/CallableItem.kt
@@ -108,16 +108,6 @@
containingClass().qualifiedName()}.${name()}(${parameters().joinToString { it.type().toSimpleType() }})"
}
- /**
- * Returns true if overloads of this callable should be checked separately when checking the
- * signature of this callable.
- *
- * This works around the issue of actual callable not generating overloads for @JvmOverloads
- * annotation when the default is specified on expect side
- * (https://youtrack.jetbrains.com/issue/KT-57537).
- */
- fun shouldExpandOverloads(): Boolean = false
-
override fun equalsToItem(other: Any?): Boolean {
if (this === other) return true
if (other !is CallableItem) return false
diff --git a/metalava/src/main/java/com/android/tools/metalava/SignatureWriter.kt b/metalava/src/main/java/com/android/tools/metalava/SignatureWriter.kt
index 48ab0da..274380d 100644
--- a/metalava/src/main/java/com/android/tools/metalava/SignatureWriter.kt
+++ b/metalava/src/main/java/com/android/tools/metalava/SignatureWriter.kt
@@ -34,7 +34,6 @@
import com.android.tools.metalava.model.visitors.ApiVisitor
import com.android.tools.metalava.model.visitors.FilteringApiVisitor
import java.io.PrintWriter
-import java.util.BitSet
class SignatureWriter(
private val writer: PrintWriter,
@@ -77,33 +76,13 @@
}
override fun visitConstructor(constructor: ConstructorItem) {
- fun writeConstructor(skipMask: BitSet? = null) {
- write(" ctor ")
- writeModifiers(constructor)
- writeTypeParameterList(constructor.typeParameterList, addSpace = true)
- write(constructor.containingClass().fullName())
- writeParameterList(constructor, skipMask)
- writeThrowsList(constructor)
- write(";\n")
- }
-
- // Workaround for https://youtrack.jetbrains.com/issue/KT-57537
- if (constructor.shouldExpandOverloads()) {
- val parameters = constructor.parameters()
- val defaultMask = BitSet(parameters.size)
-
- // fill the bitmask for all parameters
- parameters.forEachIndexed { i, item -> defaultMask.set(i, item.hasDefaultValue()) }
-
- // expand overloads ordered by number of parameters, skipping last parameters first
- for (i in parameters.indices) {
- if (!defaultMask.get(i)) continue
- writeConstructor(defaultMask)
- defaultMask.clear(i)
- }
- }
-
- writeConstructor()
+ write(" ctor ")
+ writeModifiers(constructor)
+ writeTypeParameterList(constructor.typeParameterList, addSpace = true)
+ write(constructor.containingClass().fullName())
+ writeParameterList(constructor)
+ writeThrowsList(constructor)
+ write(";\n")
}
override fun visitField(field: FieldItem) {
@@ -262,13 +241,10 @@
}
}
- private fun writeParameterList(callable: CallableItem, skipMask: BitSet? = null) {
+ private fun writeParameterList(callable: CallableItem) {
write("(")
var writtenParams = 0
- callable.parameters().asSequence().forEachIndexed { i, parameter ->
- // skip over defaults when generating @JvmOverloads permutations
- if (skipMask != null && skipMask.get(i)) return@forEachIndexed
-
+ callable.parameters().asSequence().forEach { parameter ->
if (writtenParams > 0) {
write(", ")
}