AA: introduce eq checker to handle ambiguous PSI from binary module
diff --git a/analysis/symbol-light-classes/src/org/jetbrains/kotlin/analysis/providers/DecompiledPsiDeclarationProvider.kt b/analysis/symbol-light-classes/src/org/jetbrains/kotlin/analysis/providers/DecompiledPsiDeclarationProvider.kt
index f2a2d45..9139f56 100644
--- a/analysis/symbol-light-classes/src/org/jetbrains/kotlin/analysis/providers/DecompiledPsiDeclarationProvider.kt
+++ b/analysis/symbol-light-classes/src/org/jetbrains/kotlin/analysis/providers/DecompiledPsiDeclarationProvider.kt
@@ -8,10 +8,12 @@
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiElement
import com.intellij.psi.search.GlobalSearchScope
+import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
import org.jetbrains.kotlin.analysis.api.symbols.*
+import org.jetbrains.kotlin.analysis.providers.PsiDeclarationAndKtSymbolEqualityChecker.representsTheSameDeclaration
object DecompiledPsiDeclarationProvider {
- fun findPsi(ktSymbol: KtSymbol, project: Project): PsiElement? {
+ fun KtAnalysisSession.findPsi(ktSymbol: KtSymbol, project: Project): PsiElement? {
return when (ktSymbol) {
is KtConstructorSymbol -> providePsiForConstructor(ktSymbol, project)
is KtFunctionLikeSymbol -> providePsiForFunction(ktSymbol, project)
@@ -22,7 +24,7 @@
}
}
- private fun providePsiForConstructor(
+ private fun KtAnalysisSession.providePsiForConstructor(
constructorSymbol: KtConstructorSymbol,
project: Project
): PsiElement? {
@@ -30,17 +32,24 @@
val psiClass = project.createPsiDeclarationProvider(constructorSymbol.scope(project))
?.getClassesByClassId(classId)
?.firstOrNull() ?: return null
- return psiClass.constructors.firstOrNull()
+ return psiClass.constructors.find { psiMethod ->
+ representsTheSameDeclaration(psiMethod, constructorSymbol)
+ }
}
- private fun providePsiForFunction(
+ private fun KtAnalysisSession.providePsiForFunction(
functionLikeSymbol: KtFunctionLikeSymbol,
project: Project
): PsiElement? {
return functionLikeSymbol.callableIdIfNonLocal?.let {
- project.createPsiDeclarationProvider(functionLikeSymbol.scope(project))
+ val candidates = project.createPsiDeclarationProvider(functionLikeSymbol.scope(project))
?.getFunctions(it)
- ?.firstOrNull()
+ if (candidates?.size == 1)
+ candidates.single()
+ else
+ candidates?.find { psiMethod ->
+ representsTheSameDeclaration(psiMethod, functionLikeSymbol)
+ }
}
}
diff --git a/analysis/symbol-light-classes/src/org/jetbrains/kotlin/analysis/providers/PsiDeclarationAndKtSymbolEqualityChecker.kt b/analysis/symbol-light-classes/src/org/jetbrains/kotlin/analysis/providers/PsiDeclarationAndKtSymbolEqualityChecker.kt
new file mode 100644
index 0000000..e2a043c
--- /dev/null
+++ b/analysis/symbol-light-classes/src/org/jetbrains/kotlin/analysis/providers/PsiDeclarationAndKtSymbolEqualityChecker.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors.
+ * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
+ */
+
+package org.jetbrains.kotlin.analysis.providers
+
+import com.intellij.psi.PsiMethod
+import com.intellij.psi.PsiType
+import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
+import org.jetbrains.kotlin.analysis.api.symbols.KtCallableSymbol
+import org.jetbrains.kotlin.analysis.api.symbols.KtConstructorSymbol
+import org.jetbrains.kotlin.analysis.api.symbols.KtFunctionLikeSymbol
+import org.jetbrains.kotlin.analysis.api.types.KtType
+
+// TODO replace with structural type comparison?
+internal object PsiDeclarationAndKtSymbolEqualityChecker {
+ fun KtAnalysisSession.representsTheSameDeclaration(psi: PsiMethod, symbol: KtCallableSymbol): Boolean {
+ // TODO: receiver type comparison?
+ if (!returnTypesMatch(psi, symbol)) return false
+ if (!typeParametersMatch(psi, symbol)) return false
+ if (symbol is KtFunctionLikeSymbol && !valueParametersMatch(psi, symbol)) return false
+ return true
+ }
+
+ private fun KtAnalysisSession.returnTypesMatch(psi: PsiMethod, symbol: KtCallableSymbol): Boolean {
+ if (symbol is KtConstructorSymbol) return true
+ return psi.returnType?.let { isTheSameTypes(psi, it, symbol.returnType, isVararg = false) }
+ ?: false
+ }
+
+ private fun typeParametersMatch(psi: PsiMethod, symbol: KtCallableSymbol): Boolean {
+ if (psi.typeParameters.size != symbol.typeParameters.size) return false
+ psi.typeParameters.zip(symbol.typeParameters) { psiTypeParameter, typeParameterSymbol ->
+ if (psiTypeParameter.name != typeParameterSymbol.name.asString()) return false
+ // TODO: type parameter bounds comparison
+ }
+ return true
+ }
+
+ private fun KtAnalysisSession.valueParametersMatch(psi: PsiMethod, symbol: KtFunctionLikeSymbol): Boolean {
+ val valueParameterCount = if (symbol.isExtension) symbol.valueParameters.size + 1 else symbol.valueParameters.size
+ if (psi.parameterList.parametersCount != valueParameterCount) return false
+ if (symbol.isExtension) {
+ val psiParameter = psi.parameterList.parameters[0]
+ if (symbol.receiverType?.let { isTheSameTypes(psi, psiParameter.type, it, isVararg = false) } != true) return false
+ }
+ val offset = if (symbol.isExtension) 1 else 0
+ symbol.valueParameters.forEachIndexed { index, valueParameterSymbol ->
+ val psiParameter = psi.parameterList.parameters[index + offset]
+ if (valueParameterSymbol.name.asString() != psiParameter.name) return false
+ if (valueParameterSymbol.isVararg != psiParameter.isVarArgs) return false
+ if (!isTheSameTypes(psi, psiParameter.type, valueParameterSymbol.returnType, valueParameterSymbol.isVararg)) return false
+ }
+ return true
+ }
+
+ private fun KtAnalysisSession.isTheSameTypes(
+ context: PsiMethod,
+ psi: PsiType,
+ ktType: KtType,
+ isVararg: Boolean = false
+ ): Boolean {
+ // Shortcut: primitive void == Unit as a function return type
+ if (psi == PsiType.VOID && ktType.isUnit) return true
+ val ktTypeRendered = ktType.asPsiType(context) ?: return false
+ val rendered = if (isVararg) ktTypeRendered.createArrayType() else ktTypeRendered
+ return rendered == psi
+ }
+}
\ No newline at end of file