[kotlinp] Support rendering KLIB metadata using "kotlinp" format

This commit includes:
1. New subproject :tools:kotlinp-klib that contains the facade for
   rendering the metadata in "kotlinp" format given just
   KlibModuleMetadata
2. A tool for computing (external) IR signatures that are not directly
   available in metadata: KlibKotlinpExternalSignatureComputer

^KT-62340
This commit is contained in:
Dmitriy Dolovov
2024-02-09 09:46:02 +01:00
committed by Space Team
parent ed30221f42
commit 83cc0d75a7
7 changed files with 447 additions and 0 deletions
+1
View File
@@ -345,6 +345,7 @@
/libraries/tools/kotlin-tooling-metadata/ "Kotlin Build Tools"
/libraries/tools/kotlinp/ "Kotlin JVM" "Kotlin Common Backend"
/libraries/tools/kotlinp/jvm/ "Kotlin JVM"
/libraries/tools/kotlinp/klib/ "Kotlin Common Backend"
/libraries/tools/maven-archetypes/ "Kotlin Build Tools"
/libraries/tools/mutability-annotations-compat/ "Kotlin Libraries"
/libraries/tools/script-runtime/ "Kotlin Compiler Core"
@@ -0,0 +1,26 @@
plugins {
kotlin("jvm")
id("jps-compatible")
}
dependencies {
compileOnly(project(":kotlinx-metadata"))
compileOnly(project(":kotlinx-metadata-klib"))
api(project(":tools:kotlinp"))
testApi(intellijCore())
testCompileOnly(project(":kotlinx-metadata"))
testCompileOnly(project(":kotlinx-metadata-klib"))
testImplementation(libs.junit4)
testImplementation(projectTests(":compiler:tests-common"))
testImplementation(projectTests(":generators:test-generator"))
testRuntimeOnly(project(":kotlinx-metadata-klib"))
}
sourceSets {
"main" { projectDefault() }
}
@@ -0,0 +1,178 @@
/*
* Copyright 2010-2024 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.kotlinp.klib
sealed interface DeclarationId
sealed interface ClassifierId
/**
* Represents a class or a type alias.
*
* @param qualifiedName the fully qualified name. Examples: `Outer`, `Outer.Nested`, `org/sample/Outer.Nested`.
*/
data class ClassOrTypeAliasId(val qualifiedName: String) : DeclarationId, ClassifierId, Comparable<ClassOrTypeAliasId> {
override fun compareTo(other: ClassOrTypeAliasId) = qualifiedName.compareTo(other.qualifiedName)
}
/**
* Represents a class constructor.
*
* @param qualifiedName the fully qualified name. Examples: `Outer.<init>`, `Outer.Nested.<init>`, `org/sample/Outer.Nested.<init>`.
* @param parameters value parameters.
*/
data class ConstructorId(val qualifiedName: String, val parameters: List<ParameterId>) : DeclarationId, Comparable<ConstructorId> {
override fun compareTo(other: ConstructorId) = COMPARATOR.compare(this, other)
companion object {
private val COMPARATOR = compareBy(ConstructorId::qualifiedName)
.thenByList(ConstructorId::parameters)
}
}
/**
* Represents a function or a property accessor.
*
* @param qualifiedName the fully qualified name. Examples: `foo`, `org/sample/foo`, `Outer.foo`, `Outer.Nested.foo`,
* `org/sample/Outer.Nested.foo`, `<get-bar>`, `org/sample/<get-bar>`, `Outer.<get-bar>`, `Outer.Nested.<get-bar>`,
* `org/sample/Outer.Nested.<get-bar>`.
* @param contextReceivers context receivers.
* @param extensionReceiver extension receiver.
* @param parameters value parameters.
*/
data class FunctionId(
val qualifiedName: String,
val contextReceivers: List<TypeId>,
val extensionReceiver: TypeId?,
val parameters: List<ParameterId>,
val returnType: TypeId,
) : DeclarationId, Comparable<FunctionId> {
override fun compareTo(other: FunctionId) = COMPARATOR.compare(this, other)
companion object {
private val COMPARATOR = compareBy(FunctionId::qualifiedName)
.thenByList(FunctionId::contextReceivers)
.thenBy(FunctionId::extensionReceiver)
.thenByList(FunctionId::parameters)
.thenBy(FunctionId::returnType)
}
}
/**
* Represents a property.
*
* @param qualifiedName the fully qualified name. Examples: `foo`, `org/sample/foo`, `Outer.foo`, `Outer.Nested.foo`,
* `org/sample/Outer.Nested.foo`.
* @param contextReceivers context receivers.
* @param extensionReceiver extension receiver.
*/
data class PropertyId(
val qualifiedName: String,
val contextReceivers: List<TypeId>,
val extensionReceiver: TypeId?,
val returnType: TypeId,
) : DeclarationId, Comparable<PropertyId> {
override fun compareTo(other: PropertyId) = COMPARATOR.compare(this, other)
companion object {
private val COMPARATOR = compareBy(PropertyId::qualifiedName)
.thenByList(PropertyId::contextReceivers)
.thenBy(PropertyId::extensionReceiver)
.thenBy(PropertyId::returnType)
}
}
/**
* Represents a value parameter.
*
* @param name value parameter name.
* @param type value parameter type.
* @param isVararg if it is a vararg.
*/
data class ParameterId(val name: String, val type: TypeId, val isVararg: Boolean) : Comparable<ParameterId> {
constructor(type: TypeId, isVararg: Boolean) : this(IGNORED_NAME, type, isVararg)
override fun compareTo(other: ParameterId) = COMPARATOR.compare(this, other)
companion object {
const val IGNORED_NAME = "?"
private val COMPARATOR = compareBy(
ParameterId::type,
ParameterId::isVararg,
ParameterId::name,
)
}
}
/**
* Represents a type parameter.
*
* @param index the type parameter index.
*/
data class TypeParameterId(val index: Int) : ClassifierId, Comparable<TypeParameterId> {
override fun compareTo(other: TypeParameterId) = index.compareTo(other.index)
}
/**
* Represents a type.
*
* @param classifier the classifier.
* @param arguments type arguments.
*/
data class TypeId(val classifier: ClassifierId, val arguments: List<TypeArgumentId>) : Comparable<TypeId> {
override fun compareTo(other: TypeId) = COMPARATOR.compare(this, other)
companion object {
val UNIT = TypeId(ClassOrTypeAliasId("kotlin/Unit"), emptyList())
private val COMPARATOR = compareBy<TypeId>(
{ it.classifier as? ClassOrTypeAliasId },
{ it.classifier as? TypeParameterId }
).thenByList(TypeId::arguments)
}
}
/**
* Represents a type argument.
*/
sealed class TypeArgumentId : Comparable<TypeArgumentId> {
final override fun compareTo(other: TypeArgumentId) = COMPARATOR.compare(this, other)
data object Star : TypeArgumentId()
data class Regular(val type: TypeId, val variance: VarianceId) : TypeArgumentId()
enum class VarianceId { INVARIANT, IN, OUT }
companion object {
private val COMPARATOR = Comparator<TypeArgumentId> { left, right ->
when {
left is Regular && right is Regular -> REGULAR_ARGUMENT_COMPARATOR.compare(left, right)
left is Regular -> 1
right is Regular -> -1
else -> 0
}
}
private val REGULAR_ARGUMENT_COMPARATOR = compareBy(Regular::type, Regular::variance)
}
}
private inline fun <T, R : Comparable<R>> Comparator<T>.thenByList(
crossinline selector: (T) -> List<R>,
): Comparator<T> = Comparator { left, right ->
compare(left, right).let { if (it != 0) return@Comparator it }
val leftList = selector(left)
val rightList = selector(right)
leftList.size.compareTo(rightList.size).let { if (it != 0) return@Comparator it }
for (index in leftList.indices) {
leftList[index].compareTo(rightList[index]).let { if (it != 0) return@Comparator it }
}
0
}
@@ -0,0 +1,77 @@
/*
* Copyright 2010-2024 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.kotlinp.klib
import kotlinx.metadata.*
import kotlinx.metadata.klib.KlibEnumEntry
import org.jetbrains.kotlin.kotlinp.klib.TypeArgumentId.VarianceId
private fun KmClassifier.classifierId(): ClassifierId = when (this) {
is KmClassifier.Class -> ClassOrTypeAliasId(name)
is KmClassifier.TypeAlias -> ClassOrTypeAliasId(name)
is KmClassifier.TypeParameter -> TypeParameterId(id)
}
private fun KmVariance.varianceId(): VarianceId = when (this) {
KmVariance.INVARIANT -> VarianceId.INVARIANT
KmVariance.IN -> VarianceId.IN
KmVariance.OUT -> VarianceId.OUT
}
private fun KmTypeProjection.typeArgumentId(): TypeArgumentId =
if (this == KmTypeProjection.STAR) {
TypeArgumentId.Star
} else {
val (variance, type) = this
check(variance != null && type != null) { "Variance and type should not be null" }
TypeArgumentId.Regular(type.typeId(), variance.varianceId())
}
private fun KmType.typeId(): TypeId = TypeId(classifier.classifierId(), arguments.map { it.typeArgumentId() })
private fun KmValueParameter.valueParameterId(): ParameterId = ParameterId(name, type.typeId(), varargElementType != null)
internal fun KmClass.classId(): ClassOrTypeAliasId = ClassOrTypeAliasId(name)
internal fun KmClass.enumEntryId(enumEntry: KlibEnumEntry): ClassOrTypeAliasId = ClassOrTypeAliasId("$name.${enumEntry.name}")
internal fun KmTypeAlias.typeAliasId(containerNamePrefix: String): ClassOrTypeAliasId = ClassOrTypeAliasId("$containerNamePrefix$name")
@OptIn(ExperimentalContextReceivers::class)
fun KmFunction.functionId(containerNamePrefix: String): FunctionId = FunctionId(
qualifiedName = "$containerNamePrefix$name",
contextReceivers = contextReceiverTypes.map { it.typeId() },
extensionReceiver = receiverParameterType?.typeId(),
parameters = valueParameters.map { it.valueParameterId() },
returnType = returnType.typeId()
)
@OptIn(ExperimentalContextReceivers::class)
fun KmProperty.propertyId(containerNamePrefix: String): PropertyId = PropertyId(
qualifiedName = "$containerNamePrefix$name",
contextReceivers = contextReceiverTypes.map { it.typeId() },
extensionReceiver = receiverParameterType?.typeId(),
returnType = returnType.typeId()
)
internal fun KmProperty.getterId(propertyId: PropertyId): FunctionId = FunctionId(
qualifiedName = "${propertyId.qualifiedName.dropLast(name.length)}<get-$name>",
contextReceivers = propertyId.contextReceivers,
extensionReceiver = propertyId.extensionReceiver,
parameters = emptyList(),
returnType = propertyId.returnType
)
internal fun KmProperty.setterId(propertyId: PropertyId, setterParameter: KmValueParameter?): FunctionId = FunctionId(
qualifiedName = "${propertyId.qualifiedName.dropLast(name.length)}<set-$name>",
contextReceivers = propertyId.contextReceivers,
extensionReceiver = propertyId.extensionReceiver,
parameters = listOf(ParameterId((setterParameter?.type ?: returnType).typeId(), false)),
returnType = TypeId.UNIT
)
fun KmConstructor.constructorId(containerNamePrefix: String): ConstructorId = ConstructorId(
qualifiedName = "$containerNamePrefix<init>",
parameters = valueParameters.map { it.valueParameterId() }
)
@@ -0,0 +1,75 @@
/*
* Copyright 2010-2024 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.kotlinp.klib
import kotlinx.metadata.*
import kotlinx.metadata.klib.KlibEnumEntry
import kotlinx.metadata.klib.KlibModuleMetadata
import kotlinx.metadata.klib.fqName
import kotlinx.metadata.klib.klibEnumEntries
import java.util.IdentityHashMap
/**
* An auxiliary tool that allows computing and caching IR signatures for the metadata declarations rendered
* by [KlibKotlinp]. Such signatures are not available directly in the metadata, but they can be computed by
* descriptors (K1) or Lazy IR built over FIR (K2) and then properly mapped to the corresponding `Km*` entities.
*/
class ExternalSignatureComputer(module: KlibModuleMetadata, getExternallyComputedSignature: (DeclarationId) -> String?) {
/**
* Note on [IdentityHashMap]: It's presence here might look strange. But in fact this is an approach that allows memorizing
* externally computed signatures and then looking up them using `Km*` entities as keys. I.e. no context needs to be maintained
* while traversing the `Km*`-tree (like: what is the containing declaration of a particular [KmFunction]?).
*/
private val signatures = IdentityHashMap<Any, String>()
private val getterSignatures = IdentityHashMap<KmProperty, String>()
private val setterSignatures = IdentityHashMap<KmProperty, String>()
init {
fun visitDeclarationContainer(containerNamePrefix: String, container: KmDeclarationContainer) {
container.functions.forEach { function ->
signatures[function] = getExternallyComputedSignature(function.functionId(containerNamePrefix))
}
container.properties.forEach { property ->
val propertyId = property.propertyId(containerNamePrefix)
signatures[property] = getExternallyComputedSignature(propertyId)
getterSignatures[property] = getExternallyComputedSignature(property.getterId(propertyId))
if (property.setter != null) {
setterSignatures[property] = getExternallyComputedSignature(property.setterId(propertyId, property.setterParameter))
}
}
container.typeAliases.forEach { typeAlias ->
signatures[typeAlias] = getExternallyComputedSignature(typeAlias.typeAliasId(containerNamePrefix))
}
}
module.fragments.forEach { fragment ->
fragment.classes.forEach { clazz ->
signatures[clazz] = getExternallyComputedSignature(clazz.classId())
val classNamePrefix = "${clazz.name}."
clazz.constructors.forEach { constructor ->
signatures[constructor] = getExternallyComputedSignature(constructor.constructorId(classNamePrefix))
}
visitDeclarationContainer(classNamePrefix, clazz)
clazz.klibEnumEntries.forEach { enumEntry ->
signatures[enumEntry] = getExternallyComputedSignature(clazz.enumEntryId(enumEntry))
}
}
fragment.pkg?.let {
val packageName = fragment.fqName?.replace('.', '/').orEmpty()
visitDeclarationContainer(if (packageName.isNotEmpty()) "$packageName/" else "", it)
}
}
}
fun classSignature(clazz: KmClass): String? = signatures[clazz]
fun enumEntrySignature(enumEntry: KlibEnumEntry): String? = signatures[enumEntry]
fun constructorSignature(constructor: KmConstructor): String? = signatures[constructor]
fun functionSignature(function: KmFunction): String? = signatures[function]
fun propertySignature(property: KmProperty): String? = signatures[property]
fun propertyGetterSignature(property: KmProperty): String? = getterSignatures[property]
fun propertySetterSignature(property: KmProperty): String? = setterSignatures[property]
fun typeAliasSignature(typeAlias: KmTypeAlias): String? = signatures[typeAlias]
}
@@ -0,0 +1,88 @@
/*
* Copyright 2010-2024 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.kotlinp.klib
import kotlinx.metadata.*
import kotlinx.metadata.internal.common.KmModuleFragment
import kotlinx.metadata.klib.*
import org.jetbrains.kotlin.kotlinp.Kotlinp
import org.jetbrains.kotlin.kotlinp.Settings
import org.jetbrains.kotlin.kotlinp.Printer
class KlibKotlinp(
settings: Settings,
private val signatureComputer: ExternalSignatureComputer?,
) : Kotlinp(settings) {
fun renderModule(module: KlibModuleMetadata, printer: Printer): Unit = with(printer) {
appendLine("library {")
withIndent {
appendCommentedLine("module name: ", module.name)
module.fragments.forEach { renderModuleFragment(it, printer) }
}
appendLine("}")
}
private fun renderModuleFragment(moduleFragment: KmModuleFragment, printer: Printer): Unit = with(printer) {
appendLine()
appendLine("library fragment {")
withIndent {
moduleFragment.fqName?.let {
appendCommentedLine("package name: ", it.ifEmpty { "<root>" })
}
if (moduleFragment.className.isNotEmpty()) {
appendLine()
moduleFragment.className.sortIfNeeded { it }.forEach {
appendCommentedLine("class name: ", it)
}
}
moduleFragment.classes.forEach { appendLine(); renderClass(it, printer) }
moduleFragment.pkg?.let { appendLine(); renderPackage(it, printer) }
}
appendLine("}")
}
override fun getAnnotations(clazz: KmClass) = clazz.annotations
override fun getAnnotations(constructor: KmConstructor) = constructor.annotations
override fun getAnnotations(function: KmFunction) = function.annotations
override fun getAnnotations(property: KmProperty) = property.annotations
override fun getGetterAnnotations(property: KmProperty) = property.getterAnnotations
override fun getSetterAnnotations(property: KmProperty) = property.setterAnnotations
override fun getAnnotations(typeParameter: KmTypeParameter) = typeParameter.annotations
override fun getAnnotations(type: KmType) = type.annotations
override fun getAnnotations(valueParameter: KmValueParameter) = valueParameter.annotations
override fun Printer.appendSignatures(clazz: KmClass) = appendSignature { classSignature(clazz) }
override fun Printer.appendSignatures(constructor: KmConstructor) = appendSignature { constructorSignature(constructor) }
override fun Printer.appendSignatures(function: KmFunction) = appendSignature { functionSignature(function) }
override fun Printer.appendSignatures(property: KmProperty) = appendSignature { propertySignature(property) }
override fun Printer.appendGetterSignatures(property: KmProperty) = appendSignature { propertyGetterSignature(property) }
override fun Printer.appendSetterSignatures(property: KmProperty) = appendSignature { propertySetterSignature(property) }
override fun Printer.appendSignatures(typeAlias: KmTypeAlias) = appendSignature { typeAliasSignature(typeAlias) }
private inline fun Printer.appendSignature(extractSignature: ExternalSignatureComputer.() -> String?) {
val signature = signatureComputer?.let(extractSignature) ?: return
appendCommentedLine("signature: ", signature)
}
override fun Printer.appendEnumEntries(clazz: KmClass) {
clazz.klibEnumEntries.forEach { enumEntry ->
appendLine()
appendSignature { enumEntrySignature(enumEntry) }
appendAnnotations(hasAnnotations = null, enumEntry.annotations)
appendLine(enumEntry.name, ",")
}
}
override fun Printer.appendCompileTimeConstant(property: KmProperty): Printer {
val compileTimeValue = property.compileTimeValue
if (compileTimeValue != null) {
renderAnnotationArgument(compileTimeValue, this)
return this
} else {
return append("...")
}
}
}
+2
View File
@@ -207,6 +207,7 @@ include ":kotlin-imports-dumper-compiler-plugin",
":generators:tree-generator-common",
":tools:kotlinp",
":tools:kotlinp-jvm",
":tools:kotlinp-klib",
":kotlin-build-tools-enum-compat",
":kotlin-gradle-compiler-types",
":kotlin-gradle-plugin-api",
@@ -768,6 +769,7 @@ project(':kotlin-assignment-compiler-plugin.embeddable').projectDir = "$rootDir/
project(':tools:kotlinp').projectDir = "$rootDir/libraries/tools/kotlinp" as File
project(':tools:kotlinp-jvm').projectDir = "$rootDir/libraries/tools/kotlinp/jvm" as File
project(':tools:kotlinp-klib').projectDir = "$rootDir/libraries/tools/kotlinp/klib" as File
project(":kotlin-build-tools-enum-compat").projectDir = "$rootDir/libraries/tools/kotlin-build-tools-enum-compat" as File
project(':kotlin-gradle-compiler-types').projectDir = "$rootDir/libraries/tools/kotlin-gradle-compiler-types" as File
project(':kotlin-gradle-plugin-api').projectDir = "$rootDir/libraries/tools/kotlin-gradle-plugin-api" as File