diff --git a/libraries/tools/abi-comparator/src/main/kotlin/org/jetbrains/kotlin/abicmp/checkers/metadataUtils.kt b/libraries/tools/abi-comparator/src/main/kotlin/org/jetbrains/kotlin/abicmp/checkers/metadataUtils.kt index 96abcc4becf..fe86261fb2c 100644 --- a/libraries/tools/abi-comparator/src/main/kotlin/org/jetbrains/kotlin/abicmp/checkers/metadataUtils.kt +++ b/libraries/tools/abi-comparator/src/main/kotlin/org/jetbrains/kotlin/abicmp/checkers/metadataUtils.kt @@ -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.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.stringifyTypeListSorted() = map(::printType).sorted().joinToString(prefix = "[", postfix = "]") - -fun List.stringifyTypeParameters() = joinToString(prefix = "<", postfix = ">") { typeParam -> - printTypeParameter( - typeParam, - KotlinpSettings( - isVerbose = true, - sortDeclarations = true - ) - ) -} - -fun List.stringifyValueParameters() = joinToString(prefix = "(", postfix = ")", transform = ::printValueParameter) - -fun List.stringifyAnnotations() = joinToString(prefix = "[", postfix = "]", transform = ::renderAnnotation) - inline fun checkMetadataMembers( metadata1: R, metadata2: R, diff --git a/libraries/tools/abi-comparator/src/main/kotlin/org/jetbrains/kotlin/abicmp/tasks/CheckerConfiguration.kt b/libraries/tools/abi-comparator/src/main/kotlin/org/jetbrains/kotlin/abicmp/tasks/CheckerConfiguration.kt index 861b8fabaec..a1b7a963aad 100644 --- a/libraries/tools/abi-comparator/src/main/kotlin/org/jetbrains/kotlin/abicmp/tasks/CheckerConfiguration.kt +++ b/libraries/tools/abi-comparator/src/main/kotlin/org/jetbrains/kotlin/abicmp/tasks/CheckerConfiguration.kt @@ -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, private if (enabledExclusively.isNotEmpty() && name !in enabledExclusively) return false return name !in disabled } -} \ No newline at end of file +} + +private val kotlinp = JvmKotlinp(Settings(isVerbose = true, sortDeclarations = true)) + +private fun renderAnnotation(annotation: KmAnnotation) = printString { kotlinp.renderAnnotation(annotation, this) } +private fun List.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.stringifyTypeListSorted() = map(::printType).sorted().joinToString(prefix = "[", postfix = "]") + +private fun printTypeParameter(typeParameter: KmTypeParameter) = + printString { kotlinp.renderTypeParameter(typeParameter, this) } + +private fun List.stringifyTypeParameters() = joinToString(prefix = "<", postfix = ">", transform = ::printTypeParameter) + +private fun printValueParameter(valueParameter: KmValueParameter) = printString { kotlinp.renderValueParameter(valueParameter, this) } +private fun List.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.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 = "]") diff --git a/libraries/tools/abi-comparator/src/main/kotlin/org/jetbrains/kotlin/abicmp/tasks/ClassTask.kt b/libraries/tools/abi-comparator/src/main/kotlin/org/jetbrains/kotlin/abicmp/tasks/ClassTask.kt index 8f950c947e6..fc177e5833b 100644 --- a/libraries/tools/abi-comparator/src/main/kotlin/org/jetbrains/kotlin/abicmp/tasks/ClassTask.kt +++ b/libraries/tools/abi-comparator/src/main/kotlin/org/jetbrains/kotlin/abicmp/tasks/ClassTask.kt @@ -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 } diff --git a/libraries/tools/kotlinp/src/org/jetbrains/kotlin/kotlinp/JvmKotlinp.kt b/libraries/tools/kotlinp/src/org/jetbrains/kotlin/kotlinp/JvmKotlinp.kt new file mode 100644 index 00000000000..b638ae96c89 --- /dev/null +++ b/libraries/tools/kotlinp/src/org/jetbrains/kotlin/kotlinp/JvmKotlinp.kt @@ -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 { "" } + 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) = constructors.sortedBy { it.signature.toString() } + override fun sortFunctions(functions: List) = functions.sortedBy { it.signature.toString() } + override fun sortProperties(properties: List) = 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, 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 + } +} diff --git a/libraries/tools/kotlinp/src/org/jetbrains/kotlin/kotlinp/Kotlinp.kt b/libraries/tools/kotlinp/src/org/jetbrains/kotlin/kotlinp/Kotlinp.kt index 5dfd7c9b24c..17a21b1bb89 100644 --- a/libraries/tools/kotlinp/src/org/jetbrains/kotlin/kotlinp/Kotlinp.kt +++ b/libraries/tools/kotlinp/src/org/jetbrains/kotlin/kotlinp/Kotlinp.kt @@ -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, 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, 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) { + 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) { + 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) { + versionRequirements.forEach { versionRequirement -> + commented { + renderVersionRequirement(versionRequirement, this) + appendLine() + } + } + } + + protected inline fun List.sortIfNeeded(sorter: (List) -> List): List = + 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 = emptyList() + protected open fun getAnnotations(constructor: KmConstructor): List = emptyList() + protected open fun getAnnotations(function: KmFunction): List = emptyList() + protected open fun getAnnotations(property: KmProperty): List = emptyList() + protected open fun getGetterAnnotations(property: KmProperty): List = emptyList() + protected open fun getSetterAnnotations(property: KmProperty): List = emptyList() + protected abstract fun getAnnotations(typeParameter: KmTypeParameter): List + protected abstract fun getAnnotations(type: KmType): List + protected open fun getAnnotations(valueParameter: KmValueParameter): List = emptyList() + + protected open fun sortConstructors(constructors: List): List = constructors + protected open fun sortFunctions(functions: List): List = functions + protected open fun sortProperties(properties: List): List = 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 -) diff --git a/libraries/tools/kotlinp/src/org/jetbrains/kotlin/kotlinp/KotlinpException.kt b/libraries/tools/kotlinp/src/org/jetbrains/kotlin/kotlinp/KotlinpException.kt index 6ca9e17bccb..6bce2214ab6 100644 --- a/libraries/tools/kotlinp/src/org/jetbrains/kotlin/kotlinp/KotlinpException.kt +++ b/libraries/tools/kotlinp/src/org/jetbrains/kotlin/kotlinp/KotlinpException.kt @@ -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. */ diff --git a/libraries/tools/kotlinp/src/org/jetbrains/kotlin/kotlinp/Main.kt b/libraries/tools/kotlinp/src/org/jetbrains/kotlin/kotlinp/Main.kt index 0ebf3cef75a..5bd631cdd9c 100644 --- a/libraries/tools/kotlinp/src/org/jetbrains/kotlin/kotlinp/Main.kt +++ b/libraries/tools/kotlinp/src/org/jetbrains/kotlin/kotlinp/Main.kt @@ -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) { diff --git a/libraries/tools/kotlinp/src/org/jetbrains/kotlin/kotlinp/Printer.kt b/libraries/tools/kotlinp/src/org/jetbrains/kotlin/kotlinp/Printer.kt new file mode 100644 index 00000000000..c13a6473b74 --- /dev/null +++ b/libraries/tools/kotlinp/src/org/jetbrains/kotlin/kotlinp/Printer.kt @@ -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 Printer.appendCollection( + items: Collection, + 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 Printer.appendCollectionIfNotEmpty( + items: Collection, + 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) { + 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() diff --git a/libraries/tools/kotlinp/src/org/jetbrains/kotlin/kotlinp/Settings.kt b/libraries/tools/kotlinp/src/org/jetbrains/kotlin/kotlinp/Settings.kt new file mode 100644 index 00000000000..1bdf70880c7 --- /dev/null +++ b/libraries/tools/kotlinp/src/org/jetbrains/kotlin/kotlinp/Settings.kt @@ -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 +) diff --git a/libraries/tools/kotlinp/src/org/jetbrains/kotlin/kotlinp/printers.kt b/libraries/tools/kotlinp/src/org/jetbrains/kotlin/kotlinp/printers.kt deleted file mode 100644 index 39ef1d20b73..00000000000 --- a/libraries/tools/kotlinp/src/org/jetbrains/kotlin/kotlinp/printers.kt +++ /dev/null @@ -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() - 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, - 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 > Iterable.sortIfNeededBy(settings: KotlinpSettings, selector: (T) -> R?): Iterable { - 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? = 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 { - fun print(klass: T): String -} - -class ClassPrinter(private val settings: KotlinpSettings) : AbstractPrinter { - private val sb = StringBuilder() - internal val result = StringBuilder() - - private var klass: KmClass? = null - private var name: ClassName? = null - private val typeParams = mutableListOf() - private val supertypes = mutableListOf() - private val contextReceiverTypes = mutableListOf() - private val versionRequirements = mutableListOf() - 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() - 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() - 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 { - override fun print(klass: KotlinClassMetadata.FileFacade): String { - print(klass.kmPackage) - return sb.toString() - } -} - -class LambdaPrinter(private val settings: KotlinpSettings) : AbstractPrinter { - 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 { - override fun print(klass: KotlinClassMetadata.MultiFileClassPart): String { - sb.appendLine(" // facade: ${klass.facadeClassName}") - print(klass.kmPackage) - return sb.toString() - } -} - -class MultiFileClassFacadePrinter : AbstractPrinter { - 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() - - private val sb = StringBuilder().apply { - appendLine("module {") - } - - private fun visitPackageParts(fqName: String, fileFacades: List, multiFileClassParts: Map) { - val presentableFqName = fqName.ifEmpty { "" } - 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) = 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" -) diff --git a/libraries/tools/kotlinp/src/org/jetbrains/kotlin/kotlinp/readKotlinClassHeader.kt b/libraries/tools/kotlinp/src/org/jetbrains/kotlin/kotlinp/utils.kt similarity index 75% rename from libraries/tools/kotlinp/src/org/jetbrains/kotlin/kotlinp/readKotlinClassHeader.kt rename to libraries/tools/kotlinp/src/org/jetbrains/kotlin/kotlinp/utils.kt index cb85ce8ec00..edc72288530 100644 --- a/libraries/tools/kotlinp/src/org/jetbrains/kotlin/kotlinp/readKotlinClassHeader.kt +++ b/libraries/tools/kotlinp/src/org/jetbrains/kotlin/kotlinp/utils.kt @@ -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() diff --git a/libraries/tools/kotlinp/test/org/jetbrains/kotlin/kotlinp/test/KotlinpTestUtils.kt b/libraries/tools/kotlinp/test/org/jetbrains/kotlin/kotlinp/test/KotlinpTestUtils.kt index fcde88ef6ea..16f239c8241 100644 --- a/libraries/tools/kotlinp/test/org/jetbrains/kotlin/kotlinp/test/KotlinpTestUtils.kt +++ b/libraries/tools/kotlinp/test/org/jetbrains/kotlin/kotlinp/test/KotlinpTestUtils.kt @@ -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") diff --git a/plugins/kapt3/kapt3-compiler/test/org/jetbrains/kotlin/kapt3/test/handlers/utils.kt b/plugins/kapt3/kapt3-compiler/test/org/jetbrains/kotlin/kapt3/test/handlers/utils.kt index 9ca49f4fa2f..d183e8301b2 100644 --- a/plugins/kapt3/kapt3-compiler/test/org/jetbrains/kotlin/kapt3/test/handlers/utils.kt +++ b/plugins/kapt3/kapt3-compiler/test/org/jetbrains/kotlin/kapt3/test/handlers/utils.kt @@ -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("*/", "*)") } diff --git a/plugins/kapt4/test/org/jetbrains/kotlin/kapt4/Kapt4Facade.kt b/plugins/kapt4/test/org/jetbrains/kotlin/kapt4/Kapt4Facade.kt index de0473077fc..b4385acc85a 100644 --- a/plugins/kapt4/test/org/jetbrains/kotlin/kapt4/Kapt4Facade.kt +++ b/plugins/kapt4/test/org/jetbrains/kotlin/kapt4/Kapt4Facade.kt @@ -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')