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 f2a2d456513..9139f56dbaa 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 @@ package org.jetbrains.kotlin.analysis.providers 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 @@ object DecompiledPsiDeclarationProvider { } } - private fun providePsiForConstructor( + private fun KtAnalysisSession.providePsiForConstructor( constructorSymbol: KtConstructorSymbol, project: Project ): PsiElement? { @@ -30,17 +32,24 @@ object DecompiledPsiDeclarationProvider { 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 00000000000..e2a043c7352 --- /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