[kotlinp] Refactor to separate common part from JVM-specific part

This refactoring includes the following:
1. Separate backend-neutral part of the metadata printer `Kotlinp`
   from the part that accesses JVM-specifics: `JvmKotlinp`.
2. Introduce `org.jetbrains.kotlin.kotlinp.Printer`: The component
   that renders both individual `Km*` nodes and the whole metadata
   tree, and does so with the proper indentation.

^KT-62340
This commit is contained in:
Dmitriy Dolovov
2024-02-02 17:24:39 +01:00
committed by Space Team
parent d769328311
commit eec76865a7
14 changed files with 979 additions and 924 deletions
@@ -12,7 +12,6 @@ import kotlinx.metadata.jvm.localDelegatedProperties
import kotlinx.metadata.jvm.signature import kotlinx.metadata.jvm.signature
import org.jetbrains.kotlin.abicmp.reports.MetadataPropertyReport import org.jetbrains.kotlin.abicmp.reports.MetadataPropertyReport
import org.jetbrains.kotlin.abicmp.tasks.GenericMetadataTask import org.jetbrains.kotlin.abicmp.tasks.GenericMetadataTask
import org.jetbrains.kotlin.kotlinp.*
fun loadProperties(container: KmDeclarationContainer) = container.properties.associateBy { it.getterSignature?.toString() ?: it.name } fun loadProperties(container: KmDeclarationContainer) = container.properties.associateBy { it.getterSignature?.toString() ?: it.name }
@@ -28,27 +27,6 @@ fun loadLocalDelegatedProperties(kmPackage: KmPackage) =
fun loadLocalDelegatedProperties(clazz: KotlinClassMetadata.Class) = fun loadLocalDelegatedProperties(clazz: KotlinClassMetadata.Class) =
clazz.kmClass.localDelegatedProperties.associateBy { it.getterSignature?.toString() ?: it.name } clazz.kmClass.localDelegatedProperties.associateBy { it.getterSignature?.toString() ?: it.name }
fun List<KmVersionRequirement>.stringifyRelevantRequirements() =
// older versions of requirements are redundant for compiler with version 1.9 or newer
filter { it.version.major >= 2 || (it.version.major == 1 && it.version.minor >= 8) }
.map(::printVersionRequirement).sorted().joinToString(prefix = "[", postfix = "]")
fun List<KmType>.stringifyTypeListSorted() = map(::printType).sorted().joinToString(prefix = "[", postfix = "]")
fun List<KmTypeParameter>.stringifyTypeParameters() = joinToString(prefix = "<", postfix = ">") { typeParam ->
printTypeParameter(
typeParam,
KotlinpSettings(
isVerbose = true,
sortDeclarations = true
)
)
}
fun List<KmValueParameter>.stringifyValueParameters() = joinToString(prefix = "(", postfix = ")", transform = ::printValueParameter)
fun List<KmAnnotation>.stringifyAnnotations() = joinToString(prefix = "[", postfix = "]", transform = ::renderAnnotation)
inline fun <R, T> checkMetadataMembers( inline fun <R, T> checkMetadataMembers(
metadata1: R, metadata1: R,
metadata2: R, metadata2: R,
@@ -5,11 +5,8 @@
package org.jetbrains.kotlin.abicmp.tasks package org.jetbrains.kotlin.abicmp.tasks
import kotlinx.metadata.ExperimentalContextReceivers import kotlinx.metadata.*
import kotlinx.metadata.hasConstant
import kotlinx.metadata.isVar
import kotlinx.metadata.jvm.* import kotlinx.metadata.jvm.*
import kotlinx.metadata.visibility
import org.jetbrains.kotlin.abicmp.* import org.jetbrains.kotlin.abicmp.*
import org.jetbrains.kotlin.abicmp.checkers.* import org.jetbrains.kotlin.abicmp.checkers.*
import org.jetbrains.kotlin.kotlinp.* import org.jetbrains.kotlin.kotlinp.*
@@ -65,7 +62,7 @@ private val allFunctionMetadataCheckers = listOf(
functionMetadataPropertyChecker("modifiers") { printFunctionModifiers(it) }, functionMetadataPropertyChecker("modifiers") { printFunctionModifiers(it) },
functionMetadataPropertyChecker("typeParameters") { it.typeParameters.stringifyTypeParameters() }, functionMetadataPropertyChecker("typeParameters") { it.typeParameters.stringifyTypeParameters() },
functionMetadataPropertyChecker("receiverParameterType") { functionMetadataPropertyChecker("receiverParameterType") {
it.receiverParameterType?.let { type -> printType(type) } ?: PROPERTY_VAL_STUB it.receiverParameterType?.let(::printType) ?: PROPERTY_VAL_STUB
}, },
functionMetadataPropertyChecker("valueParameters") { it.valueParameters.stringifyValueParameters() }, functionMetadataPropertyChecker("valueParameters") { it.valueParameters.stringifyValueParameters() },
functionMetadataPropertyChecker("contract") { functionMetadataPropertyChecker("contract") {
@@ -90,13 +87,13 @@ private val allPropertyMetadataCheckers = listOf(
propertyMetadataPropertyChecker("isVar") { it.isVar.toString() }, propertyMetadataPropertyChecker("isVar") { it.isVar.toString() },
propertyMetadataPropertyChecker("typeParameters") { it.typeParameters.stringifyTypeParameters() }, propertyMetadataPropertyChecker("typeParameters") { it.typeParameters.stringifyTypeParameters() },
propertyMetadataPropertyChecker("receiverParameterType") { propertyMetadataPropertyChecker("receiverParameterType") {
it.receiverParameterType?.let { type -> printType(type) } ?: PROPERTY_VAL_STUB it.receiverParameterType?.let(::printType) ?: PROPERTY_VAL_STUB
}, },
propertyMetadataPropertyChecker("returnType") { printType(it.returnType) }, propertyMetadataPropertyChecker("returnType") { printType(it.returnType) },
propertyMetadataPropertyChecker("hasConstant") { it.hasConstant.toString() }, propertyMetadataPropertyChecker("hasConstant") { it.hasConstant.toString() },
propertyMetadataPropertyChecker("getterModifiers") { printPropertyAccessorModifiers(it.getter) }, propertyMetadataPropertyChecker("getterModifiers") { printPropertyAccessorModifiers(it.getter) },
propertyMetadataPropertyChecker("setterModifiers") { propertyMetadataPropertyChecker("setterModifiers") {
it.setter?.let { setter -> printPropertyAccessorModifiers(setter) } ?: PROPERTY_VAL_STUB it.setter?.let(::printPropertyAccessorModifiers) ?: PROPERTY_VAL_STUB
}, },
propertyMetadataPropertyChecker("setterValueParameter") { propertyMetadataPropertyChecker("setterValueParameter") {
it.setterParameter?.let { param -> printValueParameter(param) } ?: PROPERTY_VAL_STUB it.setterParameter?.let { param -> printValueParameter(param) } ?: PROPERTY_VAL_STUB
@@ -134,12 +131,12 @@ private val allClassMetadataCheckers = listOf(
classMetadataPropertyChecker("typeParameters") { it.kmClass.typeParameters.stringifyTypeParameters() }, classMetadataPropertyChecker("typeParameters") { it.kmClass.typeParameters.stringifyTypeParameters() },
classMetadataPropertyChecker("superTypes") { classMetadataPropertyChecker("superTypes") {
it.kmClass.supertypes.map { type -> printType(type) }.sorted().joinToString(prefix = "[", postfix = "]") it.kmClass.supertypes.map(::printType).sorted().joinToString(prefix = "[", postfix = "]")
}, },
classMetadataPropertyChecker("companionObject") { it.kmClass.companionObject.toString() }, classMetadataPropertyChecker("companionObject") { it.kmClass.companionObject.toString() },
classMetadataPropertyChecker("inlineClassUnderlyingPropertyName") { it.kmClass.inlineClassUnderlyingPropertyName.toString() }, classMetadataPropertyChecker("inlineClassUnderlyingPropertyName") { it.kmClass.inlineClassUnderlyingPropertyName.toString() },
classMetadataPropertyChecker("inlineClassUnderlyingType") { classMetadataPropertyChecker("inlineClassUnderlyingType") {
it.kmClass.inlineClassUnderlyingType?.let { type -> printType(type) } ?: "---" it.kmClass.inlineClassUnderlyingType?.let(::printType) ?: "---"
}, },
classMetadataPropertyChecker("contextReceiverTypes") { classMetadataPropertyChecker("contextReceiverTypes") {
@OptIn(ExperimentalContextReceivers::class) @OptIn(ExperimentalContextReceivers::class)
@@ -166,15 +163,7 @@ private val allMultifileClassPartMetadataCheckers = listOf(
private val allSyntheticClassMetadataCheckers = listOf( private val allSyntheticClassMetadataCheckers = listOf(
syntheticClassMetadataPropertyChecker("isLambda") { it.isLambda.toString() }, syntheticClassMetadataPropertyChecker("isLambda") { it.isLambda.toString() },
syntheticClassMetadataPropertyChecker("function") { syntheticClassMetadataPropertyChecker("function") {
it.kmLambda?.function?.let { function -> it.kmLambda?.function?.let(::printFunction) ?: PROPERTY_VAL_STUB
printFunction(
function,
KotlinpSettings(
isVerbose = true,
sortDeclarations = true
)
)
} ?: PROPERTY_VAL_STUB
} }
) )
@@ -222,3 +211,38 @@ class CheckerConfiguration(private val enabledExclusively: Set<String>, private
return name !in disabled return name !in disabled
} }
} }
private val kotlinp = JvmKotlinp(Settings(isVerbose = true, sortDeclarations = true))
private fun renderAnnotation(annotation: KmAnnotation) = printString { kotlinp.renderAnnotation(annotation, this) }
private fun List<KmAnnotation>.stringifyAnnotations() = joinToString(prefix = "[", postfix = "]", transform = ::renderAnnotation)
private fun printFunction(function: KmFunction) = printString { kotlinp.renderFunction(function, this) }
private fun printConstructorModifiers(constructor: KmConstructor) = printString { kotlinp.renderConstructorModifiers(constructor, this) }
private fun printFunctionModifiers(function: KmFunction) = printString { kotlinp.renderFunctionModifiers(function, this) }
private fun printPropertyModifiers(property: KmProperty) = printString { kotlinp.renderPropertyModifiers(property, this) }
private fun printPropertyAccessorModifiers(accessorAttributes: KmPropertyAccessorAttributes) =
printString { kotlinp.renderPropertyAccessorModifiers(accessorAttributes, this) }
private fun printType(type: KmType) = printString { kotlinp.renderType(type, this) }
private fun List<KmType>.stringifyTypeListSorted() = map(::printType).sorted().joinToString(prefix = "[", postfix = "]")
private fun printTypeParameter(typeParameter: KmTypeParameter) =
printString { kotlinp.renderTypeParameter(typeParameter, this) }
private fun List<KmTypeParameter>.stringifyTypeParameters() = joinToString(prefix = "<", postfix = ">", transform = ::printTypeParameter)
private fun printValueParameter(valueParameter: KmValueParameter) = printString { kotlinp.renderValueParameter(valueParameter, this) }
private fun List<KmValueParameter>.stringifyValueParameters() = joinToString(prefix = "(", postfix = ")", transform = ::printValueParameter)
@OptIn(ExperimentalContracts::class)
private fun printContract(contract: KmContract) = printString { kotlinp.renderContract(contract, this) }
private fun printVersionRequirement(versionRequirement: KmVersionRequirement) =
printString { kotlinp.renderVersionRequirement(versionRequirement, this) }
private fun List<KmVersionRequirement>.stringifyRelevantRequirements() =
// older versions of requirements are redundant for compiler with version 1.9 or newer
filter { it.version.major >= 2 || (it.version.major == 1 && it.version.minor >= 8) }
.map(::printVersionRequirement).sorted().joinToString(prefix = "[", postfix = "]")
@@ -13,8 +13,8 @@ import org.jetbrains.kotlin.abicmp.isSynthetic
import org.jetbrains.kotlin.abicmp.reports.ClassReport import org.jetbrains.kotlin.abicmp.reports.ClassReport
import org.jetbrains.kotlin.abicmp.reports.ListEntryDiff import org.jetbrains.kotlin.abicmp.reports.ListEntryDiff
import org.jetbrains.kotlin.abicmp.tag import org.jetbrains.kotlin.abicmp.tag
import org.jetbrains.kotlin.kotlinp.Kotlinp import org.jetbrains.kotlin.kotlinp.Settings
import org.jetbrains.kotlin.kotlinp.KotlinpSettings import org.jetbrains.kotlin.kotlinp.JvmKotlinp
import org.jetbrains.kotlin.kotlinp.readKotlinClassHeader import org.jetbrains.kotlin.kotlinp.readKotlinClassHeader
import org.jetbrains.org.objectweb.asm.ClassReader import org.jetbrains.org.objectweb.asm.ClassReader
import org.jetbrains.org.objectweb.asm.ClassWriter import org.jetbrains.org.objectweb.asm.ClassWriter
@@ -50,7 +50,7 @@ class ClassTask(
return classReader.readKotlinClassHeader()?.run { KotlinClassMetadata.readStrict(this) } return classReader.readKotlinClassHeader()?.run { KotlinClassMetadata.readStrict(this) }
} }
val kotlinp = Kotlinp(KotlinpSettings(isVerbose = false, sortDeclarations = true)) val kotlinp = JvmKotlinp(Settings(isVerbose = false, sortDeclarations = true))
val metadata1 = class1.getMetadata() val metadata1 = class1.getMetadata()
val metadata2 = class2.getMetadata() val metadata2 = class2.getMetadata()
@@ -59,8 +59,8 @@ class ClassTask(
if (metadata1 == null || metadata2 == null) { if (metadata1 == null || metadata2 == null) {
report.addMetadataDiff( report.addMetadataDiff(
ListEntryDiff( ListEntryDiff(
metadata1?.run { kotlinp.renderClassFile(metadata1) }, metadata1?.run { kotlinp.printClassFile(metadata1) },
metadata2?.run { kotlinp.renderClassFile(metadata2) }) metadata2?.run { kotlinp.printClassFile(metadata2) })
) )
return return
} }
@@ -0,0 +1,201 @@
/*
* 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
import kotlinx.metadata.*
import kotlinx.metadata.jvm.*
class JvmKotlinp(settings: Settings) : Kotlinp(settings) {
fun printClassFile(classFile: KotlinClassMetadata): String = printString {
when (classFile) {
is KotlinClassMetadata.Class -> renderClass(classFile.kmClass, this)
is KotlinClassMetadata.FileFacade -> renderPackage(classFile.kmPackage, this)
is KotlinClassMetadata.SyntheticClass -> renderSyntheticClass(classFile, this)
is KotlinClassMetadata.MultiFileClassFacade -> renderMultiFileClassFacade(classFile, this)
is KotlinClassMetadata.MultiFileClassPart -> renderMultiFileClassPart(classFile, this)
is KotlinClassMetadata.Unknown -> appendLine("unknown file")
}
}
@OptIn(UnstableMetadataApi::class)
fun printModuleFile(metadata: KotlinModuleMetadata?): String = printString {
if (metadata != null) renderModuleFile(metadata, this)
else appendLine("unsupported file")
}
private fun renderSyntheticClass(clazz: KotlinClassMetadata.SyntheticClass, printer: Printer): Unit = with(printer) {
if (clazz.isLambda) {
appendLine("lambda {")
withIndent {
val lambda = clazz.kmLambda ?: throw KotlinpException("Synthetic class $clazz is not a lambda")
renderFunction(lambda.function, printer)
}
appendLine("}")
} else {
appendLine("synthetic class")
}
}
private fun renderMultiFileClassFacade(clazz: KotlinClassMetadata.MultiFileClassFacade, printer: Printer): Unit = with(printer) {
appendLine("multi-file class {")
withIndent {
for (part in clazz.partClassNames) {
appendCommentedLine(part)
}
}
appendLine("}")
}
private fun renderMultiFileClassPart(clazz: KotlinClassMetadata.MultiFileClassPart, printer: Printer) {
renderPackage(clazz.kmPackage, printer) {
printer.appendCommentedLine("facade: ", clazz.facadeClassName)
}
}
@OptIn(UnstableMetadataApi::class)
fun renderModuleFile(metadata: KotlinModuleMetadata, printer: Printer): Unit = with(printer) {
appendLine("module {")
withIndent {
val module = metadata.kmModule
module.packageParts.forEach { (fqName, kmPackageParts) ->
val presentableFqName = fqName.ifEmpty { "<root>" }
appendLine("package ", presentableFqName, " {")
withIndent {
for (fileFacade in kmPackageParts.fileFacades) {
appendLine(fileFacade)
}
for ((multiFileClassPart, facade) in kmPackageParts.multiFileClassParts) {
appendLine(multiFileClassPart, " (", facade, ")")
}
}
appendLine("}")
}
if (module.optionalAnnotationClasses.isNotEmpty()) {
appendLine()
appendCommentedLine("Optional annotations")
appendLine()
module.optionalAnnotationClasses.forEach { renderClass(it, printer) }
}
}
appendLine("}")
}
override fun getAnnotations(typeParameter: KmTypeParameter) = typeParameter.annotations
override fun getAnnotations(type: KmType) = type.annotations
override fun sortConstructors(constructors: List<KmConstructor>) = constructors.sortedBy { it.signature.toString() }
override fun sortFunctions(functions: List<KmFunction>) = functions.sortedBy { it.signature.toString() }
override fun sortProperties(properties: List<KmProperty>) = properties.sortedBy { it.getterSignature?.toString() ?: it.name }
override fun Printer.appendSignatures(constructor: KmConstructor) {
constructor.signature?.let {
appendCommentedLine("signature: ", it)
}
}
override fun Printer.appendSignatures(function: KmFunction) {
function.signature?.let {
appendCommentedLine("signature: ", it)
}
}
override fun Printer.appendSignatures(property: KmProperty) {
property.fieldSignature?.let {
appendCommentedLine("field: ", it)
}
property.getterSignature?.let {
appendCommentedLine("getter: ", it)
}
property.setterSignature?.let {
appendCommentedLine("setter: ", it)
}
}
override fun Printer.appendCustomAttributes(property: KmProperty) {
property.syntheticMethodForAnnotations?.let {
appendCommentedLine("synthetic method for annotations: ", it)
}
property.syntheticMethodForDelegate?.let {
appendCommentedLine("synthetic method for delegate: ", it)
}
if (property.isMovedFromInterfaceCompanion) {
appendCommentedLine("is moved from interface companion")
}
}
override fun Printer.appendOrigin(clazz: KmClass) {
clazz.anonymousObjectOriginName?.let {
appendCommentedLine("anonymous object origin: ", it)
}
}
override fun Printer.appendOrigin(function: KmFunction) {
function.lambdaClassOriginName?.let {
appendCommentedLine("lambda class origin: ", it)
}
}
override fun Printer.appendCustomAttributes(clazz: KmClass) {
appendExtensions(clazz.localDelegatedProperties, clazz.moduleName)
if (clazz.hasMethodBodiesInInterface) {
appendLine()
appendCommentedLine("has method bodies in interface")
}
if (clazz.isCompiledInCompatibilityMode) {
appendLine()
appendCommentedLine("is compiled in compatibility mode")
}
}
override fun Printer.appendCustomAttributes(pkg: KmPackage) {
appendExtensions(pkg.localDelegatedProperties, pkg.moduleName)
}
private fun Printer.appendExtensions(localDelegatedProperties: List<KmProperty>, moduleName: String?) {
localDelegatedProperties.sortIfNeeded(::sortProperties).forEachIndexed { index, property ->
appendLine()
appendCommentedLine("local delegated property #", index)
// Comment all uncommented lines to not make it look like these properties are declared here
printString { renderProperty(property, this) }
.lineSequence()
.filter { it.isNotBlank() }
.forEach { appendCommentedLine(it) }
}
if (settings.isVerbose) {
moduleName?.let {
appendLine()
appendCommentedLine("module name: ", it)
}
}
}
override fun Printer.appendEnumEntries(clazz: KmClass) {
clazz.enumEntries.forEach { enumEntry ->
appendLine()
appendLine(enumEntry, ",")
}
}
override fun Printer.appendCompileTimeConstant(property: KmProperty): Printer {
return append("...")
}
override fun isRaw(type: KmType) = type.isRaw
override fun renderFlexibleTypeUpperBound(flexibleTypeUpperBound: KmFlexibleTypeUpperBound): String? {
@Suppress("DEPRECATION_ERROR")
return if (flexibleTypeUpperBound.typeFlexibilityId == JvmTypeExtensionVisitor.PLATFORM_TYPE_ID)
printString { appendType(flexibleTypeUpperBound.type) }
else
null
}
}
@@ -1,53 +1,567 @@
/* /*
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors. * 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. * 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 package org.jetbrains.kotlin.kotlinp
import kotlinx.metadata.jvm.KotlinClassMetadata import kotlinx.metadata.*
import kotlinx.metadata.jvm.KotlinModuleMetadata import kotlin.contracts.ExperimentalContracts
import kotlinx.metadata.jvm.UnstableMetadataApi
import java.io.File
class Kotlinp(private val settings: KotlinpSettings) { abstract class Kotlinp(protected val settings: Settings) {
fun renderClassFile(classFile: KotlinClassMetadata): String = fun renderAnnotation(annotation: KmAnnotation, printer: Printer): Unit = with(printer) {
when (classFile) { append(annotation.className)
is KotlinClassMetadata.Class -> ClassPrinter(settings).print(classFile) appendCollectionIfNotEmpty(annotation.arguments.entries, prefix = "(", postfix = ")") { (name, argument) ->
is KotlinClassMetadata.FileFacade -> FileFacadePrinter(settings).print(classFile) append(name, " = ")
is KotlinClassMetadata.SyntheticClass -> { renderAnnotationArgument(argument, printer)
if (classFile.isLambda) LambdaPrinter(settings).print(classFile) }
else buildString { appendLine("synthetic class") } }
fun renderAnnotationArgument(argument: KmAnnotationArgument, printer: Printer): Unit = with(printer) {
fun String.sanitize(quote: Char): String = buildString(length) {
for (c in this@sanitize) {
when (c) {
'\n' -> append("\\n")
'\r' -> append("\\r")
'\t' -> append("\\t")
quote -> append("\\").append(quote)
else -> append(if (c.isISOControl()) "\\u%04x".format(c.code) else c)
}
} }
is KotlinClassMetadata.MultiFileClassFacade -> MultiFileClassFacadePrinter().print(classFile)
is KotlinClassMetadata.MultiFileClassPart -> MultiFileClassPartPrinter(settings).print(classFile)
is KotlinClassMetadata.Unknown -> buildString { appendLine("unknown file") }
} }
when (argument) {
internal fun readClassFile(file: File): Metadata { is KmAnnotationArgument.ByteValue -> append("${argument.value}.toByte()")
return file.readKotlinClassHeader() ?: throw KotlinpException("file is not a Kotlin class file: $file") is KmAnnotationArgument.CharValue -> append("'${argument.value.toString().sanitize(quote = '\'')}'")
} is KmAnnotationArgument.ShortValue -> append("${argument.value}.toShort()")
is KmAnnotationArgument.IntValue -> append(argument.value)
internal fun readMetadata(metadata: Metadata): KotlinClassMetadata { is KmAnnotationArgument.LongValue -> append("${argument.value}L")
return try { is KmAnnotationArgument.FloatValue -> append("${argument.value}f")
KotlinClassMetadata.readLenient(metadata) is KmAnnotationArgument.DoubleValue -> append(argument.value)
} catch (e: IllegalArgumentException) { is KmAnnotationArgument.UByteValue -> append("${argument.value}.toUByte()")
throw KotlinpException("inconsistent Kotlin metadata: ${e.message}") is KmAnnotationArgument.UShortValue -> append("${argument.value}.toUShort()")
is KmAnnotationArgument.UIntValue -> append("${argument.value}u")
is KmAnnotationArgument.ULongValue -> append("${argument.value}uL")
is KmAnnotationArgument.BooleanValue -> append(argument.value.toString())
is KmAnnotationArgument.StringValue -> append("\"${argument.value.sanitize(quote = '"')}\"")
is KmAnnotationArgument.KClassValue -> append("${argument.className}::class")
is KmAnnotationArgument.ArrayKClassValue -> {
repeat(argument.arrayDimensionCount) { append("kotlin/Array<") }
append(argument.className, "::class")
repeat(argument.arrayDimensionCount) { append(">") }
}
is KmAnnotationArgument.EnumValue -> append(argument.enumClassName, '.', argument.enumEntryName)
is KmAnnotationArgument.AnnotationValue -> argument.annotation.let { annotation ->
append(annotation.className)
appendCollection(annotation.arguments.entries, prefix = "(", postfix = ")") { (name, argument) ->
append(name, " = ")
renderAnnotationArgument(argument, printer)
}
}
is KmAnnotationArgument.ArrayValue -> appendCollection(argument.elements, prefix = "[", postfix = "]") { argument ->
renderAnnotationArgument(argument, printer)
}
} }
} }
@OptIn(UnstableMetadataApi::class) protected fun Printer.appendAnnotations(hasAnnotations: Boolean?, annotations: List<KmAnnotation>, onePerLine: Boolean = true) {
internal fun renderModuleFile(metadata: KotlinModuleMetadata?): String = if (hasAnnotations != false) {
if (metadata != null) ModuleFilePrinter(settings).print(metadata) annotations.forEach { annotation ->
else buildString { appendLine("unsupported file") } append("@")
renderAnnotation(annotation, this)
if (onePerLine) appendLine() else append(" ")
}
}
}
@OptIn(UnstableMetadataApi::class) @OptIn(ExperimentalContextReceivers::class)
internal fun readModuleFile(file: File): KotlinModuleMetadata? = fun renderClass(clazz: KmClass, printer: Printer): Unit = with(printer) {
runCatching { KotlinModuleMetadata.read(file.readBytes()) }.getOrNull() appendOrigin(clazz)
appendVersionRequirements(clazz.versionRequirements)
appendSignatures(clazz)
appendAnnotations(clazz.hasAnnotations, getAnnotations(clazz))
appendContextReceiverTypes(clazz.contextReceiverTypes)
append(VISIBILITY_MAP[clazz.visibility])
append(MODALITY_MAP[clazz.modality])
appendFlags(
clazz.isInner to "inner",
clazz.isData to "data",
clazz.isExternal to "external",
clazz.isExpect to "expect",
clazz.isValue to "value",
clazz.isFunInterface to "fun",
)
append(CLASS_KIND_MAP[clazz.kind])
append(clazz.name)
appendTypeParameters(clazz.typeParameters)
appendCollectionIfNotEmpty(clazz.supertypes, prefix = " : ") { appendType(it) }
appendLine(" {")
withIndent {
clazz.constructors.sortIfNeeded(::sortConstructors).forEach { renderConstructor(it, printer) }
appendDeclarationContainerMembers(clazz)
clazz.companionObject?.let {
appendLine()
appendCommentedLine("companion object: $it")
}
clazz.nestedClasses.forEach {
appendLine()
appendCommentedLine("nested class: $it")
}
appendEnumEntries(clazz)
clazz.sealedSubclasses.sortIfNeeded { it }.forEach {
appendLine()
appendCommentedLine("sealed subclass: $it")
}
clazz.inlineClassUnderlyingPropertyName?.let {
appendLine()
appendCommentedLine("underlying property: $it")
}
clazz.inlineClassUnderlyingType?.let {
appendLine()
commented { append("underlying type: ").appendType(it).appendLine() }
}
appendCustomAttributes(clazz)
if (clazz.hasEnumEntries) {
appendLine()
appendCommentedLine("has Enum.entries")
}
}
appendLine("}")
}
private fun Printer.appendDeclarationContainerMembers(container: KmDeclarationContainer) {
container.functions.sortIfNeeded(::sortFunctions).forEach { renderFunction(it, this) }
container.properties.sortIfNeeded(::sortProperties).forEach { renderProperty(it, this) }
container.typeAliases.sortIfNeeded { it.sortedBy(KmTypeAlias::name) }.forEach { renderTypeAlias(it, this) }
}
fun renderConstructor(constructor: KmConstructor, printer: Printer): Unit = with(printer) {
appendLine()
appendVersionRequirements(constructor.versionRequirements)
appendSignatures(constructor)
appendAnnotations(constructor.hasAnnotations, getAnnotations(constructor))
renderConstructorModifiers(constructor, printer)
append("constructor")
appendValueParameters(constructor.valueParameters)
appendLine()
}
fun renderConstructorModifiers(constructor: KmConstructor, printer: Printer): Unit = with(printer) {
append(VISIBILITY_MAP[constructor.visibility])
appendFlags(
constructor.isSecondary to "/* secondary */",
constructor.hasNonStableParameterNames to "/* non-stable parameter names */"
)
}
@OptIn(ExperimentalContextReceivers::class, ExperimentalContracts::class)
fun renderFunction(function: KmFunction, printer: Printer): Unit = with(printer) {
appendLine()
appendOrigin(function)
appendVersionRequirements(function.versionRequirements)
appendSignatures(function)
appendAnnotations(function.hasAnnotations, getAnnotations(function))
appendContextReceiverTypes(function.contextReceiverTypes)
renderFunctionModifiers(function, printer)
append("fun ")
appendTypeParameters(function.typeParameters, postfix = " ")
appendReceiverParameterType(function.receiverParameterType)
append(function.name)
appendValueParameters(function.valueParameters)
append(": ").appendType(function.returnType)
appendLine()
function.contract?.let {
withIndent {
renderContract(it, printer)
appendLine()
}
}
}
fun renderFunctionModifiers(function: KmFunction, printer: Printer): Unit = with(printer) {
append(VISIBILITY_MAP[function.visibility])
append(MODALITY_MAP[function.modality])
append(MEMBER_KIND_MAP[function.kind])
appendFlags(
function.isOperator to "operator",
function.isInfix to "infix",
function.isInline to "inline",
function.isTailrec to "tailrec",
function.isExternal to "external",
function.isSuspend to "suspend",
function.isExpect to "expect",
function.hasNonStableParameterNames to "/* non-stable parameter names */"
)
}
@OptIn(ExperimentalContracts::class)
fun renderContract(contract: KmContract, printer: Printer): Unit = with(printer) {
appendLine("contract {")
withIndent {
contract.effects.forEach { effect ->
renderEffect(effect, printer)
appendLine()
}
}
append("}")
}
@OptIn(ExperimentalContracts::class)
private fun renderEffect(effect: KmEffect, printer: Printer) {
// If there are several arguments, only the first is taken, see ContractDeserializerImpl.deserializeSimpleEffect
fun Printer.appendMeaningfulConstructorArgument(effect: KmEffect): Printer {
effect.constructorArguments.firstOrNull()?.let { append(printEffectExpression(it)) }
return this
}
when (effect.type) {
KmEffectType.RETURNS_CONSTANT -> {
printer.append("returns(").appendMeaningfulConstructorArgument(effect).append(")")
}
KmEffectType.CALLS -> {
printer.append("callsInPlace(").appendMeaningfulConstructorArgument(effect)
effect.invocationKind?.let {
printer.append(", InvocationKind.${it.name}")
}
printer.append(")")
}
KmEffectType.RETURNS_NOT_NULL -> {
printer.append("returnsNotNull()")
}
}
effect.conclusion?.let {
printer.append(" implies (", printEffectExpression(it), ")")
}
}
@OptIn(ExperimentalContracts::class)
private fun printEffectExpression(effectExpression: KmEffectExpression): String {
val andArguments = effectExpression.andArguments.map(::printEffectExpression)
val orArguments = effectExpression.orArguments.map(::printEffectExpression)
fun wrapIfNeeded(s: String): String =
// A simple heuristic to avoid wrapping into unnecessary parentheses
if ('&' in s || '|' in s) "($s)" else s
return printString {
append(
effectExpression.constantValue?.let { it.value.toString() }
?: effectExpression.parameterIndex?.let { "p#$it" }
?: ""
)
effectExpression.isInstanceType?.let { isInstanceType ->
append(" ")
if (effectExpression.isNegated) append("!")
append("is ").appendType(isInstanceType)
}
if (effectExpression.isNullCheckPredicate) {
append(if (effectExpression.isNegated) " != " else " == ")
append("null")
}
if (orArguments.isEmpty()) {
for (andArgument in andArguments) {
if (!isEmpty) append(" && ")
append(wrapIfNeeded(andArgument))
}
}
if (andArguments.isEmpty()) {
for (orArgument in orArguments) {
if (!isEmpty) append(" || ")
append(wrapIfNeeded(orArgument))
}
}
}
}
fun renderPackage(pkg: KmPackage, printer: Printer, appendPackageAttributes: () -> Unit = {}): Unit = with(printer) {
appendLine("package {")
withIndent {
appendPackageAttributes()
appendDeclarationContainerMembers(pkg)
appendCustomAttributes(pkg)
}
appendLine("}")
}
@OptIn(ExperimentalContextReceivers::class)
fun renderProperty(property: KmProperty, printer: Printer): Unit = with(printer) {
appendLine()
appendVersionRequirements(property.versionRequirements)
appendSignatures(property)
appendCustomAttributes(property)
appendAnnotations(property.hasAnnotations, getAnnotations(property))
appendContextReceiverTypes(property.contextReceiverTypes)
renderPropertyModifiers(property, printer)
append(if (property.isVar) "var " else "val ")
appendTypeParameters(property.typeParameters, postfix = " ")
appendReceiverParameterType(property.receiverParameterType)
append(property.name)
append(": ").appendType(property.returnType)
if (property.hasConstant) {
append(" /* = ").appendCompileTimeConstant(property).append(" */")
}
appendLine()
withIndent {
appendGetterSignatures(property)
appendAnnotations(property.getter.hasAnnotations, getGetterAnnotations(property))
renderPropertyAccessorModifiers(property.getter, printer)
appendLine("get")
property.setter?.let { setter ->
appendSetterSignatures(property)
appendAnnotations(setter.hasAnnotations, getSetterAnnotations(property))
renderPropertyAccessorModifiers(setter, printer)
append("set")
property.setterParameter?.let {
appendValueParameters(listOf(it))
}
appendLine()
}
}
}
fun renderPropertyModifiers(property: KmProperty, printer: Printer): Unit = with(printer) {
append(VISIBILITY_MAP[property.visibility])
append(MODALITY_MAP[property.modality])
append(MEMBER_KIND_MAP[property.kind])
appendFlags(
property.isConst to "const",
property.isLateinit to "lateinit",
property.isExternal to "external",
property.isDelegated to "/* delegated */",
property.isExpect to "expect"
)
}
fun renderPropertyAccessorModifiers(accessorAttributes: KmPropertyAccessorAttributes, printer: Printer): Unit = with(printer) {
append(VISIBILITY_MAP[accessorAttributes.visibility])
append(MODALITY_MAP[accessorAttributes.modality])
appendFlags(
accessorAttributes.isNotDefault to "/* non-default */",
accessorAttributes.isExternal to "external",
accessorAttributes.isInline to "inline"
)
}
fun renderTypeAlias(typeAlias: KmTypeAlias, printer: Printer): Unit = with(printer) {
appendLine()
appendVersionRequirements(typeAlias.versionRequirements)
appendSignatures(typeAlias)
appendAnnotations(typeAlias.hasAnnotations, typeAlias.annotations)
append(VISIBILITY_MAP[typeAlias.visibility], "typealias ", typeAlias.name)
appendTypeParameters(typeAlias.typeParameters)
append(" = ").appendType(typeAlias.underlyingType)
append(" /* = ").appendType(typeAlias.expandedType).append(" */")
appendLine()
}
fun renderTypeParameter(typeParameter: KmTypeParameter, printer: Printer): Unit = with(printer) {
appendFlags(typeParameter.isReified to "reified")
appendAnnotations(hasAnnotations = null, getAnnotations(typeParameter), onePerLine = false)
if (typeParameter.variance != KmVariance.INVARIANT) {
append(typeParameter.variance.name.lowercase()).append(" ")
}
append("T#${typeParameter.id}")
if (settings.isVerbose) {
append(" /* ${typeParameter.name} */")
}
appendCollectionIfNotEmpty(typeParameter.upperBounds, separator = " & ", prefix = " : ") { appendType(it) }
}
private fun Printer.appendTypeParameters(typeParameters: List<KmTypeParameter>, postfix: String = "") {
appendCollectionIfNotEmpty(typeParameters, prefix = "<", postfix = ">$postfix") { renderTypeParameter(it, this) }
}
fun renderType(type: KmType, printer: Printer) {
val classifier = when (val cls = type.classifier) {
is KmClassifier.Class -> cls.name
is KmClassifier.TypeParameter -> "T#${cls.id}"
is KmClassifier.TypeAlias -> "${cls.name}$TYPE_ALIAS_MARKER"
}
val outerType = type.outerType
val abbreviatedType = type.abbreviatedType
val platformTypeUpperBound = type.flexibleTypeUpperBound?.let { renderFlexibleTypeUpperBound(it) }
printer += printString {
appendAnnotations(hasAnnotations = null, getAnnotations(type), onePerLine = false)
appendFlags(
isRaw(type) to "/* raw */",
type.isSuspend to "suspend"
)
if (outerType != null) {
appendType(outerType).append(".").append(classifier.substringAfterLast('.'))
} else {
append(classifier)
}
appendCollectionIfNotEmpty(type.arguments, prefix = "<", postfix = ">") { argument ->
if (argument == KmTypeProjection.STAR) {
append("*")
} else {
val (variance, argumentType) = argument
if (variance == null || argumentType == null)
throw IllegalArgumentException("Variance and type must be set for non-star type projection")
if (variance != KmVariance.INVARIANT) {
append(variance.name.lowercase()).append(" ")
}
appendType(argumentType)
}
}
if (type.isNullable) append("?")
if (type.isDefinitelyNonNull) append(" & Any")
if (abbreviatedType != null) append(" /* = ").appendType(abbreviatedType).append(" */")
if (platformTypeUpperBound == "$this?") {
append("!")
} else if (platformTypeUpperBound != null) {
append("..").append(platformTypeUpperBound)
}
}
}
protected fun Printer.appendType(type: KmType): Printer {
renderType(type, this)
return this
}
private fun Printer.appendContextReceiverTypes(contextReceiverTypes: List<KmType>) {
appendCollectionIfNotEmpty(contextReceiverTypes, prefix = "context(", postfix = ")\n") { appendType(it) }
}
private fun Printer.appendReceiverParameterType(receiverParameterType: KmType?) {
receiverParameterType?.let {
appendType(it).append(".")
}
}
fun renderValueParameter(valueParameter: KmValueParameter, printer: Printer): Unit = with(printer) {
appendAnnotations(valueParameter.hasAnnotations, getAnnotations(valueParameter), onePerLine = false)
appendFlags(
valueParameter.isCrossinline to "crossinline",
valueParameter.isNoinline to "noinline"
)
val varargElementType = valueParameter.varargElementType
if (varargElementType != null) {
append("vararg ", valueParameter.name, ": ").appendType(varargElementType)
append(" /* ").appendType(valueParameter.type).append(" */")
} else {
append(valueParameter.name, ": ").appendType(valueParameter.type)
}
if (valueParameter.declaresDefaultValue) {
append(" /* = ... */")
}
}
private fun Printer.appendValueParameters(valueParameters: List<KmValueParameter>) {
appendCollection(valueParameters, prefix = "(", postfix = ")") { renderValueParameter(it, this) }
}
fun renderVersionRequirement(versionRequirement: KmVersionRequirement, printer: Printer) {
val version = with(versionRequirement.version) { "$major.$minor.$patch" }
val kind = when (versionRequirement.kind) {
KmVersionRequirementVersionKind.LANGUAGE_VERSION -> "language version "
KmVersionRequirementVersionKind.COMPILER_VERSION -> "compiler version "
KmVersionRequirementVersionKind.API_VERSION -> "API version "
KmVersionRequirementVersionKind.UNKNOWN -> "unknown requirement "
}
val remainder = listOfNotNull(
"level=${versionRequirement.level}",
versionRequirement.errorCode?.let { "errorCode=$it" },
versionRequirement.message?.let { "message=\"$it\"" }
).joinToString(prefix = " (", postfix = ")")
printer.append("requires ", kind, version, remainder)
}
private fun Printer.appendVersionRequirements(versionRequirements: List<KmVersionRequirement>) {
versionRequirements.forEach { versionRequirement ->
commented {
renderVersionRequirement(versionRequirement, this)
appendLine()
}
}
}
protected inline fun <T : Any> List<T>.sortIfNeeded(sorter: (List<T>) -> List<T>): List<T> =
if (settings.sortDeclarations) sorter(this) else this
companion object {
private const val TYPE_ALIAS_MARKER = '^'
internal val VISIBILITY_MAP = mapOf(
Visibility.INTERNAL to "internal ",
Visibility.PRIVATE to "private ",
Visibility.PRIVATE_TO_THIS to "private ",
Visibility.PROTECTED to "protected ",
Visibility.PUBLIC to "public ",
Visibility.LOCAL to "local "
)
internal val MODALITY_MAP = mapOf(
Modality.FINAL to "final ",
Modality.OPEN to "open ",
Modality.ABSTRACT to "abstract ",
Modality.SEALED to "sealed "
)
internal val CLASS_KIND_MAP = mapOf(
ClassKind.CLASS to "class ",
ClassKind.INTERFACE to "interface ",
ClassKind.ENUM_CLASS to "enum class ",
ClassKind.ENUM_ENTRY to "enum entry ",
ClassKind.ANNOTATION_CLASS to "annotation class ",
ClassKind.OBJECT to "object ",
ClassKind.COMPANION_OBJECT to "companion object "
)
internal val MEMBER_KIND_MAP = mapOf(
MemberKind.DECLARATION to "",
MemberKind.FAKE_OVERRIDE to "/* fake override */ ",
MemberKind.DELEGATION to "/* delegation */ ",
MemberKind.SYNTHESIZED to "/* synthesized */ ",
)
}
protected open fun getAnnotations(clazz: KmClass): List<KmAnnotation> = emptyList()
protected open fun getAnnotations(constructor: KmConstructor): List<KmAnnotation> = emptyList()
protected open fun getAnnotations(function: KmFunction): List<KmAnnotation> = emptyList()
protected open fun getAnnotations(property: KmProperty): List<KmAnnotation> = emptyList()
protected open fun getGetterAnnotations(property: KmProperty): List<KmAnnotation> = emptyList()
protected open fun getSetterAnnotations(property: KmProperty): List<KmAnnotation> = emptyList()
protected abstract fun getAnnotations(typeParameter: KmTypeParameter): List<KmAnnotation>
protected abstract fun getAnnotations(type: KmType): List<KmAnnotation>
protected open fun getAnnotations(valueParameter: KmValueParameter): List<KmAnnotation> = emptyList()
protected open fun sortConstructors(constructors: List<KmConstructor>): List<KmConstructor> = constructors
protected open fun sortFunctions(functions: List<KmFunction>): List<KmFunction> = functions
protected open fun sortProperties(properties: List<KmProperty>): List<KmProperty> = properties
protected open fun Printer.appendSignatures(clazz: KmClass) = Unit
protected open fun Printer.appendSignatures(constructor: KmConstructor) = Unit
protected open fun Printer.appendSignatures(function: KmFunction) = Unit
protected open fun Printer.appendSignatures(property: KmProperty) = Unit
protected open fun Printer.appendGetterSignatures(property: KmProperty) = Unit
protected open fun Printer.appendSetterSignatures(property: KmProperty) = Unit
protected open fun Printer.appendSignatures(typeAlias: KmTypeAlias) = Unit
protected open fun Printer.appendOrigin(clazz: KmClass) = Unit
protected open fun Printer.appendOrigin(function: KmFunction) = Unit
protected abstract fun Printer.appendEnumEntries(clazz: KmClass)
protected open fun Printer.appendCustomAttributes(clazz: KmClass) = Unit
protected open fun Printer.appendCustomAttributes(pkg: KmPackage) = Unit
protected open fun Printer.appendCustomAttributes(property: KmProperty) = Unit
protected abstract fun Printer.appendCompileTimeConstant(property: KmProperty): Printer
protected open fun isRaw(type: KmType): Boolean = false
protected open fun renderFlexibleTypeUpperBound(flexibleTypeUpperBound: KmFlexibleTypeUpperBound): String? = null
} }
data class KotlinpSettings(
val isVerbose: Boolean,
val sortDeclarations: Boolean
)
@@ -1,5 +1,5 @@
/* /*
* Copyright 2000-2018 JetBrains s.r.o. and Kotlin Programming Language contributors. * 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. * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/ */
@@ -1,5 +1,5 @@
/* /*
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors. * 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. * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/ */
@@ -35,7 +35,7 @@ object Main {
} }
} }
val kotlinp = Kotlinp(KotlinpSettings(isVerbose = verbose, sortDeclarations = sort)) val kotlinp = JvmKotlinp(Settings(isVerbose = verbose, sortDeclarations = sort))
for (path in paths) { for (path in paths) {
val file = File(path) val file = File(path)
@@ -43,8 +43,8 @@ object Main {
val text = try { val text = try {
when (file.extension) { when (file.extension) {
"class" -> kotlinp.renderClassFile(kotlinp.readMetadata(kotlinp.readClassFile(file))) "class" -> kotlinp.printClassFile(readMetadata(readClassFile(file)))
"kotlin_module" -> @OptIn(UnstableMetadataApi::class) kotlinp.renderModuleFile(kotlinp.readModuleFile(file)) "kotlin_module" -> @OptIn(UnstableMetadataApi::class) kotlinp.printModuleFile(readModuleFile(file))
else -> throw KotlinpException("only .class and .kotlin_module files are supported") else -> throw KotlinpException("only .class and .kotlin_module files are supported")
} }
} catch (e: IOException) { } catch (e: IOException) {
@@ -0,0 +1,127 @@
/*
* 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
inline fun printString(builder: StringBuilderPrinter.() -> Unit): String {
return StringBuilderPrinter().apply(builder).toString()
}
abstract class Printer : Appendable {
private var indentLevel = 0
private var commentsMode = false
private var onEmptyLine = true
protected abstract val wrapped: Appendable
operator fun plusAssign(value: Any?) {
append(value.toString())
}
override fun append(charSequence: CharSequence, start: Int, end: Int): Printer = append(charSequence.subSequence(start, end))
override fun append(charSequence: CharSequence): Printer {
for (ch in charSequence) append(ch)
return this
}
override fun append(ch: Char): Printer {
if (ch == '\n') {
wrapped.append('\n')
onEmptyLine = true
} else {
if (onEmptyLine) {
onEmptyLine = false
repeat(indentLevel) { wrapped.append(INDENT_UNIT) }
if (commentsMode) wrapped.append("// ")
}
wrapped.append(ch)
}
return this
}
fun append(vararg values: Any?): Printer {
for (value in values) {
append(value.toString())
}
return this
}
fun appendLine(vararg values: Any?): Printer {
return append(*values).append('\n')
}
inline fun <T> Printer.appendCollection(
items: Collection<T>,
separator: String = ", ",
prefix: String = "",
postfix: String = "",
appendElement: Printer.(T) -> Unit,
) {
append(prefix)
items.forEachIndexed { index, item ->
if (index > 0) append(separator)
appendElement(item)
}
append(postfix)
}
inline fun <T> Printer.appendCollectionIfNotEmpty(
items: Collection<T>,
separator: String = ", ",
prefix: String = "",
postfix: String = "",
appendElement: Printer.(T) -> Unit,
) {
if (items.isEmpty()) return
appendCollection(items, separator, prefix, postfix, appendElement)
}
fun appendFlags(vararg modifiers: Pair<Boolean, String>) {
for ((condition, token) in modifiers) {
if (condition) {
append(token, " ")
}
}
}
fun appendCommentedLine(vararg values: Any?): Printer {
commented { appendLine(*values) }
return this
}
fun commented(block: Printer.() -> Unit) {
val hadCommentsMode = commentsMode
try {
commentsMode = true
this.block()
} finally {
commentsMode = hadCommentsMode
}
}
fun withIndent(block: Printer.() -> Unit) {
try {
indentLevel++
this.block()
} finally {
indentLevel--
}
}
companion object {
private const val INDENT_UNIT = " "
}
}
class StringBuilderPrinter : Printer() {
override val wrapped = StringBuilder()
val isEmpty: Boolean get() = wrapped.isEmpty()
override fun toString() = wrapped.toString()
}
fun Printer(output: Appendable): Printer = DelegatingBuilder(output)
private class DelegatingBuilder(override val wrapped: Appendable) : Printer()
@@ -0,0 +1,11 @@
/*
* 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
data class Settings(
val isVerbose: Boolean,
val sortDeclarations: Boolean
)
@@ -1,819 +0,0 @@
/*
* 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.kotlinp
import kotlinx.metadata.*
import kotlinx.metadata.jvm.*
import kotlin.contracts.ExperimentalContracts
private object SpecialCharacters {
const val TYPE_ALIAS_MARKER = '^'
}
fun printFunction(function: KmFunction, settings: KotlinpSettings): String {
return StringBuilder().also { visitFunction(function, settings, it) }.toString()
}
@OptIn(ExperimentalContextReceivers::class, ExperimentalContracts::class)
private fun visitFunction(
function: KmFunction,
settings: KotlinpSettings,
sb: StringBuilder
) {
sb.appendLine()
function.lambdaClassOriginName?.let {
sb.appendLine(" // lambda class origin: $it")
}
function.versionRequirements.map(::printVersionRequirement).forEach { versionRequirement ->
sb.appendLine(" // $versionRequirement")
}
function.signature?.let {
sb.appendLine(" // signature: $it")
}
if (function.contextReceiverTypes.isNotEmpty()) {
sb.appendLine(function.contextReceiverTypes.joinToString(prefix = " context(", postfix = ")", transform = ::printType))
}
sb.append(" ")
sb.appendFunctionModifiers(function)
sb.append("fun ")
if (function.typeParameters.isNotEmpty()) {
function.typeParameters.joinTo(sb, prefix = "<", postfix = ">", transform = { printTypeParameter(it, settings) })
sb.append(" ")
}
function.receiverParameterType?.let {
sb.append(printType(it)).append(".")
}
sb.append(function.name)
function.valueParameters.joinTo(sb, prefix = "(", postfix = ")", transform = ::printValueParameter)
sb.append(": ").append(printType(function.returnType))
sb.appendLine()
function.contract?.let {
sb.appendLine(" ${printContract(it)}")
}
}
@OptIn(ExperimentalContextReceivers::class)
private fun visitProperty(
property: KmProperty,
settings: KotlinpSettings,
sb: StringBuilder
) {
sb.appendLine()
property.versionRequirements.map(::printVersionRequirement).forEach { versionRequirement ->
sb.appendLine(" // $versionRequirement")
}
if (property.fieldSignature != null) {
sb.appendLine(" // field: ${property.fieldSignature}")
}
if (property.getterSignature != null) {
sb.appendLine(" // getter: ${property.getterSignature}")
}
if (property.setterSignature != null) {
sb.appendLine(" // setter: ${property.setterSignature}")
}
if (property.syntheticMethodForAnnotations != null) {
sb.appendLine(" // synthetic method for annotations: ${property.syntheticMethodForAnnotations}")
}
if (property.syntheticMethodForDelegate != null) {
sb.appendLine(" // synthetic method for delegate: ${property.syntheticMethodForDelegate}")
}
if (property.isMovedFromInterfaceCompanion) {
sb.appendLine(" // is moved from interface companion")
}
if (property.contextReceiverTypes.isNotEmpty()) {
sb.appendLine(property.contextReceiverTypes.joinToString(prefix = " context(", postfix = ")", transform = ::printType))
}
sb.append(" ")
sb.appendPropertyModifiers(property)
sb.append(if (property.isVar) "var " else "val ")
if (property.typeParameters.isNotEmpty()) {
property.typeParameters.joinTo(sb, prefix = "<", postfix = ">", transform = { printTypeParameter(it, settings) })
sb.append(" ")
}
property.receiverParameterType?.let {
sb.append(printType(it)).append(".")
}
sb.append(property.name)
sb.append(": ").append(property.returnType.let(::printType))
if (property.hasConstant) {
sb.append(" /* = ... */")
}
sb.appendLine()
sb.append(" ")
sb.appendPropertyAccessorModifiers(property.getter)
sb.appendLine("get")
val setter = property.setter
if (setter != null) {
sb.append(" ")
sb.appendPropertyAccessorModifiers(setter)
sb.append("set")
property.setterParameter?.let {
sb.append("(").append(printValueParameter(it)).append(")")
}
sb.appendLine()
}
}
private fun visitConstructor(constructor: KmConstructor, sb: StringBuilder) {
sb.appendLine()
constructor.versionRequirements.map(::printVersionRequirement).forEach { versionRequirement ->
sb.appendLine(" // $versionRequirement")
}
if (constructor.signature != null) {
sb.appendLine(" // signature: ${constructor.signature}")
}
sb.append(" ")
sb.appendConstructorModifiers(constructor)
sb.append("constructor(")
constructor.valueParameters.joinTo(sb, transform = ::printValueParameter)
sb.appendLine(")")
}
private fun visitTypeAlias(
typeAlias: KmTypeAlias,
settings: KotlinpSettings,
sb: StringBuilder
) {
sb.appendLine()
typeAlias.versionRequirements.map(::printVersionRequirement).forEach { versionRequirement ->
sb.appendLine(" // $versionRequirement")
}
typeAlias.annotations.forEach { annotation ->
sb.append(" ").append("@").append(renderAnnotation(annotation)).appendLine()
}
sb.append(" ")
sb.append(VISIBILITY_MAP[typeAlias.visibility])
sb.append("typealias ").append(typeAlias.name)
if (typeAlias.typeParameters.isNotEmpty()) {
typeAlias.typeParameters.joinTo(sb, prefix = "<", postfix = ">") { printTypeParameter(it, settings) }
}
sb.append(" = ").append(typeAlias.underlyingType.let(::printType))
sb.append(" /* = ").append(typeAlias.expandedType.let(::printType)).append(" */")
sb.appendLine()
}
fun printType(type: KmType): String {
val classifier = when (val cls = type.classifier) {
is KmClassifier.Class -> cls.name
is KmClassifier.TypeParameter -> "T#${cls.id}"
is KmClassifier.TypeAlias -> "${cls.name}${SpecialCharacters.TYPE_ALIAS_MARKER}"
}
val arguments = mutableListOf<String>()
type.arguments.forEach { argument ->
arguments += if (argument == KmTypeProjection.STAR) {
"*"
} else {
val (variance, argumentType) = argument
if (variance == null || argumentType == null)
throw IllegalArgumentException("Variance and type must be set for non-star type projection")
val argumentTypeString = printType(argumentType)
buildString {
if (variance != KmVariance.INVARIANT) {
append(variance.name.lowercase()).append(" ")
}
append(argumentTypeString)
}
}
}
val abbreviatedType = type.abbreviatedType?.let(::printType)
val outerType = type.outerType?.let(::printType)
val platformTypeUpperBound = type.flexibleTypeUpperBound?.let {
@Suppress("DEPRECATION_ERROR")
(if (it.typeFlexibilityId == JvmTypeExtensionVisitor.PLATFORM_TYPE_ID) {
printType(it.type)
} else null)
}
return buildString {
for (annotation in type.annotations) {
append("@").append(renderAnnotation(annotation)).append(" ")
}
if (type.isRaw) {
append("/* raw */ ")
}
appendFlags(type.isSuspend to "suspend")
if (outerType != null) {
append(outerType).append(".").append(classifier.substringAfterLast('.'))
} else {
append(classifier)
}
if (arguments.isNotEmpty()) {
arguments.joinTo(this, prefix = "<", postfix = ">")
}
if (type.isNullable) {
append("?")
}
if (type.isDefinitelyNonNull) {
append(" & Any")
}
if (abbreviatedType != null) {
append(" /* = ").append(abbreviatedType).append(" */")
}
if (platformTypeUpperBound == "$this?") {
append("!")
} else if (platformTypeUpperBound != null) {
append("..").append(platformTypeUpperBound)
}
}
}
fun printTypeParameter(
typeParameter: KmTypeParameter,
settings: KotlinpSettings
): String = buildString {
appendFlags(typeParameter.isReified to "reified")
for (annotation in typeParameter.annotations) {
append("@").append(renderAnnotation(annotation)).append(" ")
}
if (typeParameter.variance != KmVariance.INVARIANT) {
append(typeParameter.variance.name.lowercase()).append(" ")
}
append("T#${typeParameter.id}")
if (settings.isVerbose) {
append(" /* ${typeParameter.name} */")
}
if (typeParameter.upperBounds.isNotEmpty()) {
typeParameter.upperBounds.joinTo(this, separator = " & ", prefix = " : ", transform = ::printType)
}
}
fun printValueParameter(
valueParameter: KmValueParameter
): String {
val type = printType(valueParameter.type)
val varargElementType = valueParameter.varargElementType?.let(::printType)
return buildString {
appendValueParameterModifiers(valueParameter)
if (varargElementType != null) {
append("vararg ").append(valueParameter.name).append(": ").append(varargElementType).append(" /* ").append(type).append(" */")
} else {
append(valueParameter.name).append(": ").append(type)
}
if (valueParameter.declaresDefaultValue) {
append(" /* = ... */")
}
}
}
fun renderAnnotation(annotation: KmAnnotation): String =
annotation.className + if (annotation.arguments.isEmpty()) "" else
annotation.arguments.entries.joinToString(prefix = "(", postfix = ")") { (name, argument) ->
"$name = ${renderAnnotationArgument(argument)}"
}
private fun renderAnnotationArgument(arg: KmAnnotationArgument): String =
when (arg) {
is KmAnnotationArgument.ByteValue -> arg.value.toString() + ".toByte()"
is KmAnnotationArgument.CharValue -> "'${arg.value.toString().sanitize(quote = '\'')}'"
is KmAnnotationArgument.ShortValue -> arg.value.toString() + ".toShort()"
is KmAnnotationArgument.IntValue -> arg.value.toString()
is KmAnnotationArgument.LongValue -> arg.value.toString() + "L"
is KmAnnotationArgument.FloatValue -> arg.value.toString() + "f"
is KmAnnotationArgument.DoubleValue -> arg.value.toString()
is KmAnnotationArgument.UByteValue -> arg.value.toString() + ".toUByte()"
is KmAnnotationArgument.UShortValue -> arg.value.toString() + ".toUShort()"
is KmAnnotationArgument.UIntValue -> arg.value.toString() + "u"
is KmAnnotationArgument.ULongValue -> arg.value.toString() + "uL"
is KmAnnotationArgument.BooleanValue -> arg.value.toString()
is KmAnnotationArgument.StringValue -> "\"${arg.value.sanitize(quote = '"')}\""
is KmAnnotationArgument.KClassValue -> "${arg.className}::class"
is KmAnnotationArgument.ArrayKClassValue -> buildString {
repeat(arg.arrayDimensionCount) { append("kotlin/Array<") }
append(arg.className).append("::class")
repeat(arg.arrayDimensionCount) { append(">") }
}
is KmAnnotationArgument.EnumValue -> "${arg.enumClassName}.${arg.enumEntryName}"
is KmAnnotationArgument.AnnotationValue -> arg.annotation.let { annotation ->
val args = annotation.arguments.entries.joinToString { (name, argument) ->
"$name = ${renderAnnotationArgument(argument)}"
}
"${annotation.className}($args)"
}
is KmAnnotationArgument.ArrayValue -> arg.elements.joinToString(
prefix = "[",
postfix = "]",
transform = ::renderAnnotationArgument
)
}
private fun String.sanitize(quote: Char): String =
buildString(length) {
for (c in this@sanitize) {
when (c) {
'\n' -> append("\\n")
'\r' -> append("\\r")
'\t' -> append("\\t")
quote -> append("\\").append(quote)
else -> append(if (c.isISOControl()) "\\u%04x".format(c.code) else c)
}
}
}
fun printVersionRequirement(versionRequirement: KmVersionRequirement): String {
val version = with(versionRequirement.version) { "$major.$minor.$patch" }
return buildString {
append("requires ").append(
when (versionRequirement.kind) {
KmVersionRequirementVersionKind.LANGUAGE_VERSION -> "language version"
KmVersionRequirementVersionKind.COMPILER_VERSION -> "compiler version"
KmVersionRequirementVersionKind.API_VERSION -> "API version"
KmVersionRequirementVersionKind.UNKNOWN -> "unknown requirement"
}
).append(" ").append(version)
listOfNotNull(
"level=${versionRequirement.level}",
versionRequirement.errorCode?.let { "errorCode=$it" },
versionRequirement.message?.let { "message=\"$it\"" }
).joinTo(this, prefix = " (", postfix = ")")
}
}
private fun StringBuilder.appendDeclarationContainerExtensions(
settings: KotlinpSettings,
localDelegatedProperties: List<StringBuilder>,
moduleName: String?
) {
for ((i, sb) in localDelegatedProperties.withIndex()) {
appendLine()
appendLine(" // local delegated property #$i")
for (line in sb.lineSequence()) {
if (line.isBlank()) continue
// Comment all uncommented lines to not make it look like these properties are declared here
appendLine(
if (line.startsWith(" ") && !line.startsWith(" //")) line.replaceFirst(" ", " // ")
else line
)
}
}
if (settings.isVerbose && moduleName != null) {
appendLine()
appendLine(" // module name: $moduleName")
}
}
private fun <T, R : Comparable<R>> Iterable<T>.sortIfNeededBy(settings: KotlinpSettings, selector: (T) -> R?): Iterable<T> {
return if (settings.sortDeclarations) sortedBy(selector) else this
}
@ExperimentalContracts
fun printContract(kmContract: KmContract): String = buildString {
appendLine("contract {")
kmContract.effects.map(::printEffect).forEach { effect ->
appendLine(" $effect")
}
append(" }")
}
@ExperimentalContracts
private fun printEffect(
kmEffect: KmEffect
): String {
var argument: String? = null
kmEffect.constructorArguments.forEach {
// If there are several arguments, only the first is taken, see ContractDeserializerImpl.deserializeSimpleEffect
if (argument == null) {
argument = printEffectExpression(it)
}
}
val conclusion: String? = kmEffect.conclusion?.let(::printEffectExpression)
return buildString {
when (kmEffect.type) {
KmEffectType.RETURNS_CONSTANT -> {
append("returns(")
if (argument != null) {
append(argument)
}
append(")")
}
KmEffectType.CALLS -> {
append("callsInPlace($argument")
kmEffect.invocationKind?.let {
append(", InvocationKind.${it.name}")
}
append(")")
}
KmEffectType.RETURNS_NOT_NULL -> {
append("returnsNotNull()")
}
}
if (conclusion != null) {
append(" implies ($conclusion)")
}
}
}
@ExperimentalContracts
private fun printEffectExpression(effectExpression: KmEffectExpression): String {
val parameterIndex: Int? = effectExpression.parameterIndex
val constantValue: List<Any?>? = effectExpression.constantValue?.let { listOf(it.value) }
val andArguments = effectExpression.andArguments.map(::printEffectExpression)
val orArguments = effectExpression.orArguments.map(::printEffectExpression)
fun wrapIfNeeded(s: String): String =
// A simple heuristic to avoid wrapping into unnecessary parentheses
if ('&' in s || '|' in s) "($s)" else s
return buildString {
append(
when {
constantValue != null -> constantValue.single().toString()
parameterIndex != null -> "p#$parameterIndex"
else -> ""
}
)
if (effectExpression.isInstanceType != null) {
append(" ")
if (effectExpression.isNegated) append("!")
append("is ${effectExpression.isInstanceType?.let(::printType)}")
}
if (effectExpression.isNullCheckPredicate) {
append(if (effectExpression.isNegated) " != " else " == ")
append("null")
}
if (orArguments.isEmpty()) {
for (andArgument in andArguments) {
if (!isEmpty()) append(" && ")
append(wrapIfNeeded(andArgument))
}
}
if (andArguments.isEmpty()) {
for (orArgument in orArguments) {
if (!isEmpty()) append(" || ")
append(wrapIfNeeded(orArgument))
}
}
}
}
interface AbstractPrinter<in T : KotlinClassMetadata> {
fun print(klass: T): String
}
class ClassPrinter(private val settings: KotlinpSettings) : AbstractPrinter<KotlinClassMetadata.Class> {
private val sb = StringBuilder()
internal val result = StringBuilder()
private var klass: KmClass? = null
private var name: ClassName? = null
private val typeParams = mutableListOf<String>()
private val supertypes = mutableListOf<String>()
private val contextReceiverTypes = mutableListOf<String>()
private val versionRequirements = mutableListOf<String>()
private var anonymousObjectOriginName: String? = null
private fun visitEnd() {
if (anonymousObjectOriginName != null) {
result.appendLine("// anonymous object origin: $anonymousObjectOriginName")
}
for (versionRequirement in versionRequirements) {
result.appendLine("// $versionRequirement")
}
if (contextReceiverTypes.isNotEmpty()) {
result.appendLine(contextReceiverTypes.joinToString(prefix = "context(", postfix = ")"))
}
result.appendClassModifiers(klass!!)
result.append(name)
if (typeParams.isNotEmpty()) {
typeParams.joinTo(result, prefix = "<", postfix = ">")
}
if (supertypes.isNotEmpty()) {
result.append(" : ")
supertypes.joinTo(result)
}
result.appendLine(" {")
if (klass!!.hasEnumEntries) {
sb.appendLine()
sb.appendLine(" // has Enum.entries")
}
result.append(sb)
result.appendLine("}")
}
private fun visitCompanionObject(name: String) {
sb.appendLine()
sb.appendLine(" // companion object: $name")
}
private fun visitNestedClass(name: String) {
sb.appendLine()
sb.appendLine(" // nested class: $name")
}
private fun visitEnumEntry(name: String) {
sb.appendLine()
sb.appendLine(" $name,")
}
private fun visitSealedSubclass(name: ClassName) {
sb.appendLine()
sb.appendLine(" // sealed subclass: $name")
}
private fun visitInlineClassUnderlyingPropertyName(name: String) {
sb.appendLine()
sb.appendLine(" // underlying property: $name")
}
private fun visitInlineClassUnderlyingType(type: String) {
sb.appendLine()
sb.appendLine(" // underlying type: $type")
}
private fun visitExtensions(kclass: KmClass) {
val localDelegatedProperties = mutableListOf<StringBuilder>()
val moduleName: String? = kclass.moduleName
anonymousObjectOriginName = kclass.anonymousObjectOriginName
kclass.localDelegatedProperties.sortIfNeededBy(settings) { it.getterSignature?.toString() ?: it.name }.forEach { p ->
visitProperty(
p, settings, StringBuilder().also { localDelegatedProperties.add(it) }
)
}
sb.appendDeclarationContainerExtensions(settings, localDelegatedProperties, moduleName)
if (kclass.hasMethodBodiesInInterface) {
sb.appendLine()
sb.appendLine(" // has method bodies in interface")
}
if (kclass.isCompiledInCompatibilityMode) {
sb.appendLine()
sb.appendLine(" // is compiled in compatibility mode")
}
}
override fun print(klass: KotlinClassMetadata.Class): String = print(klass.kmClass)
@OptIn(ExperimentalContextReceivers::class)
fun print(kmClass: KmClass): String {
klass = kmClass
name = kmClass.name
kmClass.typeParameters.forEach { typeParams.add(printTypeParameter(it, settings)) }
supertypes.addAll(kmClass.supertypes.map { printType(it) })
kmClass.constructors.sortIfNeededBy(settings) { it.signature.toString() }.forEach { visitConstructor(it, sb) }
kmClass.functions.sortIfNeededBy(settings) { it.signature.toString() }.forEach { visitFunction(it, settings, sb) }
kmClass.properties.sortIfNeededBy(settings) {
it.getterSignature?.toString() ?: it.name
}.forEach { visitProperty(it, settings, sb) }
kmClass.typeAliases.sortIfNeededBy(settings) { it.name }.forEach { visitTypeAlias(it, settings, sb) }
kmClass.companionObject?.let { visitCompanionObject(it) }
kmClass.nestedClasses.forEach { visitNestedClass(it) }
kmClass.enumEntries.forEach { visitEnumEntry(it) }
kmClass.sealedSubclasses.sortIfNeededBy(settings) { it }.forEach { visitSealedSubclass(it) }
kmClass.inlineClassUnderlyingPropertyName?.let { visitInlineClassUnderlyingPropertyName(it) }
kmClass.inlineClassUnderlyingType?.let { visitInlineClassUnderlyingType(printType(it)) }
kmClass.contextReceiverTypes.forEach { contextReceiverTypes.add(printType(it)) }
kmClass.versionRequirements.forEach { versionRequirements.add(printVersionRequirement(it)) }
visitExtensions(kmClass)
visitEnd()
return result.toString()
}
}
abstract class PackagePrinter(private val settings: KotlinpSettings) {
internal val sb = StringBuilder().apply {
appendLine("package {")
}
private fun visitExtensions(kmPackage: KmPackage) {
val localDelegatedProperties = mutableListOf<StringBuilder>()
val moduleName: String? = kmPackage.moduleName
kmPackage.localDelegatedProperties.sortIfNeededBy(settings) { it.getterSignature?.toString() ?: it.name }.forEach { p ->
visitProperty(p, settings, StringBuilder().also { localDelegatedProperties.add(it) })
}
sb.appendDeclarationContainerExtensions(settings, localDelegatedProperties, moduleName)
}
fun print(kmPackage: KmPackage) {
kmPackage.functions.sortIfNeededBy(settings) { it.signature.toString() }.forEach { visitFunction(it, settings, sb) }
kmPackage.properties.sortIfNeededBy(settings) {
it.getterSignature?.toString() ?: it.name
}.forEach { visitProperty(it, settings, sb) }
kmPackage.typeAliases.sortIfNeededBy(settings) { it.name }.forEach { visitTypeAlias(it, settings, sb) }
visitExtensions(kmPackage)
sb.appendLine("}")
}
}
class FileFacadePrinter(settings: KotlinpSettings) : PackagePrinter(settings), AbstractPrinter<KotlinClassMetadata.FileFacade> {
override fun print(klass: KotlinClassMetadata.FileFacade): String {
print(klass.kmPackage)
return sb.toString()
}
}
class LambdaPrinter(private val settings: KotlinpSettings) : AbstractPrinter<KotlinClassMetadata.SyntheticClass> {
override fun print(klass: KotlinClassMetadata.SyntheticClass): String {
val sb = StringBuilder().apply {
appendLine("lambda {")
}
val kLambda = klass.kmLambda ?: throw KotlinpException("Synthetic class $klass is not a lambda")
visitFunction(kLambda.function, settings, sb)
sb.appendLine("}")
return sb.toString()
}
}
class MultiFileClassPartPrinter(
settings: KotlinpSettings
) : PackagePrinter(settings), AbstractPrinter<KotlinClassMetadata.MultiFileClassPart> {
override fun print(klass: KotlinClassMetadata.MultiFileClassPart): String {
sb.appendLine(" // facade: ${klass.facadeClassName}")
print(klass.kmPackage)
return sb.toString()
}
}
class MultiFileClassFacadePrinter : AbstractPrinter<KotlinClassMetadata.MultiFileClassFacade> {
override fun print(klass: KotlinClassMetadata.MultiFileClassFacade): String =
buildString {
appendLine("multi-file class {")
for (part in klass.partClassNames) {
appendLine(" // $part")
}
appendLine("}")
}
}
class ModuleFilePrinter(private val settings: KotlinpSettings) {
private val optionalAnnotations = mutableListOf<String>()
private val sb = StringBuilder().apply {
appendLine("module {")
}
private fun visitPackageParts(fqName: String, fileFacades: List<String>, multiFileClassParts: Map<String, String>) {
val presentableFqName = fqName.ifEmpty { "<root>" }
sb.appendLine(" package $presentableFqName {")
for (fileFacade in fileFacades) {
sb.appendLine(" $fileFacade")
}
for ((multiFileClassPart, facade) in multiFileClassParts) {
sb.appendLine(" $multiFileClassPart ($facade)")
}
sb.appendLine(" }")
}
private fun visitEnd() {
if (optionalAnnotations.isNotEmpty()) {
sb.appendLine()
sb.appendLine(" // Optional annotations")
sb.appendLine()
for (element in optionalAnnotations) {
sb.appendLine(" " + element.replace("\n", "\n ").trimEnd())
}
}
sb.appendLine("}")
}
@UnstableMetadataApi
fun print(metadata: KotlinModuleMetadata): String {
val kmModule = metadata.kmModule
kmModule.packageParts.forEach { (fqName, kmPackageParts) ->
visitPackageParts(fqName, kmPackageParts.fileFacades, kmPackageParts.multiFileClassParts)
}
// kmModule.annotations.forEach { visitAnnotation(it) } TODO
optionalAnnotations.addAll(kmModule.optionalAnnotationClasses.map { ClassPrinter(settings).print(it) })
visitEnd()
return sb.toString()
}
}
private val VISIBILITY_MAP = mapOf(
Visibility.INTERNAL to "internal ",
Visibility.PRIVATE to "private ",
Visibility.PRIVATE_TO_THIS to "private ",
Visibility.PROTECTED to "protected ",
Visibility.PUBLIC to "public ",
Visibility.LOCAL to "local "
)
private val MODALITY_MAP = mapOf(
Modality.FINAL to "final ",
Modality.OPEN to "open ",
Modality.ABSTRACT to "abstract ",
Modality.SEALED to "sealed "
)
private val CLASS_KIND_MAP = mapOf(
ClassKind.CLASS to "class ",
ClassKind.INTERFACE to "interface ",
ClassKind.ENUM_CLASS to "enum class ",
ClassKind.ENUM_ENTRY to "enum entry ",
ClassKind.ANNOTATION_CLASS to "annotation class ",
ClassKind.OBJECT to "object ",
ClassKind.COMPANION_OBJECT to "companion object "
)
private val MEMBER_KIND_MAP = mapOf(
MemberKind.DECLARATION to "",
MemberKind.FAKE_OVERRIDE to "/* fake override */ ",
MemberKind.DELEGATION to "/* delegation */ ",
MemberKind.SYNTHESIZED to "/* synthesized */ ",
)
private fun StringBuilder.appendFlags(vararg modifiers: Pair<Boolean, String>) = modifiers.forEach { (condition, s) ->
if (condition) {
append(s)
if (s.isNotEmpty()) append(" ")
}
}
private fun StringBuilder.appendClassModifiers(kmClass: KmClass) {
append(VISIBILITY_MAP[kmClass.visibility])
append(MODALITY_MAP[kmClass.modality])
appendFlags(
kmClass.isInner to "inner",
kmClass.isData to "data",
kmClass.isExternal to "external",
kmClass.isExpect to "expect",
kmClass.isValue to "value",
kmClass.isFunInterface to "fun",
)
append(CLASS_KIND_MAP[kmClass.kind])
}
fun printConstructorModifiers(kmConstructor: KmConstructor) = buildString {
append(VISIBILITY_MAP[kmConstructor.visibility])
appendFlags(
kmConstructor.isSecondary to "/* secondary */",
kmConstructor.hasNonStableParameterNames to "/* non-stable parameter names */"
)
}
private fun StringBuilder.appendConstructorModifiers(kmConstructor: KmConstructor) {
append(printConstructorModifiers(kmConstructor))
}
fun printFunctionModifiers(kmFunction: KmFunction) = buildString {
append(VISIBILITY_MAP[kmFunction.visibility])
append(MODALITY_MAP[kmFunction.modality])
append(MEMBER_KIND_MAP[kmFunction.kind])
appendFlags(
kmFunction.isOperator to "operator",
kmFunction.isInfix to "infix",
kmFunction.isInline to "inline",
kmFunction.isTailrec to "tailrec",
kmFunction.isExternal to "external",
kmFunction.isSuspend to "suspend",
kmFunction.isExpect to "expect",
kmFunction.hasNonStableParameterNames to "/* non-stable parameter names */"
)
}
private fun StringBuilder.appendFunctionModifiers(kmFunction: KmFunction) {
append(printFunctionModifiers(kmFunction))
}
fun printPropertyModifiers(kmProperty: KmProperty) = buildString {
append(VISIBILITY_MAP[kmProperty.visibility])
append(MODALITY_MAP[kmProperty.modality])
append(MEMBER_KIND_MAP[kmProperty.kind])
appendFlags(
kmProperty.isConst to "const",
kmProperty.isLateinit to "lateinit",
kmProperty.isExternal to "external",
kmProperty.isDelegated to "/* delegated */",
kmProperty.isExpect to "expect"
)
}
private fun StringBuilder.appendPropertyModifiers(kmProperty: KmProperty) {
append(printPropertyModifiers(kmProperty))
}
fun printPropertyAccessorModifiers(accessorAttributes: KmPropertyAccessorAttributes) = buildString {
append(VISIBILITY_MAP[accessorAttributes.visibility])
append(MODALITY_MAP[accessorAttributes.modality])
appendFlags(
accessorAttributes.isNotDefault to "/* non-default */",
accessorAttributes.isExternal to "external",
accessorAttributes.isInline to "inline"
)
}
private fun StringBuilder.appendPropertyAccessorModifiers(accessorAttributes: KmPropertyAccessorAttributes) {
append(printPropertyAccessorModifiers(accessorAttributes))
}
private fun StringBuilder.appendValueParameterModifiers(valueParameter: KmValueParameter) = appendFlags(
valueParameter.isCrossinline to "crossinline",
valueParameter.isNoinline to "noinline"
)
@@ -1,17 +1,18 @@
/* /*
* Copyright 2000-2018 JetBrains s.r.o. and Kotlin Programming Language contributors. * 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. * 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 package org.jetbrains.kotlin.kotlinp
import kotlinx.metadata.jvm.KotlinClassMetadata
import kotlinx.metadata.jvm.KotlinModuleMetadata
import kotlinx.metadata.jvm.Metadata import kotlinx.metadata.jvm.Metadata
import kotlinx.metadata.jvm.UnstableMetadataApi
import org.jetbrains.org.objectweb.asm.* import org.jetbrains.org.objectweb.asm.*
import java.io.File import java.io.File
import java.io.FileInputStream import java.io.FileInputStream
internal fun File.readKotlinClassHeader(): Metadata? = ClassReader(FileInputStream(this)).readKotlinClassHeader()
fun ClassReader.readKotlinClassHeader(): Metadata? { fun ClassReader.readKotlinClassHeader(): Metadata? {
var header: Metadata? = null var header: Metadata? = null
@@ -74,3 +75,19 @@ private fun readMetadataVisitor(output: (Metadata) -> Unit): AnnotationVisitor =
output(Metadata(kind, metadataVersion, data1, data2, extraString, packageName, extraInt)) output(Metadata(kind, metadataVersion, data1, data2, extraString, packageName, extraInt))
} }
} }
internal fun readClassFile(file: File): Metadata {
return ClassReader(FileInputStream(file)).readKotlinClassHeader() ?: throw KotlinpException("file is not a Kotlin class file: $file")
}
internal fun readMetadata(metadata: Metadata): KotlinClassMetadata {
return try {
KotlinClassMetadata.readLenient(metadata)
} catch (e: IllegalArgumentException) {
throw KotlinpException("inconsistent Kotlin metadata: ${e.message}")
}
}
@OptIn(UnstableMetadataApi::class)
internal fun readModuleFile(file: File): KotlinModuleMetadata? =
runCatching { KotlinModuleMetadata.read(file.readBytes()) }.getOrNull()
@@ -17,8 +17,10 @@ import org.jetbrains.kotlin.codegen.GenerationUtils
import org.jetbrains.kotlin.config.CommonConfigurationKeys import org.jetbrains.kotlin.config.CommonConfigurationKeys
import org.jetbrains.kotlin.config.JVMConfigurationKeys import org.jetbrains.kotlin.config.JVMConfigurationKeys
import org.jetbrains.kotlin.jvm.compiler.AbstractLoadJavaTest import org.jetbrains.kotlin.jvm.compiler.AbstractLoadJavaTest
import org.jetbrains.kotlin.kotlinp.Kotlinp import org.jetbrains.kotlin.kotlinp.Settings
import org.jetbrains.kotlin.kotlinp.KotlinpSettings import org.jetbrains.kotlin.kotlinp.JvmKotlinp
import org.jetbrains.kotlin.kotlinp.readClassFile
import org.jetbrains.kotlin.kotlinp.readModuleFile
import org.jetbrains.kotlin.test.ConfigurationKind import org.jetbrains.kotlin.test.ConfigurationKind
import org.jetbrains.kotlin.test.InTextDirectivesUtils import org.jetbrains.kotlin.test.InTextDirectivesUtils
import org.jetbrains.kotlin.test.KotlinTestUtils import org.jetbrains.kotlin.test.KotlinTestUtils
@@ -62,24 +64,24 @@ private fun compileAndPrintAllFiles(
val main = StringBuilder() val main = StringBuilder()
val afterNodes = StringBuilder() val afterNodes = StringBuilder()
val kotlinp = Kotlinp(KotlinpSettings(isVerbose = true, sortDeclarations = true)) val kotlinp = JvmKotlinp(Settings(isVerbose = true, sortDeclarations = true))
@OptIn(UnstableMetadataApi::class) @OptIn(UnstableMetadataApi::class)
compile(file, disposable, tmpdir, useK2) { outputFile -> compile(file, disposable, tmpdir, useK2) { outputFile ->
when (outputFile.extension) { when (outputFile.extension) {
"kotlin_module" -> { "kotlin_module" -> {
val moduleFile = kotlinp.readModuleFile(outputFile)!! val moduleFile = readModuleFile(outputFile)!!
val transformedWithNodes = KotlinModuleMetadata.read(moduleFile.write()) val transformedWithNodes = KotlinModuleMetadata.read(moduleFile.write())
for ((sb, moduleFileToRender) in listOf( for ((sb, moduleFileToRender) in listOf(
main to moduleFile, afterNodes to transformedWithNodes main to moduleFile, afterNodes to transformedWithNodes
)) { )) {
sb.appendFileName(outputFile.relativeTo(tmpdir)) sb.appendFileName(outputFile.relativeTo(tmpdir))
sb.append(kotlinp.renderModuleFile(moduleFileToRender)) sb.append(kotlinp.printModuleFile(moduleFileToRender))
} }
} }
"class" -> { "class" -> {
val metadata = kotlinp.readClassFile(outputFile) val metadata = readClassFile(outputFile)
val classFile = KotlinClassMetadata.readStrict(metadata) val classFile = KotlinClassMetadata.readStrict(metadata)
val classFile2 = KotlinClassMetadata.readStrict(classFile.write()) val classFile2 = KotlinClassMetadata.readStrict(classFile.write())
@@ -87,7 +89,7 @@ private fun compileAndPrintAllFiles(
main to classFile, afterNodes to classFile2 main to classFile, afterNodes to classFile2
)) { )) {
sb.appendFileName(outputFile.relativeTo(tmpdir)) sb.appendFileName(outputFile.relativeTo(tmpdir))
sb.append(kotlinp.renderClassFile(classFileToRender)) sb.append(kotlinp.printClassFile(classFileToRender))
} }
} }
else -> fail("Unknown file: $outputFile") else -> fail("Unknown file: $outputFile")
@@ -8,8 +8,8 @@ package org.jetbrains.kotlin.kapt3.test.handlers
import com.sun.tools.javac.tree.JCTree.* import com.sun.tools.javac.tree.JCTree.*
import com.sun.tools.javac.tree.Pretty import com.sun.tools.javac.tree.Pretty
import kotlinx.metadata.jvm.KotlinClassMetadata import kotlinx.metadata.jvm.KotlinClassMetadata
import org.jetbrains.kotlin.kotlinp.Kotlinp import org.jetbrains.kotlin.kotlinp.Settings
import org.jetbrains.kotlin.kotlinp.KotlinpSettings import org.jetbrains.kotlin.kotlinp.JvmKotlinp
import org.jetbrains.kotlin.load.java.JvmAnnotationNames import org.jetbrains.kotlin.load.java.JvmAnnotationNames
import org.jetbrains.kotlin.test.Assertions import org.jetbrains.kotlin.test.Assertions
import org.jetbrains.kotlin.test.model.TestModule import org.jetbrains.kotlin.test.model.TestModule
@@ -37,7 +37,7 @@ fun renderMetadata(pretty: Pretty, tree: JCAnnotation): String {
extraString = args[JvmAnnotationNames.METADATA_EXTRA_STRING_FIELD_NAME].stringValue() ?: "", extraString = args[JvmAnnotationNames.METADATA_EXTRA_STRING_FIELD_NAME].stringValue() ?: "",
packageName = args[JvmAnnotationNames.METADATA_PACKAGE_NAME_FIELD_NAME].stringValue() ?: "", packageName = args[JvmAnnotationNames.METADATA_PACKAGE_NAME_FIELD_NAME].stringValue() ?: "",
) )
val text = Kotlinp(KotlinpSettings(isVerbose = true, sortDeclarations = true)).renderClassFile(KotlinClassMetadata.readStrict(metadata)) val text = JvmKotlinp(Settings(isVerbose = true, sortDeclarations = true)).printClassFile(KotlinClassMetadata.readStrict(metadata))
// "/*" and "*/" delimiters are used in kotlinp, for example to render type parameter names. Replace them with something else // "/*" and "*/" delimiters are used in kotlinp, for example to render type parameter names. Replace them with something else
// to avoid them being interpreted as Java comments. // to avoid them being interpreted as Java comments.
val sanitized = text.split('\n').dropLast(1).map { it.replace("/*", "(*").replace("*/", "*)") } val sanitized = text.split('\n').dropLast(1).map { it.replace("/*", "(*").replace("*/", "*)") }
@@ -22,8 +22,8 @@ import org.jetbrains.kotlin.kapt3.base.util.KaptLogger
import org.jetbrains.kotlin.kapt3.base.util.WriterBackedKaptLogger import org.jetbrains.kotlin.kapt3.base.util.WriterBackedKaptLogger
import org.jetbrains.kotlin.kapt3.test.KaptMessageCollectorProvider import org.jetbrains.kotlin.kapt3.test.KaptMessageCollectorProvider
import org.jetbrains.kotlin.kapt3.test.kaptOptionsProvider import org.jetbrains.kotlin.kapt3.test.kaptOptionsProvider
import org.jetbrains.kotlin.kotlinp.Kotlinp import org.jetbrains.kotlin.kotlinp.Settings
import org.jetbrains.kotlin.kotlinp.KotlinpSettings import org.jetbrains.kotlin.kotlinp.JvmKotlinp
import org.jetbrains.kotlin.test.model.* import org.jetbrains.kotlin.test.model.*
import org.jetbrains.kotlin.test.services.* import org.jetbrains.kotlin.test.services.*
import org.jetbrains.kotlin.utils.Printer import org.jetbrains.kotlin.utils.Printer
@@ -125,7 +125,7 @@ internal data class Kapt4ContextBinaryArtifact(
} }
private fun Printer.renderMetadata(metadata: Metadata) { private fun Printer.renderMetadata(metadata: Metadata) {
val text = Kotlinp(KotlinpSettings(isVerbose = true, sortDeclarations = true)).renderClassFile(KotlinClassMetadata.readLenient(metadata)) val text = JvmKotlinp(Settings(isVerbose = true, sortDeclarations = true)).printClassFile(KotlinClassMetadata.readLenient(metadata))
// "/*" and "*/" delimiters are used in kotlinp, for example to render type parameter names. Replace them with something else // "/*" and "*/" delimiters are used in kotlinp, for example to render type parameter names. Replace them with something else
// to avoid them being interpreted as Java comments. // to avoid them being interpreted as Java comments.
val sanitized = text.split('\n') val sanitized = text.split('\n')