[K2, IDE] Don't filter declarations from built-ins in K2 decompiler

Intercept decompiler calls to process files that belong to classpath
builtins from K2 IDE differently. Their content won't be filtered w.r.t.
JVM logic during decompilation.

Split stub versions for .kotlin_builtins with K1 and K2 IDE.

K2 currently uses a single shared symbol provider for builtins of all
platforms. KotlinBuiltInDecompiler filters out duplicated declarations
contained in JVM classfiles as required for K1. For K2 this logic
doesn't fit: non-JVM modules (common, in particular) rely on
.kotlin_builtins, because kotlin-stdlib-common doesn't contain
.kotlin_builtins or .knm files for some of the built-in declarations
(e.g., kotlin.Unit). IDE has to restore the missing declarations using
built-ins from classloader. Filtering declarations from there using K1
logic breaks resolution of these declarations inside common modules in
IDE. The change shouldn't affect JVM modules as are able to resolve
all builtin symbols from the standard library before falling back to
built-ins.

KT-61757
This commit is contained in:
Pavel Kirpichenkov
2023-10-17 15:49:46 +03:00
committed by Space Team
parent c38887ae68
commit cf849835ba
2 changed files with 57 additions and 6 deletions
@@ -0,0 +1,35 @@
/*
* Copyright 2010-2023 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.decompiler.psi
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.vfs.VirtualFile
import org.jetbrains.kotlin.analysis.decompiler.stub.file.KotlinMetadataStubBuilder
/**
* Application service that adds a constant offset to the stub version of .kotlin_builtins files.
* The purpose of this offset is to rebuild the decompiled text and the stubs for .kotlin_builtins files after K1 <-> K2 IDE switches.
* K1 and K2 provide different sets of declarations from .kotlin_builtins files under certain conditions,
* see [org.jetbrains.kotlin.analysis.decompiler.psi.BuiltInDefinitionFile].
* Not forcing a rebuild for affected decompiled files and corresponding stubs leads to a stub error.
*/
interface KotlinBuiltInStubVersionOffsetProvider {
fun getVersionOffset(): Int
companion object {
fun getVersionOffset(): Int =
ApplicationManager.getApplication().getService(KotlinBuiltInStubVersionOffsetProvider::class.java)?.getVersionOffset() ?: 0
}
}
interface KotlinBuiltInDecompilationInterceptor {
fun readFile(bytes: ByteArray, file: VirtualFile): KotlinMetadataStubBuilder.FileWithMetadata?
companion object {
fun readFile(bytes: ByteArray, file: VirtualFile): KotlinMetadataStubBuilder.FileWithMetadata? =
ApplicationManager.getApplication().getService(KotlinBuiltInDecompilationInterceptor::class.java)?.readFile(bytes, file)
}
}
@@ -23,19 +23,19 @@ import java.io.ByteArrayInputStream
class KotlinBuiltInDecompiler : KotlinMetadataDecompiler<BuiltInsBinaryVersion>(
KotlinBuiltInFileType, { BuiltInSerializerProtocol },
FlexibleTypeDeserializer.ThrowException, { BuiltInsBinaryVersion.INSTANCE }, { BuiltInsBinaryVersion.INVALID_VERSION },
KotlinStubVersions.BUILTIN_STUB_VERSION
stubVersionForStubBuilderAndDecompiler,
) {
override val metadataStubBuilder: KotlinMetadataStubBuilder =
KotlinBuiltInMetadataStubBuilder(::readFileSafely)
override fun readFile(bytes: ByteArray, file: VirtualFile): KotlinMetadataStubBuilder.FileWithMetadata? {
return BuiltInDefinitionFile.read(bytes, file)
return KotlinBuiltInDecompilationInterceptor.readFile(bytes, file) ?: BuiltInDefinitionFile.read(bytes, file)
}
}
private class KotlinBuiltInMetadataStubBuilder(
readFile: (VirtualFile, ByteArray) -> FileWithMetadata?,
) : KotlinMetadataStubBuilder(KotlinStubVersions.BUILTIN_STUB_VERSION, KotlinBuiltInFileType, { BuiltInSerializerProtocol }, readFile) {
) : KotlinMetadataStubBuilder(stubVersionForStubBuilderAndDecompiler, 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
@@ -48,15 +48,23 @@ private class KotlinBuiltInMetadataStubBuilder(
}
}
/**
* This version is used for .kotlin_builtins and is not used for .kotlin_metadata files:
* K1 IDE and K2 IDE produce different decompiled files and stubs for .kotlin_builtins, but not for .kotlin_metadata
*/
private val stubVersionForStubBuilderAndDecompiler: Int
get() = KotlinStubVersions.BUILTIN_STUB_VERSION + KotlinBuiltInStubVersionOffsetProvider.getVersionOffset()
class BuiltInDefinitionFile(
proto: ProtoBuf.PackageFragment,
version: BuiltInsBinaryVersion,
val packageDirectory: VirtualFile,
val isMetadata: Boolean,
private val filterOutClassesExistingAsClassFiles: Boolean = true,
) : KotlinMetadataStubBuilder.FileWithMetadata.Compatible(proto, version, BuiltInSerializerProtocol) {
override val classesToDecompile: List<ProtoBuf.Class>
get() = super.classesToDecompile.let { classes ->
if (isMetadata || !FILTER_OUT_CLASSES_EXISTING_AS_JVM_CLASS_FILES) classes
if (isMetadata || !FILTER_OUT_CLASSES_EXISTING_AS_JVM_CLASS_FILES || !filterOutClassesExistingAsClassFiles) classes
else classes.filter { classProto ->
shouldDecompileBuiltInClass(nameResolver.getClassId(classProto.fqName))
}
@@ -71,7 +79,11 @@ class BuiltInDefinitionFile(
var FILTER_OUT_CLASSES_EXISTING_AS_JVM_CLASS_FILES = true
@TestOnly set
fun read(contents: ByteArray, file: VirtualFile): KotlinMetadataStubBuilder.FileWithMetadata? {
@JvmOverloads
fun read(
contents: ByteArray, file: VirtualFile,
filterOutClassesExistingAsClassFiles: Boolean = true
): KotlinMetadataStubBuilder.FileWithMetadata? {
val stream = ByteArrayInputStream(contents)
val version = BuiltInsBinaryVersion.readFrom(stream)
@@ -81,7 +93,11 @@ class BuiltInDefinitionFile(
val proto = ProtoBuf.PackageFragment.parseFrom(stream, BuiltInSerializerProtocol.extensionRegistry)
val result =
BuiltInDefinitionFile(proto, version, file.parent, file.extension == MetadataPackageFragment.METADATA_FILE_EXTENSION)
BuiltInDefinitionFile(
proto, version, file.parent,
file.extension == MetadataPackageFragment.METADATA_FILE_EXTENSION,
filterOutClassesExistingAsClassFiles
)
val packageProto = result.proto.`package`
if (result.classesToDecompile.isEmpty() &&
packageProto.typeAliasCount == 0 && packageProto.functionCount == 0 && packageProto.propertyCount == 0