From 47aa6392bfbd556a9e9c10a541e14416bcba1c34 Mon Sep 17 00:00:00 2001 From: Roman Golyshev Date: Wed, 17 Jan 2024 13:08:32 +0100 Subject: [PATCH] KT-64808 [stubs] Materialize all synthethic declarations in Kotlin Stubs Here is the reasoning behind that change: Historically, all the declarations generated by compiler plugins were marked with `SYNTHESIZED` member kind in Kotlin Metadata by K1 compiler. In K1 IDE, descriptors were deserialized directly from the Kotlin Metadata, and they saw all the generated declarations from it. In the stubs, however, such declarations were not materialized at all. It caused no troubles, since K1 IDE relied on descriptors to get the essential resolution information in completion and other subsystems. So, the resolution of members from jars processed by compiler plugins (e.g. `kotlinx-serialization-json`) was mostly fine. In K2 IDE, however, we use stubs to "deserialize" FIR declarations from them, to later create `KtSymbol`s upon that FIR. If we see a library file which was processed by compiler plugins, we build stubs for it based on the Kotlin Metadata, and then use stubs to create FIR. But if stubs do not contain information about the generated declarations, then the resulting FIR will also not contain such declarations. In the end, the K2 IDE would also be blind to such declarations, since there is no other way to retrieve them from the library jar. By meterializing all the synthethic declarations in stubs (with some minor exceptions for data classes), we would avoid such problems, and the resolve of such declarations from compiler jars in K2 IDE would become possible. Important note: currently, the Kotlin Metadata format between K1 and K2 frontends is not 100% the same; most notably, in K2, the generated declarations are marked as regular declarations, not as `SYNTHESIZED` ones. Hence, if you compile a jar with the current version of K2 frontend, then all the generated declarations would have a regular `DECLARATION` origin, and there would be no such issues as described above. There are two notes here: 1. K2 IDE still has to support the jars compiled by the K1 compiler, so it still makes sense to alter the stubs and make the generated declarations visible. 2. The issue about the different stub formats has been reported (see KT-64924), and might be resolved in the future. So it is possible that K2 frontend's metadata will also start marking the generated declarations as `SYNTHESIZED`. ^KT-64808 Fixed --- .../psi/text/buildDecompiledText.kt | 33 +++++++++++++------ .../decompiler/stub/CallableClsStubBuilder.kt | 30 +++++++++++------ .../kotlin/psi/stubs/KotlinStubVersions.kt | 2 +- .../deserialization/ProtoContainer.kt | 1 + 4 files changed, 45 insertions(+), 21 deletions(-) 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 3933a580272..0108929b195 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 @@ -12,18 +12,14 @@ import org.jetbrains.kotlin.builtins.StandardNames import org.jetbrains.kotlin.contracts.description.ContractProviderKey import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.name.FqName -import org.jetbrains.kotlin.name.Name -import org.jetbrains.kotlin.name.SpecialNames import org.jetbrains.kotlin.psi.psiUtil.quoteIfNeeded import org.jetbrains.kotlin.renderer.DescriptorRenderer import org.jetbrains.kotlin.renderer.DescriptorRendererModifier import org.jetbrains.kotlin.renderer.DescriptorRendererOptions import org.jetbrains.kotlin.renderer.render -import org.jetbrains.kotlin.resolve.DataClassDescriptorResolver import org.jetbrains.kotlin.resolve.DescriptorUtils.isEnumEntry import org.jetbrains.kotlin.resolve.descriptorUtil.secondaryConstructors import org.jetbrains.kotlin.types.isFlexible -import org.jetbrains.kotlin.util.OperatorNameConventions import org.jetbrains.kotlin.util.capitalizeDecapitalize.toLowerCaseAsciiOnly private const val DECOMPILED_CODE_COMMENT = "/* compiled code */" @@ -44,18 +40,35 @@ fun DescriptorRendererOptions.defaultDecompilerRendererOptions() { propertyConstantRenderer = { _ -> COMPILED_DEFAULT_INITIALIZER } } +/** + * @see org.jetbrains.kotlin.analysis.decompiler.stub.mustNotBeWrittenToStubs + */ internal fun CallableMemberDescriptor.mustNotBeWrittenToDecompiledText(): Boolean { return when (kind) { CallableMemberDescriptor.Kind.DECLARATION, CallableMemberDescriptor.Kind.DELEGATION -> false CallableMemberDescriptor.Kind.FAKE_OVERRIDE -> true - CallableMemberDescriptor.Kind.SYNTHESIZED -> { - // Of all synthesized functions, only `component*` functions are rendered (for historical reasons) - !DataClassDescriptorResolver.isComponentLike(name) && name !in listOf( - OperatorNameConventions.EQUALS, - StandardNames.HASHCODE_NAME, - OperatorNameConventions.TO_STRING + CallableMemberDescriptor.Kind.SYNTHESIZED -> syntheticMemberMustNotBeWrittenToDecompiledText() + } +} + +private fun CallableMemberDescriptor.syntheticMemberMustNotBeWrittenToDecompiledText(): Boolean { + val containingClass = containingDeclaration as? ClassDescriptor ?: return false + + return when { + containingClass.isData && containingClass.kind != ClassKind.OBJECT -> { + // we want to materialize every synthetic data class function except for the 'copy' (for historical reasons) + name == StandardNames.DATA_CLASS_COPY + } + + containingClass.kind == ClassKind.ENUM_CLASS -> { + name in arrayOf( + StandardNames.ENUM_VALUES, + StandardNames.ENUM_ENTRIES, + StandardNames.ENUM_VALUE_OF, ) } + + else -> false } } diff --git a/analysis/decompiled/decompiler-to-stubs/src/org/jetbrains/kotlin/analysis/decompiler/stub/CallableClsStubBuilder.kt b/analysis/decompiled/decompiler-to-stubs/src/org/jetbrains/kotlin/analysis/decompiler/stub/CallableClsStubBuilder.kt index 830416b0258..d9946046b61 100644 --- a/analysis/decompiled/decompiler-to-stubs/src/org/jetbrains/kotlin/analysis/decompiler/stub/CallableClsStubBuilder.kt +++ b/analysis/decompiled/decompiler-to-stubs/src/org/jetbrains/kotlin/analysis/decompiler/stub/CallableClsStubBuilder.kt @@ -26,12 +26,10 @@ import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.psi.stubs.KotlinPropertyStub import org.jetbrains.kotlin.psi.stubs.elements.KtStubElementTypes import org.jetbrains.kotlin.psi.stubs.impl.* -import org.jetbrains.kotlin.resolve.DataClassResolver import org.jetbrains.kotlin.resolve.constants.ClassLiteralValue import org.jetbrains.kotlin.serialization.deserialization.AnnotatedCallableKind import org.jetbrains.kotlin.serialization.deserialization.ProtoContainer import org.jetbrains.kotlin.serialization.deserialization.getName -import org.jetbrains.kotlin.util.OperatorNameConventions import org.jetbrains.kotlin.utils.addIfNotNull import org.jetbrains.kotlin.utils.addToStdlib.runIf @@ -55,14 +53,14 @@ fun createDeclarationsStubs( propertyProtos: List, ) { for (propertyProto in propertyProtos) { - if (mustNotBeWrittenToStubs(propertyProto.flags, outerContext.nameResolver.getName(propertyProto.name))) { + if (mustNotBeWrittenToStubs(propertyProto.flags, outerContext.nameResolver.getName(propertyProto.name), protoContainer)) { continue } PropertyClsStubBuilder(parentStub, outerContext, protoContainer, propertyProto).build() } for (functionProto in functionProtos) { - if (mustNotBeWrittenToStubs(functionProto.flags, outerContext.nameResolver.getName(functionProto.name))) { + if (mustNotBeWrittenToStubs(functionProto.flags, outerContext.nameResolver.getName(functionProto.name), protoContainer)) { continue } @@ -90,15 +88,27 @@ fun createConstructorStub( ConstructorClsStubBuilder(parentStub, outerContext, protoContainer, constructorProto).build() } -private fun mustNotBeWrittenToStubs(flags: Int, name: Name): Boolean { +/** + * @see org.jetbrains.kotlin.analysis.decompiler.psi.text.mustNotBeWrittenToDecompiledText + */ +private fun mustNotBeWrittenToStubs(flags: Int, name: Name, protoContainer: ProtoContainer): Boolean { return when (Flags.MEMBER_KIND.get(flags)) { MemberKind.FAKE_OVERRIDE -> true //TODO: fix decompiler to use sane criteria - MemberKind.SYNTHESIZED -> !DataClassResolver.isComponentLike(name) && name !in listOf( - OperatorNameConventions.EQUALS, - StandardNames.HASHCODE_NAME, - OperatorNameConventions.TO_STRING - ) + MemberKind.SYNTHESIZED -> syntheticMemberMustNotBeWrittenToStubs(name, protoContainer) + else -> false + } +} + +private fun syntheticMemberMustNotBeWrittenToStubs(name: Name, protoContainer: ProtoContainer): Boolean { + val containingClass = protoContainer as? ProtoContainer.Class ?: return false + + return when { + containingClass.isData && containingClass.kind != ProtoBuf.Class.Kind.OBJECT -> { + // we want to materialize every synthetic data class function except for the 'copy' (for historical reasons) + name == StandardNames.DATA_CLASS_COPY + } + else -> false } } diff --git a/compiler/psi/src/org/jetbrains/kotlin/psi/stubs/KotlinStubVersions.kt b/compiler/psi/src/org/jetbrains/kotlin/psi/stubs/KotlinStubVersions.kt index 4368127d729..adf9b1f4686 100644 --- a/compiler/psi/src/org/jetbrains/kotlin/psi/stubs/KotlinStubVersions.kt +++ b/compiler/psi/src/org/jetbrains/kotlin/psi/stubs/KotlinStubVersions.kt @@ -17,7 +17,7 @@ object KotlinStubVersions { // Binary stub version should be increased if stub format (org.jetbrains.kotlin.psi.stubs.impl) is changed // or changes are made to the core stub building code (org.jetbrains.kotlin.idea.decompiler.stubBuilder). // Increasing this version will lead to reindexing of all binary files that are potentially kotlin binaries (including all class files). - private const val BINARY_STUB_VERSION = 97 + private const val BINARY_STUB_VERSION = 98 // Classfile stub version should be increased if changes are made to classfile stub building subsystem (org.jetbrains.kotlin.idea.decompiler.classFile) // Increasing this version will lead to reindexing of all classfiles. diff --git a/core/deserialization.common/src/org/jetbrains/kotlin/serialization/deserialization/ProtoContainer.kt b/core/deserialization.common/src/org/jetbrains/kotlin/serialization/deserialization/ProtoContainer.kt index 74e9d4d956d..e460a09e47c 100644 --- a/core/deserialization.common/src/org/jetbrains/kotlin/serialization/deserialization/ProtoContainer.kt +++ b/core/deserialization.common/src/org/jetbrains/kotlin/serialization/deserialization/ProtoContainer.kt @@ -29,6 +29,7 @@ sealed class ProtoContainer( val kind: ProtoBuf.Class.Kind = Flags.CLASS_KIND.get(classProto.flags) ?: ProtoBuf.Class.Kind.CLASS val isInner: Boolean = Flags.IS_INNER.get(classProto.flags) + val isData: Boolean = Flags.IS_DATA.get(classProto.flags) override fun debugFqName(): FqName = classId.asSingleFqName() }