[decompiler] fix ambiguities to builtin members resolve in K2 IDE

If a user has multiple kotlin-stdlib libraries in a project, all those stdlib libraries will contain builtins
which will result in resolution ambiguities.

To prevent such kind of ambiguities inside LLFirDependenciesSymbolProvider,
callables are grouped by facade class name.
Only matching callables from the first facade are used.

Facade is a Kotlin/JVM-term so right now it works only for JVM targets.
Builtins are also used in JVM, so the current solution is to have a Facade name also for builtins.

^KTIJ-26760
This commit is contained in:
Ilya Kirillov
2023-08-25 10:50:29 +02:00
committed by Space Team
parent 866368cf69
commit 9ea344fe76
6 changed files with 43 additions and 8 deletions
@@ -12,11 +12,17 @@ import com.intellij.psi.impl.compiled.ClassFileStubBuilder
import com.intellij.psi.stubs.PsiFileStub import com.intellij.psi.stubs.PsiFileStub
import com.intellij.util.indexing.FileContent import com.intellij.util.indexing.FileContent
import org.jetbrains.kotlin.analysis.decompiler.stub.* import org.jetbrains.kotlin.analysis.decompiler.stub.*
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.descriptors.SourceElement
import org.jetbrains.kotlin.load.kotlin.JvmPackagePartSource
import org.jetbrains.kotlin.metadata.ProtoBuf import org.jetbrains.kotlin.metadata.ProtoBuf
import org.jetbrains.kotlin.metadata.deserialization.BinaryVersion import org.jetbrains.kotlin.metadata.deserialization.BinaryVersion
import org.jetbrains.kotlin.metadata.deserialization.NameResolverImpl import org.jetbrains.kotlin.metadata.deserialization.NameResolverImpl
import org.jetbrains.kotlin.metadata.deserialization.TypeTable import org.jetbrains.kotlin.metadata.deserialization.TypeTable
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
import org.jetbrains.kotlin.serialization.SerializerExtensionProtocol import org.jetbrains.kotlin.serialization.SerializerExtensionProtocol
import org.jetbrains.kotlin.serialization.deserialization.ClassDeserializer import org.jetbrains.kotlin.serialization.deserialization.ClassDeserializer
import org.jetbrains.kotlin.serialization.deserialization.ProtoBasedClassDataFinder import org.jetbrains.kotlin.serialization.deserialization.ProtoBasedClassDataFinder
@@ -56,7 +62,12 @@ open class KotlinMetadataStubBuilder(
val fileStub = createFileStub(packageFqName, isScript = false) val fileStub = createFileStub(packageFqName, isScript = false)
createPackageDeclarationsStubs( createPackageDeclarationsStubs(
fileStub, context, fileStub, context,
ProtoContainer.Package(packageFqName, context.nameResolver, context.typeTable, source = null), ProtoContainer.Package(
packageFqName,
context.nameResolver,
context.typeTable,
source = createCallableSource(file, content.fileName)
),
packageProto packageProto
) )
for (classProto in file.classesToDecompile) { for (classProto in file.classesToDecompile) {
@@ -69,6 +80,8 @@ open class KotlinMetadataStubBuilder(
} }
} }
protected open fun createCallableSource(file: FileWithMetadata.Compatible, filename: String): SourceElement? = null
sealed class FileWithMetadata { sealed class FileWithMetadata {
class Incompatible(val version: BinaryVersion) : FileWithMetadata() class Incompatible(val version: BinaryVersion) : FileWithMetadata()
@@ -6,10 +6,14 @@ import com.intellij.ide.highlighter.JavaClassFileType
import com.intellij.openapi.vfs.VirtualFile import com.intellij.openapi.vfs.VirtualFile
import org.jetbrains.annotations.TestOnly import org.jetbrains.annotations.TestOnly
import org.jetbrains.kotlin.analysis.decompiler.stub.file.KotlinMetadataStubBuilder import org.jetbrains.kotlin.analysis.decompiler.stub.file.KotlinMetadataStubBuilder
import org.jetbrains.kotlin.descriptors.SourceElement
import org.jetbrains.kotlin.load.kotlin.JvmPackagePartSource
import org.jetbrains.kotlin.load.kotlin.PackagePartClassUtils
import org.jetbrains.kotlin.metadata.ProtoBuf import org.jetbrains.kotlin.metadata.ProtoBuf
import org.jetbrains.kotlin.metadata.builtins.BuiltInsBinaryVersion import org.jetbrains.kotlin.metadata.builtins.BuiltInsBinaryVersion
import org.jetbrains.kotlin.name.ClassId import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.psi.stubs.KotlinStubVersions import org.jetbrains.kotlin.psi.stubs.KotlinStubVersions
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
import org.jetbrains.kotlin.serialization.deserialization.FlexibleTypeDeserializer import org.jetbrains.kotlin.serialization.deserialization.FlexibleTypeDeserializer
import org.jetbrains.kotlin.serialization.deserialization.MetadataPackageFragment import org.jetbrains.kotlin.serialization.deserialization.MetadataPackageFragment
import org.jetbrains.kotlin.serialization.deserialization.builtins.BuiltInSerializerProtocol import org.jetbrains.kotlin.serialization.deserialization.builtins.BuiltInSerializerProtocol
@@ -21,16 +25,34 @@ class KotlinBuiltInDecompiler : KotlinMetadataDecompiler<BuiltInsBinaryVersion>(
FlexibleTypeDeserializer.ThrowException, { BuiltInsBinaryVersion.INSTANCE }, { BuiltInsBinaryVersion.INVALID_VERSION }, FlexibleTypeDeserializer.ThrowException, { BuiltInsBinaryVersion.INSTANCE }, { BuiltInsBinaryVersion.INVALID_VERSION },
KotlinStubVersions.BUILTIN_STUB_VERSION KotlinStubVersions.BUILTIN_STUB_VERSION
) { ) {
override val metadataStubBuilder: KotlinMetadataStubBuilder =
KotlinBuiltInMetadataStubBuilder(::readFileSafely)
override fun readFile(bytes: ByteArray, file: VirtualFile): KotlinMetadataStubBuilder.FileWithMetadata? { override fun readFile(bytes: ByteArray, file: VirtualFile): KotlinMetadataStubBuilder.FileWithMetadata? {
return BuiltInDefinitionFile.read(bytes, file) return BuiltInDefinitionFile.read(bytes, file)
} }
} }
private class KotlinBuiltInMetadataStubBuilder(
readFile: (VirtualFile, ByteArray) -> FileWithMetadata?,
) : KotlinMetadataStubBuilder(KotlinStubVersions.BUILTIN_STUB_VERSION, KotlinBuiltInFileType, { BuiltInSerializerProtocol }, readFile) {
override fun createCallableSource(file: FileWithMetadata.Compatible, filename: String): SourceElement? {
val fileNameForFacade = when (val withoutExtension = filename.removeSuffix(BuiltInSerializerProtocol.DOT_DEFAULT_EXTENSION)) {
// this is the filename used in stdlib, others should match
"kotlin" -> "library"
else -> withoutExtension
}
val facadeFqName = PackagePartClassUtils.getPackagePartFqName(file.packageFqName, fileNameForFacade)
return JvmPackagePartSource(JvmClassName.byClassId(ClassId.topLevel(facadeFqName)), null, file.proto.`package`, file.nameResolver)
}
}
class BuiltInDefinitionFile( class BuiltInDefinitionFile(
proto: ProtoBuf.PackageFragment, proto: ProtoBuf.PackageFragment,
version: BuiltInsBinaryVersion, version: BuiltInsBinaryVersion,
val packageDirectory: VirtualFile, val packageDirectory: VirtualFile,
val isMetadata: Boolean val isMetadata: Boolean,
) : KotlinMetadataStubBuilder.FileWithMetadata.Compatible(proto, version, BuiltInSerializerProtocol) { ) : KotlinMetadataStubBuilder.FileWithMetadata.Compatible(proto, version, BuiltInSerializerProtocol) {
override val classesToDecompile: List<ProtoBuf.Class> override val classesToDecompile: List<ProtoBuf.Class>
get() = super.classesToDecompile.let { classes -> get() = super.classesToDecompile.let { classes ->
@@ -31,7 +31,7 @@ abstract class KotlinMetadataDecompiler<out V : BinaryVersion>(
private val invalidBinaryVersion: () -> V, private val invalidBinaryVersion: () -> V,
stubVersion: Int stubVersion: Int
) : ClassFileDecompilers.Full() { ) : ClassFileDecompilers.Full() {
private val metadataStubBuilder: KotlinMetadataStubBuilder = protected open val metadataStubBuilder: KotlinMetadataStubBuilder =
KotlinMetadataStubBuilder(stubVersion, fileType, serializerProtocol, ::readFileSafely) KotlinMetadataStubBuilder(stubVersion, fileType, serializerProtocol, ::readFileSafely)
private val renderer: DescriptorRenderer by lazy { private val renderer: DescriptorRenderer by lazy {
@@ -61,7 +61,7 @@ abstract class KotlinMetadataDecompiler<out V : BinaryVersion>(
@TestOnly @TestOnly
fun readFile(file: VirtualFile) = readFileSafely(file) fun readFile(file: VirtualFile) = readFileSafely(file)
private fun readFileSafely(file: VirtualFile, content: ByteArray? = null): KotlinMetadataStubBuilder.FileWithMetadata? { protected fun readFileSafely(file: VirtualFile, content: ByteArray? = null): KotlinMetadataStubBuilder.FileWithMetadata? {
if (!file.isValid) return null if (!file.isValid) return null
return try { return try {
@@ -15,6 +15,7 @@ import org.jetbrains.kotlin.analysis.providers.createPackageProvider
import org.jetbrains.kotlin.fir.FirSession import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.caches.FirCache import org.jetbrains.kotlin.fir.caches.FirCache
import org.jetbrains.kotlin.fir.caches.firCachesFactory import org.jetbrains.kotlin.fir.caches.firCachesFactory
import org.jetbrains.kotlin.fir.caches.getValue
import org.jetbrains.kotlin.fir.declarations.FirDeclarationOrigin import org.jetbrains.kotlin.fir.declarations.FirDeclarationOrigin
import org.jetbrains.kotlin.fir.deserialization.SingleModuleDataProvider import org.jetbrains.kotlin.fir.deserialization.SingleModuleDataProvider
import org.jetbrains.kotlin.fir.java.deserialization.KotlinBuiltins import org.jetbrains.kotlin.fir.java.deserialization.KotlinBuiltins
@@ -28,7 +29,6 @@ import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.stubs.impl.* import org.jetbrains.kotlin.psi.stubs.impl.*
import org.jetbrains.kotlin.resolve.jvm.JvmClassName import org.jetbrains.kotlin.resolve.jvm.JvmClassName
import org.jetbrains.kotlin.utils.addToStdlib.ifNotEmpty import org.jetbrains.kotlin.utils.addToStdlib.ifNotEmpty
import org.jetbrains.kotlin.fir.caches.getValue
typealias DeserializedTypeAliasPostProcessor = (FirTypeAliasSymbol) -> Unit typealias DeserializedTypeAliasPostProcessor = (FirTypeAliasSymbol) -> Unit
@@ -124,7 +124,7 @@ internal open class StubBasedFirDeserializedSymbolProvider(
StubBasedAnnotationDeserializer(session), StubBasedAnnotationDeserializer(session),
kotlinScopeProvider, kotlinScopeProvider,
parentContext = context, parentContext = context,
containerSource = if (initialOrigin == FirDeclarationOrigin.BuiltIns) null else JvmStubDeserializedContainerSource(classId), containerSource = JvmStubDeserializedContainerSource(classId),
deserializeNestedClass = this::getClass, deserializeNestedClass = this::getClass,
initialOrigin = initialOrigin initialOrigin = initialOrigin
) )
@@ -169,7 +169,7 @@ internal class StubBasedFirDeserializationContext(
callableId.packageName, callableId.packageName,
callableId.className, callableId.className,
parameterListOwner, parameterListOwner,
containerSource = containerSource.takeIf { initialOrigin != FirDeclarationOrigin.BuiltIns }, containerSource = containerSource,
outerClassSymbol = null, outerClassSymbol = null,
symbol, symbol,
initialOrigin initialOrigin
@@ -36,7 +36,7 @@ object KotlinStubVersions {
// BuiltIn stub version should be increased if changes are made to builtIn stub building subsystem (org.jetbrains.kotlin.idea.decompiler.builtIns) // BuiltIn stub version should be increased if changes are made to builtIn stub building subsystem (org.jetbrains.kotlin.idea.decompiler.builtIns)
// Increasing this version will lead to reindexing of all builtIn files (see KotlinBuiltInFileType). // Increasing this version will lead to reindexing of all builtIn files (see KotlinBuiltInFileType).
const val BUILTIN_STUB_VERSION = BINARY_STUB_VERSION + 3 const val BUILTIN_STUB_VERSION = BINARY_STUB_VERSION + 4
// JS stub version should be increased if changes are made to js stub building subsystem (org.jetbrains.kotlin.idea.decompiler.js) // JS stub version should be increased if changes are made to js stub building subsystem (org.jetbrains.kotlin.idea.decompiler.js)
// Increasing this version will lead to reindexing of js binary files (see KotlinJavaScriptMetaFileType). // Increasing this version will lead to reindexing of js binary files (see KotlinJavaScriptMetaFileType).