diff --git a/.space/CODEOWNERS b/.space/CODEOWNERS index d629d1c5ca6..37dd56cd78c 100644 --- a/.space/CODEOWNERS +++ b/.space/CODEOWNERS @@ -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" diff --git a/libraries/tools/kotlinp/klib/build.gradle.kts b/libraries/tools/kotlinp/klib/build.gradle.kts new file mode 100644 index 00000000000..b45e7bf92ec --- /dev/null +++ b/libraries/tools/kotlinp/klib/build.gradle.kts @@ -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() } +} diff --git a/libraries/tools/kotlinp/klib/src/org/jetbrains/kotlin/kotlinp/klib/DeclarationId.kt b/libraries/tools/kotlinp/klib/src/org/jetbrains/kotlin/kotlinp/klib/DeclarationId.kt new file mode 100644 index 00000000000..f53087d904c --- /dev/null +++ b/libraries/tools/kotlinp/klib/src/org/jetbrains/kotlin/kotlinp/klib/DeclarationId.kt @@ -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 { + override fun compareTo(other: ClassOrTypeAliasId) = qualifiedName.compareTo(other.qualifiedName) +} + +/** + * Represents a class constructor. + * + * @param qualifiedName the fully qualified name. Examples: `Outer.`, `Outer.Nested.`, `org/sample/Outer.Nested.`. + * @param parameters value parameters. + */ +data class ConstructorId(val qualifiedName: String, val parameters: List) : DeclarationId, Comparable { + 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`, ``, `org/sample/`, `Outer.`, `Outer.Nested.`, + * `org/sample/Outer.Nested.`. + * @param contextReceivers context receivers. + * @param extensionReceiver extension receiver. + * @param parameters value parameters. + */ +data class FunctionId( + val qualifiedName: String, + val contextReceivers: List, + val extensionReceiver: TypeId?, + val parameters: List, + val returnType: TypeId, +) : DeclarationId, Comparable { + 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, + val extensionReceiver: TypeId?, + val returnType: TypeId, +) : DeclarationId, Comparable { + 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 { + 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 { + 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) : Comparable { + override fun compareTo(other: TypeId) = COMPARATOR.compare(this, other) + + companion object { + val UNIT = TypeId(ClassOrTypeAliasId("kotlin/Unit"), emptyList()) + + private val COMPARATOR = compareBy( + { it.classifier as? ClassOrTypeAliasId }, + { it.classifier as? TypeParameterId } + ).thenByList(TypeId::arguments) + } +} + +/** + * Represents a type argument. + */ +sealed class TypeArgumentId : Comparable { + 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 { 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 > Comparator.thenByList( + crossinline selector: (T) -> List, +): Comparator = 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 +} diff --git a/libraries/tools/kotlinp/klib/src/org/jetbrains/kotlin/kotlinp/klib/DeclarationIdOverMetadata.kt b/libraries/tools/kotlinp/klib/src/org/jetbrains/kotlin/kotlinp/klib/DeclarationIdOverMetadata.kt new file mode 100644 index 00000000000..ec31b3a685e --- /dev/null +++ b/libraries/tools/kotlinp/klib/src/org/jetbrains/kotlin/kotlinp/klib/DeclarationIdOverMetadata.kt @@ -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)}", + 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)}", + 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", + parameters = valueParameters.map { it.valueParameterId() } +) diff --git a/libraries/tools/kotlinp/klib/src/org/jetbrains/kotlin/kotlinp/klib/ExternalSignatureComputer.kt b/libraries/tools/kotlinp/klib/src/org/jetbrains/kotlin/kotlinp/klib/ExternalSignatureComputer.kt new file mode 100644 index 00000000000..23f269eb2da --- /dev/null +++ b/libraries/tools/kotlinp/klib/src/org/jetbrains/kotlin/kotlinp/klib/ExternalSignatureComputer.kt @@ -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() + private val getterSignatures = IdentityHashMap() + private val setterSignatures = IdentityHashMap() + + 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] +} diff --git a/libraries/tools/kotlinp/klib/src/org/jetbrains/kotlin/kotlinp/klib/KlibKotlinp.kt b/libraries/tools/kotlinp/klib/src/org/jetbrains/kotlin/kotlinp/klib/KlibKotlinp.kt new file mode 100644 index 00000000000..31b2ea3dac4 --- /dev/null +++ b/libraries/tools/kotlinp/klib/src/org/jetbrains/kotlin/kotlinp/klib/KlibKotlinp.kt @@ -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 { "" }) + } + 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("...") + } + } +} diff --git a/settings.gradle b/settings.gradle index ea401ecca35..63ce1d73336 100644 --- a/settings.gradle +++ b/settings.gradle @@ -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