[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 org.jetbrains.kotlin.abicmp.reports.MetadataPropertyReport
import org.jetbrains.kotlin.abicmp.tasks.GenericMetadataTask
import org.jetbrains.kotlin.kotlinp.*
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) =
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(
metadata1: R,
metadata2: R,
@@ -5,11 +5,8 @@
package org.jetbrains.kotlin.abicmp.tasks
import kotlinx.metadata.ExperimentalContextReceivers
import kotlinx.metadata.hasConstant
import kotlinx.metadata.isVar
import kotlinx.metadata.*
import kotlinx.metadata.jvm.*
import kotlinx.metadata.visibility
import org.jetbrains.kotlin.abicmp.*
import org.jetbrains.kotlin.abicmp.checkers.*
import org.jetbrains.kotlin.kotlinp.*
@@ -65,7 +62,7 @@ private val allFunctionMetadataCheckers = listOf(
functionMetadataPropertyChecker("modifiers") { printFunctionModifiers(it) },
functionMetadataPropertyChecker("typeParameters") { it.typeParameters.stringifyTypeParameters() },
functionMetadataPropertyChecker("receiverParameterType") {
it.receiverParameterType?.let { type -> printType(type) } ?: PROPERTY_VAL_STUB
it.receiverParameterType?.let(::printType) ?: PROPERTY_VAL_STUB
},
functionMetadataPropertyChecker("valueParameters") { it.valueParameters.stringifyValueParameters() },
functionMetadataPropertyChecker("contract") {
@@ -90,13 +87,13 @@ private val allPropertyMetadataCheckers = listOf(
propertyMetadataPropertyChecker("isVar") { it.isVar.toString() },
propertyMetadataPropertyChecker("typeParameters") { it.typeParameters.stringifyTypeParameters() },
propertyMetadataPropertyChecker("receiverParameterType") {
it.receiverParameterType?.let { type -> printType(type) } ?: PROPERTY_VAL_STUB
it.receiverParameterType?.let(::printType) ?: PROPERTY_VAL_STUB
},
propertyMetadataPropertyChecker("returnType") { printType(it.returnType) },
propertyMetadataPropertyChecker("hasConstant") { it.hasConstant.toString() },
propertyMetadataPropertyChecker("getterModifiers") { printPropertyAccessorModifiers(it.getter) },
propertyMetadataPropertyChecker("setterModifiers") {
it.setter?.let { setter -> printPropertyAccessorModifiers(setter) } ?: PROPERTY_VAL_STUB
it.setter?.let(::printPropertyAccessorModifiers) ?: PROPERTY_VAL_STUB
},
propertyMetadataPropertyChecker("setterValueParameter") {
it.setterParameter?.let { param -> printValueParameter(param) } ?: PROPERTY_VAL_STUB
@@ -134,12 +131,12 @@ private val allClassMetadataCheckers = listOf(
classMetadataPropertyChecker("typeParameters") { it.kmClass.typeParameters.stringifyTypeParameters() },
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("inlineClassUnderlyingPropertyName") { it.kmClass.inlineClassUnderlyingPropertyName.toString() },
classMetadataPropertyChecker("inlineClassUnderlyingType") {
it.kmClass.inlineClassUnderlyingType?.let { type -> printType(type) } ?: "---"
it.kmClass.inlineClassUnderlyingType?.let(::printType) ?: "---"
},
classMetadataPropertyChecker("contextReceiverTypes") {
@OptIn(ExperimentalContextReceivers::class)
@@ -166,15 +163,7 @@ private val allMultifileClassPartMetadataCheckers = listOf(
private val allSyntheticClassMetadataCheckers = listOf(
syntheticClassMetadataPropertyChecker("isLambda") { it.isLambda.toString() },
syntheticClassMetadataPropertyChecker("function") {
it.kmLambda?.function?.let { function ->
printFunction(
function,
KotlinpSettings(
isVerbose = true,
sortDeclarations = true
)
)
} ?: PROPERTY_VAL_STUB
it.kmLambda?.function?.let(::printFunction) ?: PROPERTY_VAL_STUB
}
)
@@ -221,4 +210,39 @@ class CheckerConfiguration(private val enabledExclusively: Set<String>, private
if (enabledExclusively.isNotEmpty() && name !in enabledExclusively) return false
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.ListEntryDiff
import org.jetbrains.kotlin.abicmp.tag
import org.jetbrains.kotlin.kotlinp.Kotlinp
import org.jetbrains.kotlin.kotlinp.KotlinpSettings
import org.jetbrains.kotlin.kotlinp.Settings
import org.jetbrains.kotlin.kotlinp.JvmKotlinp
import org.jetbrains.kotlin.kotlinp.readKotlinClassHeader
import org.jetbrains.org.objectweb.asm.ClassReader
import org.jetbrains.org.objectweb.asm.ClassWriter
@@ -50,7 +50,7 @@ class ClassTask(
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 metadata2 = class2.getMetadata()
@@ -59,8 +59,8 @@ class ClassTask(
if (metadata1 == null || metadata2 == null) {
report.addMetadataDiff(
ListEntryDiff(
metadata1?.run { kotlinp.renderClassFile(metadata1) },
metadata2?.run { kotlinp.renderClassFile(metadata2) })
metadata1?.run { kotlinp.printClassFile(metadata1) },
metadata2?.run { kotlinp.printClassFile(metadata2) })
)
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.
*/
package org.jetbrains.kotlin.kotlinp
import kotlinx.metadata.jvm.KotlinClassMetadata
import kotlinx.metadata.jvm.KotlinModuleMetadata
import kotlinx.metadata.jvm.UnstableMetadataApi
import java.io.File
import kotlinx.metadata.*
import kotlin.contracts.ExperimentalContracts
class Kotlinp(private val settings: KotlinpSettings) {
fun renderClassFile(classFile: KotlinClassMetadata): String =
when (classFile) {
is KotlinClassMetadata.Class -> ClassPrinter(settings).print(classFile)
is KotlinClassMetadata.FileFacade -> FileFacadePrinter(settings).print(classFile)
is KotlinClassMetadata.SyntheticClass -> {
if (classFile.isLambda) LambdaPrinter(settings).print(classFile)
else buildString { appendLine("synthetic class") }
abstract class Kotlinp(protected val settings: Settings) {
fun renderAnnotation(annotation: KmAnnotation, printer: Printer): Unit = with(printer) {
append(annotation.className)
appendCollectionIfNotEmpty(annotation.arguments.entries, prefix = "(", postfix = ")") { (name, argument) ->
append(name, " = ")
renderAnnotationArgument(argument, printer)
}
}
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") }
}
internal fun readClassFile(file: File): Metadata {
return 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}")
when (argument) {
is KmAnnotationArgument.ByteValue -> append("${argument.value}.toByte()")
is KmAnnotationArgument.CharValue -> append("'${argument.value.toString().sanitize(quote = '\'')}'")
is KmAnnotationArgument.ShortValue -> append("${argument.value}.toShort()")
is KmAnnotationArgument.IntValue -> append(argument.value)
is KmAnnotationArgument.LongValue -> append("${argument.value}L")
is KmAnnotationArgument.FloatValue -> append("${argument.value}f")
is KmAnnotationArgument.DoubleValue -> append(argument.value)
is KmAnnotationArgument.UByteValue -> append("${argument.value}.toUByte()")
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)
internal fun renderModuleFile(metadata: KotlinModuleMetadata?): String =
if (metadata != null) ModuleFilePrinter(settings).print(metadata)
else buildString { appendLine("unsupported file") }
protected fun Printer.appendAnnotations(hasAnnotations: Boolean?, annotations: List<KmAnnotation>, onePerLine: Boolean = true) {
if (hasAnnotations != false) {
annotations.forEach { annotation ->
append("@")
renderAnnotation(annotation, this)
if (onePerLine) appendLine() else append(" ")
}
}
}
@OptIn(UnstableMetadataApi::class)
internal fun readModuleFile(file: File): KotlinModuleMetadata? =
runCatching { KotlinModuleMetadata.read(file.readBytes()) }.getOrNull()
@OptIn(ExperimentalContextReceivers::class)
fun renderClass(clazz: KmClass, printer: Printer): Unit = with(printer) {
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.
*/
@@ -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.
*/
@@ -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) {
val file = File(path)
@@ -43,8 +43,8 @@ object Main {
val text = try {
when (file.extension) {
"class" -> kotlinp.renderClassFile(kotlinp.readMetadata(kotlinp.readClassFile(file)))
"kotlin_module" -> @OptIn(UnstableMetadataApi::class) kotlinp.renderModuleFile(kotlinp.readModuleFile(file))
"class" -> kotlinp.printClassFile(readMetadata(readClassFile(file)))
"kotlin_module" -> @OptIn(UnstableMetadataApi::class) kotlinp.printModuleFile(readModuleFile(file))
else -> throw KotlinpException("only .class and .kotlin_module files are supported")
}
} 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.
*/
package org.jetbrains.kotlin.kotlinp
import kotlinx.metadata.jvm.KotlinClassMetadata
import kotlinx.metadata.jvm.KotlinModuleMetadata
import kotlinx.metadata.jvm.Metadata
import kotlinx.metadata.jvm.UnstableMetadataApi
import org.jetbrains.org.objectweb.asm.*
import java.io.File
import java.io.FileInputStream
internal fun File.readKotlinClassHeader(): Metadata? = ClassReader(FileInputStream(this)).readKotlinClassHeader()
fun ClassReader.readKotlinClassHeader(): Metadata? {
var header: Metadata? = null
@@ -74,3 +75,19 @@ private fun readMetadataVisitor(output: (Metadata) -> Unit): AnnotationVisitor =
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.JVMConfigurationKeys
import org.jetbrains.kotlin.jvm.compiler.AbstractLoadJavaTest
import org.jetbrains.kotlin.kotlinp.Kotlinp
import org.jetbrains.kotlin.kotlinp.KotlinpSettings
import org.jetbrains.kotlin.kotlinp.Settings
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.InTextDirectivesUtils
import org.jetbrains.kotlin.test.KotlinTestUtils
@@ -62,24 +64,24 @@ private fun compileAndPrintAllFiles(
val main = StringBuilder()
val afterNodes = StringBuilder()
val kotlinp = Kotlinp(KotlinpSettings(isVerbose = true, sortDeclarations = true))
val kotlinp = JvmKotlinp(Settings(isVerbose = true, sortDeclarations = true))
@OptIn(UnstableMetadataApi::class)
compile(file, disposable, tmpdir, useK2) { outputFile ->
when (outputFile.extension) {
"kotlin_module" -> {
val moduleFile = kotlinp.readModuleFile(outputFile)!!
val moduleFile = readModuleFile(outputFile)!!
val transformedWithNodes = KotlinModuleMetadata.read(moduleFile.write())
for ((sb, moduleFileToRender) in listOf(
main to moduleFile, afterNodes to transformedWithNodes
)) {
sb.appendFileName(outputFile.relativeTo(tmpdir))
sb.append(kotlinp.renderModuleFile(moduleFileToRender))
sb.append(kotlinp.printModuleFile(moduleFileToRender))
}
}
"class" -> {
val metadata = kotlinp.readClassFile(outputFile)
val metadata = readClassFile(outputFile)
val classFile = KotlinClassMetadata.readStrict(metadata)
val classFile2 = KotlinClassMetadata.readStrict(classFile.write())
@@ -87,7 +89,7 @@ private fun compileAndPrintAllFiles(
main to classFile, afterNodes to classFile2
)) {
sb.appendFileName(outputFile.relativeTo(tmpdir))
sb.append(kotlinp.renderClassFile(classFileToRender))
sb.append(kotlinp.printClassFile(classFileToRender))
}
}
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.Pretty
import kotlinx.metadata.jvm.KotlinClassMetadata
import org.jetbrains.kotlin.kotlinp.Kotlinp
import org.jetbrains.kotlin.kotlinp.KotlinpSettings
import org.jetbrains.kotlin.kotlinp.Settings
import org.jetbrains.kotlin.kotlinp.JvmKotlinp
import org.jetbrains.kotlin.load.java.JvmAnnotationNames
import org.jetbrains.kotlin.test.Assertions
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() ?: "",
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
// to avoid them being interpreted as Java comments.
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.test.KaptMessageCollectorProvider
import org.jetbrains.kotlin.kapt3.test.kaptOptionsProvider
import org.jetbrains.kotlin.kotlinp.Kotlinp
import org.jetbrains.kotlin.kotlinp.KotlinpSettings
import org.jetbrains.kotlin.kotlinp.Settings
import org.jetbrains.kotlin.kotlinp.JvmKotlinp
import org.jetbrains.kotlin.test.model.*
import org.jetbrains.kotlin.test.services.*
import org.jetbrains.kotlin.utils.Printer
@@ -125,7 +125,7 @@ internal data class Kapt4ContextBinaryArtifact(
}
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
// to avoid them being interpreted as Java comments.
val sanitized = text.split('\n')