From d120d83d60e65864b54f478eca032960fe7edd7e Mon Sep 17 00:00:00 2001 From: Roman Efremov Date: Thu, 9 Feb 2023 13:22:20 +0100 Subject: [PATCH] Fix navigation in IDE to synthetic methods in compiled Kotlin For some synthetic methods from compiled Kotlin classes, it is not possible to find the PsiElement, when navigating to declaration in the IDE. For other methods it works like this: 1. There is a SourceElement with a psi inside 2. Or for K1, the element is searched in the decompiled text (see ByDescriptorIndexer), 3. Or for K2, the element is searched by Stub taken from metadata (see FirDeserializedDeclarationSourceProvider) However, these approaches do not apply to synthetic methods which have no SourceElement and are not written into decompiled text and metadata. These are methods values, valueOf, entries from enum and copy, equals, hashCode, toString from data classes. Therefore, it was decided to handle their cases separately at the resolve stage. Tests (in idea repository): - org.jetbrains.kotlin.idea.resolve.ReferenceResolveWithLibTestGenerated#testDataClassSyntheticMethods - org.jetbrains.kotlin.idea.resolve.ReferenceResolveWithLibTestGenerated#testEnumSyntheticMethods - org.jetbrains.kotlin.idea.fir.resolve.FirReferenceResolveWithLibTestGenerated#testDataClassSyntheticMethods - org.jetbrains.kotlin.idea.fir.resolve.FirReferenceResolveWithLibTestGenerated#testEnumSyntheticMethods ^KTIJ-24413 Fixed --- ...irDeserializedDeclarationSourceProvider.kt | 34 +++++++++++++++++++ .../kotlin/analysis/api/fir/psiUtils.kt | 9 +++-- .../psi/text/ByDescriptorIndexer.kt | 6 ++++ .../psi/text/buildDecompiledText.kt | 30 ++++++++-------- 4 files changed, 61 insertions(+), 18 deletions(-) diff --git a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/FirDeserializedDeclarationSourceProvider.kt b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/FirDeserializedDeclarationSourceProvider.kt index 383a04ac647..887c90b7e7a 100644 --- a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/FirDeserializedDeclarationSourceProvider.kt +++ b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/FirDeserializedDeclarationSourceProvider.kt @@ -12,10 +12,15 @@ import org.jetbrains.kotlin.analysis.low.level.api.fir.api.KtDeclarationAndFirDe import org.jetbrains.kotlin.analysis.low.level.api.fir.project.structure.llFirModuleData import org.jetbrains.kotlin.analysis.project.structure.KtBuiltinsModule import org.jetbrains.kotlin.analysis.providers.createDeclarationProvider +import org.jetbrains.kotlin.builtins.StandardNames import org.jetbrains.kotlin.fir.FirElement import org.jetbrains.kotlin.fir.containingClassLookupTag import org.jetbrains.kotlin.fir.declarations.* +import org.jetbrains.kotlin.fir.declarations.utils.isData +import org.jetbrains.kotlin.fir.declarations.utils.isEnumClass +import org.jetbrains.kotlin.fir.resolve.getContainingClass import org.jetbrains.kotlin.fir.scopes.impl.delegatedWrapperData +import org.jetbrains.kotlin.fir.symbols.impl.FirCallableSymbol import org.jetbrains.kotlin.fir.unwrapFakeOverrides import org.jetbrains.kotlin.name.ClassId import org.jetbrains.kotlin.name.FqName @@ -25,6 +30,7 @@ import org.jetbrains.kotlin.psi.KtClassOrObject import org.jetbrains.kotlin.psi.KtElement import org.jetbrains.kotlin.psi.KtFunction import org.jetbrains.kotlin.psi.KtNamedFunction +import org.jetbrains.kotlin.util.OperatorNameConventions //todo introduce LibraryModificationTracker based cache? internal object FirDeserializedDeclarationSourceProvider { @@ -40,6 +46,34 @@ internal object FirDeserializedDeclarationSourceProvider { } } + fun findClassPsiForGeneratedMembers(fir: FirElement, project: Project): PsiElement? { + if (fir !is FirCallableDeclaration) { + return null + } + val containingClass = fir.getContainingClass(fir.moduleData.session) ?: return null + if (isGeneratedMemberNotWrittenToMetadata(fir.symbol, containingClass)) { + return provideSourceForClass(containingClass, project) + } + return null + } + + private fun isGeneratedMemberNotWrittenToMetadata(symbol: FirCallableSymbol<*>, containingClass: FirRegularClass): Boolean { + return when { + containingClass.isEnumClass -> symbol.name in setOf( + StandardNames.ENUM_VALUES, + StandardNames.ENUM_VALUE_OF, + StandardNames.ENUM_ENTRIES, + ) + containingClass.isData -> symbol.name in setOf( + OperatorNameConventions.EQUALS, + OperatorNameConventions.TO_STRING, + StandardNames.HASHCODE_NAME, + StandardNames.DATA_CLASS_COPY, + ) + else -> false + } + } + private fun provideSourceForFunction( function: FirSimpleFunction, project: Project diff --git a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/psiUtils.kt b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/psiUtils.kt index 10d3cef99f3..3ecf8b3554b 100644 --- a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/psiUtils.kt +++ b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/psiUtils.kt @@ -51,6 +51,9 @@ fun FirElement.findPsi(session: FirSession): PsiElement? = * For data classes & enums generated members like `copy` `componentN`, `values` it will return corresponding enum/data class * Otherwise, behaves the same way as [findPsi] returns exact PSI declaration corresponding to passed [FirDeclaration] */ -fun FirDeclaration.findReferencePsi(): PsiElement? = - psi - ?: FirDeserializedDeclarationSourceProvider.findPsi(this, (moduleData.session as LLFirSession).project) +fun FirDeclaration.findReferencePsi(): PsiElement? { + psi?.let { return it } + val project = (moduleData.session as LLFirSession).project + return FirDeserializedDeclarationSourceProvider.findPsi(this, project) + ?: FirDeserializedDeclarationSourceProvider.findClassPsiForGeneratedMembers(this, project) +} diff --git a/analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/text/ByDescriptorIndexer.kt b/analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/text/ByDescriptorIndexer.kt index 7784f468ea9..f962c68b484 100644 --- a/analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/text/ByDescriptorIndexer.kt +++ b/analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/text/ByDescriptorIndexer.kt @@ -52,6 +52,12 @@ object ByDescriptorIndexer : DecompiledTextIndexer { return classOrObject?.primaryConstructor ?: classOrObject } + if ((original as? CallableMemberDescriptor)?.mustNotBeWrittenToDecompiledText() == true && + original.containingDeclaration is ClassDescriptor + ) { + return getDeclarationForDescriptor(original.containingDeclaration, file) + } + val descriptorKey = original.toStringKey() if (!file.isContentsLoaded && original is MemberDescriptor) { diff --git a/analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/text/buildDecompiledText.kt b/analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/text/buildDecompiledText.kt index c80b75ab555..87ad3f2b002 100644 --- a/analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/text/buildDecompiledText.kt +++ b/analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/text/buildDecompiledText.kt @@ -34,6 +34,20 @@ fun DescriptorRendererOptions.defaultDecompilerRendererOptions() { parameterNamesInFunctionalTypes = false // to support parameters names in decompiled text we need to load annotation arguments } +internal fun CallableMemberDescriptor.mustNotBeWrittenToDecompiledText(): Boolean { + return when (kind) { + CallableMemberDescriptor.Kind.DECLARATION -> false + + CallableMemberDescriptor.Kind.FAKE_OVERRIDE, + CallableMemberDescriptor.Kind.DELEGATION -> true + + CallableMemberDescriptor.Kind.SYNTHESIZED -> { + // Of all synthesized functions, only `component*` functions are rendered (for historical reasons) + !DataClassDescriptorResolver.isComponentLike(name) + } + } +} + fun buildDecompiledText( packageFqName: FqName, descriptors: List, @@ -56,20 +70,6 @@ fun buildDecompiledText( textIndex.addToIndex(descriptor, TextRange(startOffset, endOffset)) } - fun CallableMemberDescriptor.isConsideredSynthetic(): Boolean { - return when (kind) { - CallableMemberDescriptor.Kind.DECLARATION -> false - - CallableMemberDescriptor.Kind.FAKE_OVERRIDE, - CallableMemberDescriptor.Kind.DELEGATION -> true - - CallableMemberDescriptor.Kind.SYNTHESIZED -> { - // Of all synthesized functions, only `component*` functions are rendered (for historical reasons) - !DataClassDescriptorResolver.isComponentLike(name) - } - } - } - fun appendDescriptor(descriptor: DeclarationDescriptor, indent: String, lastEnumEntry: Boolean? = null) { val startOffset = builder.length if (isEnumEntry(descriptor)) { @@ -144,7 +144,7 @@ fun buildDecompiledText( if (member == companionObject) { continue } - if (member is CallableMemberDescriptor && member.isConsideredSynthetic()) { + if (member is CallableMemberDescriptor && member.mustNotBeWrittenToDecompiledText()) { continue } newlineExceptFirst()