[klib] Use DescriptorRenderer instead of PrettyPrinter

This commit is contained in:
Ilya Matveev
2018-02-14 16:39:29 +07:00
committed by ilmat192
parent 132c873927
commit ad9babfec6
10 changed files with 553 additions and 543 deletions
+27
View File
@@ -16,11 +16,38 @@
buildscript {
apply from: "$rootDir/gradle/kotlinGradlePlugin.gradle"
dependencies {
classpath "org.jetbrains.kotlin:kotlin-native-gradle-plugin"
}
if (!hasProperty("konan.home")) {
ext.setProperty("konan.home", distDir.absolutePath)
}
}
apply plugin: 'kotlin'
apply plugin: 'konan'
konanArtifacts {
def testFiles = fileTree('src/test/testData') {
include "*.kt"
}
testFiles.files.each { file ->
def name = file.name
library(name.take(name.lastIndexOf('.'))) { srcFiles file.absolutePath }
}
}
dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib:$buildKotlinVersion"
compile project(path: ':backend.native', configuration: 'cli_bc')
testCompile "junit:junit:4.12"
testCompile "org.jetbrains.kotlin:kotlin-test-junit:$buildKotlinVersion"
testCompile "org.jetbrains.kotlin:kotlin-test:$buildKotlinVersion"
}
test {
dependsOn 'cleanTest'
systemProperty('konan.home', getProperty("konan.home"))
dependsOn konanArtifacts.collect { it.getByTarget('host') }
}
@@ -0,0 +1,117 @@
/*
* Copyright 2010-2018 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.cli.klib
import org.jetbrains.kotlin.backend.konan.descriptors.getPackageFragments
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.descriptors.impl.DeclarationDescriptorVisitorEmptyBodies
import org.jetbrains.kotlin.renderer.AnnotationArgumentsRenderingPolicy
import org.jetbrains.kotlin.renderer.DescriptorRenderer
import org.jetbrains.kotlin.renderer.DescriptorRendererModifier
import org.jetbrains.kotlin.renderer.OverrideRenderingPolicy
import org.jetbrains.kotlin.utils.Printer
class KlibPrinter(out: Appendable) {
val printer = Printer(out, 1, " ")
val renderer = DescriptorRenderer.COMPACT_WITH_SHORT_TYPES.withOptions {
modifiers = DescriptorRendererModifier.ALL
overrideRenderingPolicy = OverrideRenderingPolicy.RENDER_OVERRIDE
annotationArgumentsRenderingPolicy = AnnotationArgumentsRenderingPolicy.UNLESS_EMPTY
excludedAnnotationClasses += setOf(KotlinBuiltIns.FQ_NAMES.suppress)
classWithPrimaryConstructor = true
renderConstructorKeyword = true
includePropertyConstant = true
unitReturnType = false
withDefinedIn = false
renderDefaultVisibility = false
secondaryConstructorsAsPrimary = false
}
val DeclarationDescriptorWithVisibility.isPublicOrProtected: Boolean
get() = visibility == Visibilities.PUBLIC || visibility == Visibilities.PROTECTED
val CallableMemberDescriptor.isFakeOverride: Boolean
get() = kind == CallableMemberDescriptor.Kind.FAKE_OVERRIDE
val DeclarationDescriptor.shouldBePrinted: Boolean
get() = this is ClassDescriptor && isPublicOrProtected || this is CallableMemberDescriptor && isPublicOrProtected && !isFakeOverride
private fun Printer.printBody(header: CharSequence, block: () -> Unit) {
println()
println("$header {")
pushIndent()
block()
popIndent()
println("}")
println()
}
fun print(module: ModuleDescriptor) {
module.accept(PrinterVisitor(), Unit)
}
private inner class PrinterVisitor : DeclarationDescriptorVisitorEmptyBodies<Unit, Unit>() {
override fun visitModuleDeclaration(descriptor: ModuleDescriptor, data: Unit) {
descriptor.getPackageFragments().forEach {
it.accept(this, data)
}
}
override fun visitPackageFragmentDescriptor(descriptor: PackageFragmentDescriptor, data: Unit) {
val children = descriptor.getMemberScope().getContributedDescriptors().filter { it.shouldBePrinted }
if (children.isNotEmpty()) {
val pacakgeName = descriptor.fqName.let { if (it.isRoot) "<root>" else it.asString() }
val header = "package $pacakgeName"
printer.printBody(header) {
children.forEach { it.accept(this, data) }
}
}
}
override fun visitClassDescriptor(descriptor: ClassDescriptor, data: Unit) {
val children = descriptor.unsubstitutedMemberScope.getContributedDescriptors().filter { it.shouldBePrinted }
val constructors = descriptor.constructors.filter { !it.isPrimary && it.shouldBePrinted }
val header = renderer.render(descriptor)
if (children.isNotEmpty() || constructors.isNotEmpty()) {
printer.printBody(header) {
constructors.forEach { it.accept(this, data) }
children.forEach { it.accept(this, data) }
}
} else {
printer.println(header)
}
}
override fun visitFunctionDescriptor(descriptor: FunctionDescriptor, data: Unit) {
printer.println(renderer.render(descriptor))
}
override fun visitPropertyDescriptor(descriptor: PropertyDescriptor, data: Unit) {
printer.println(renderer.render(descriptor))
}
override fun visitConstructorDescriptor(constructorDescriptor: ConstructorDescriptor, data: Unit) {
printer.println(renderer.render(constructorDescriptor))
}
}
}
@@ -1,535 +0,0 @@
/*
* Copyright 2010-2018 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.cli.klib
import org.jetbrains.kotlin.backend.konan.serialization.KonanSerializerProtocol
import org.jetbrains.kotlin.backend.konan.serialization.parseModuleHeader
import org.jetbrains.kotlin.backend.konan.serialization.parsePackageFragment
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.serialization.Flags
import org.jetbrains.kotlin.serialization.Flags.*
import org.jetbrains.kotlin.serialization.KonanLinkData
import org.jetbrains.kotlin.serialization.ProtoBuf
import org.jetbrains.kotlin.serialization.deserialization.*
import org.jetbrains.kotlin.utils.Printer
import java.lang.System.out
open class ModuleDeserializer(val library: ByteArray) {
protected val moduleHeader: KonanLinkData.Library
get() = parseModuleHeader(library)
val moduleName: String
get() = moduleHeader.moduleName
val packageFragmentNameList: List<String>
get() = moduleHeader.packageFragmentNameList
}
//-----------------------------------------------------------------------------//
class PrettyPrinter(library: ByteArray, val packageLoader: (String) -> ByteArray)
: ModuleDeserializer(library) {
private fun packageFragment(fqname: String): KonanLinkData.PackageFragment
= parsePackageFragment(packageLoader(fqname))
fun printPackageFragment(fqname: String) {
val fragment = packageFragment(fqname)
PackageFragmentPrinter(fragment, out).print()
}
}
//-----------------------------------------------------------------------------//
class CachedNameResolver(private val nameResolver: NameResolver): NameResolver by nameResolver {
private val fqNameToClassId = mutableMapOf<Int, ClassId>()
override fun getClassId(fqName: Int): ClassId = fqNameToClassId.getOrPut(fqName) {
nameResolver.getClassId(fqName)
}
}
//-----------------------------------------------------------------------------//
class PackageFragmentPrinter(val packageFragment: KonanLinkData.PackageFragment, out: Appendable) {
private val printer = Printer(out, " ")
private val stringTable = packageFragment.stringTable!!
private val qualifiedNameTable = packageFragment.nameTable!!
private val nameResolver = CachedNameResolver(NameResolverImpl(stringTable, qualifiedNameTable))
private val classIdToProtoBuf = packageFragment.classes.classesList.map {
it.classId to it
}.toMap()
private val enumToEntries = mutableMapOf<ClassId, MutableList<ProtoBuf.Class>>().apply {
packageFragment.classes.classesList
.filter { it.isEnumEntry }
.forEach { entryProto ->
val enumClassId = entryProto.supertypes(TypeTable(entryProto.typeTable))
.map { nameResolver.getClassId(it.className) }
.single {classIdToProtoBuf.getValue(it).isEnumClass }
val entryList = getOrPut(enumClassId) { mutableListOf() }
entryList.add(entryProto)
}
}
private val ProtoBuf.Class.classId: ClassId
get() = nameResolver.getClassId(this.fqName)
private val ProtoBuf.TypeParameter.resolvedName: String
get() = nameResolver.getName(name).asString()
//-------------------------------------------------------------------------//
object Indent {
private var indent: String = ""
fun push() { indent += " " }
fun pop() { indent = indent.dropLast(4) }
override fun toString() = indent
}
//-------------------------------------------------------------------------//
// TODO: Replace with one context
object TypeTables {
val tables = mutableListOf<TypeTable>()
fun push(table: ProtoBuf.TypeTable) { tables.add(TypeTable(table)) }
fun pop() = tables.removeAt(tables.lastIndex)
fun peek() = tables.last()
fun type(typeId: Int) = tables.last().get(typeId)
}
object TypeParameterNames {
val typeParameters = mutableListOf<Map<Int, ProtoBuf.TypeParameter>>()
fun push(list: List<ProtoBuf.TypeParameter>) {
typeParameters.add(list.map { it.id to it }.toMap())
}
fun pop() = typeParameters.removeAt(typeParameters.lastIndex)
fun typeParameter(parameterId: Int): ProtoBuf.TypeParameter? {
typeParameters.asReversed().forEach {
val typeParameter = it[parameterId]
if (typeParameter != null) {
return typeParameter
}
}
return null
}
}
//-------------------------------------------------------------------------//
val ProtoBuf.Class.isTopLevel: Boolean
get() = !isEnumEntry && !classId.let { it.isLocal || it.isNestedClass }
fun print() {
if (packageFragment.isEmpty) {
return
}
TypeTables.push(packageFragment.`package`.typeTable)
if (packageFragment.hasFqName()) printer.print("package ${packageFragment.fqName}")
val packageBody = StringBuilder().buildBody {
packageFragment.classes.classesList.forEach {if (it.isTopLevel) appendln(it.asString()) }
packageFragment.`package`.functionList.forEach { appendln(it.asString()) }
packageFragment.`package`.propertyList.forEach { appendln(it.asString()) }
}
printer.print(packageBody.toString())
TypeTables.pop()
}
//-------------------------------------------------------------------------//
private fun StringBuilder.buildBody(body: StringBuilder.() -> Unit): StringBuilder {
Indent.push()
val result = StringBuilder().apply(body).toString()
Indent.pop()
append(if (result.isEmpty()) "\n" else " {\n$result$Indent}\n")
return this
}
//-------------------------------------------------------------------------//
private fun ProtoBuf.Class.asString(): String {
if (hasTypeTable()) TypeTables.push(typeTable)
TypeParameterNames.push(typeParameterList)
val result = buildString {
val classId = nameResolver.getClassId(fqName)
val className = if (!isCompanionObject) classId.shortClassName else ""
val classKind = Flags.CLASS_KIND.get(flags).asString()
val modality = Flags.MODALITY.get(flags).asString(isInterface)
val visibility = Flags.VISIBILITY.get(flags).asString()
val inner = Flags.IS_INNER.asString(flags)
val typeParameters = typeParameterList
.joinToString("<", "> ") { it.asString() }
.let { if (it.isEmpty()) " " else it }
val supertypes = supertypesToString(supertypes(TypeTables.peek()))
val annotations = annotationsToString(getExtension(KonanSerializerProtocol.classAnnotation))
val primaryConstructor = when {
isObject || isCompanionObject -> ""
// Constructors of an enum class are always private so we don't print their visibility.
else -> primaryConstructor()?.asString(!isEnumClass, true) ?: ""
}
when {
isEnumEntry -> append("$annotations$Indent$className")
else -> append("$annotations$Indent$modality$visibility$inner$classKind$className$typeParameters$primaryConstructor$supertypes")
}
buildBody {
secondaryConstructors().forEach { append(it.asString(!isEnumClass)) }
functionList.forEach { append(it.asString(isInterface)) }
propertyList.forEach { append(it.asString(isInterface)) }
nestedClassNameList.asSequence()
.map { classIdToProtoBuf.getValue(classId.createNestedClassId(nameResolver.getName(it))) }
.forEach { append(it.asString()) }
enumToEntries[classId]?.forEach { append(it.asString()) }
}
}
TypeParameterNames.pop()
if (hasTypeTable()) TypeTables.pop()
return result
}
//-------------------------------------------------------------------------//
private fun supertypesToString(supertypes: List<ProtoBuf.Type>): String {
val result = supertypes.joinToString {
val supertype = it.asString()
// TODO: Use fq names here.
if (supertype == "Any" || supertype.startsWith("Enum<")) "" else supertype
}
return if (result.isEmpty()) "" else " : $result"
}
//-------------------------------------------------------------------------//
private fun ProtoBuf.Constructor.asString(printVisibility: Boolean = true, isPrimary: Boolean = false): String {
val visibility = if (printVisibility) Flags.VISIBILITY.get(flags).asString() else ""
val annotations = annotationsToString(getExtension(KonanSerializerProtocol.constructorAnnotation), true)
val parameters = valueParameterList.joinToString(", ", prefix = "(", postfix = ")") { it.asString() }
val header = "$visibility$annotations"
if (isPrimary) {
return when {
header.isNotEmpty() -> "${header}constructor$parameters"
parameters == "()" -> ""
else -> parameters
}
} else {
return if (header.isNotEmpty()) {
"$Indent$header constructor$parameters\n"
} else {
"${Indent}constructor$parameters\n"
}
}
}
//-------------------------------------------------------------------------//
private fun ProtoBuf.Function.asString(isInterface: Boolean = false): String {
if (hasTypeTable()) TypeTables.push(typeTable)
TypeParameterNames.push(typeParameterList)
val result = buildString {
val name = stringTable.getString(name)
val visibility = Flags.VISIBILITY.get(flags).asString()
val modality = Flags.MODALITY.get(flags).asString(isInterface)
val isInline = Flags.IS_INLINE.asString(flags)
val isExternal = Flags.IS_EXTERNAL_FUNCTION.asString(flags)
val receiverType = receiverType()
val annotations = annotationsToString(getExtension(KonanSerializerProtocol.functionAnnotation))
val typeParameters = typeParameterList.joinToString("<", "> ") { it.asString() }
val valueParameters = valueParameterList.joinToString(", ", "(", ")") { it.asString() }
val returnType = returnType()
append("$annotations$Indent$isExternal$modality$visibility${isInline}fun $typeParameters$receiverType$name$valueParameters$returnType\n")
}
TypeParameterNames.pop()
if (hasTypeTable()) TypeTables.pop()
return result
}
//-------------------------------------------------------------------------//
private fun ProtoBuf.Property.asString(isInterface: Boolean = false) = buildString {
val name = stringTable.getString(name)
val isVar = Flags.IS_VAR.asString(flags)
val modality = Flags.MODALITY.get(flags).asString(isInterface)
val visibility = Flags.VISIBILITY.get(flags).asString()
val isExternal = Flags.IS_EXTERNAL_PROPERTY.asString(flags)
val isConst = Flags.IS_CONST.asString(flags)
val returnType = returnType(TypeTables.peek()).asString()
val annotations = annotationsToString(getExtension(KonanSerializerProtocol.propertyAnnotation))
append("$annotations$Indent$isExternal$modality$visibility$isConst$isVar$name: $returnType\n")
}
//-------------------------------------------------------------------------//
private fun ProtoBuf.Function.receiverType(): String {
return receiverType(TypeTables.peek())?.let { it.asString() + "." } ?: ""
}
//-------------------------------------------------------------------------//
private fun ProtoBuf.Function.returnType(): String {
val returnType = returnType(TypeTables.peek()).asString()
return if (returnType == "Unit") "" else ": " + returnType
}
//-------------------------------------------------------------------------//
private fun ProtoBuf.TypeParameter.asString(): String {
val parameterName = stringTable.getString(name)
val upperBounds = upperBoundsToString(upperBounds(TypeTables.peek()))
val isReified = if (reified) "reified " else ""
val variance = variance.asString()
val annotations = annotationsToString(getExtension(KonanSerializerProtocol.typeParameterAnnotation), true)
return "$annotations$isReified$variance$parameterName$upperBounds"
}
private fun upperBoundsToString(upperBounds: List<ProtoBuf.Type>): String {
if (upperBounds.isEmpty()) return ""
return ": " + upperBounds.first().asString()
}
//-------------------------------------------------------------------------//
private fun ProtoBuf.ValueParameter.asString(): String {
val parameterName = stringTable.getString(name)
val type = type(TypeTables.peek()).asString()
val isCrossInline = Flags.IS_CROSSINLINE.asString(flags)
val annotations = annotationsToString(getExtension(KonanSerializerProtocol.parameterAnnotation), true)
return "$annotations$isCrossInline$parameterName: $type"
}
//-------------------------------------------------------------------------//
private fun annotationsToString(annotations: List<ProtoBuf.Annotation>, oneline: Boolean = false): String {
if (annotations.isEmpty()) {
return ""
}
val prefix = if (oneline) "" else "$Indent"
val separator = if (oneline) " " else "\n$Indent"
val postfix = if (oneline) " " else "\n"
return annotations.joinToString(prefix = prefix, separator = separator, postfix = postfix) {
it.asString()
}
}
//-------------------------------------------------------------------------//
private fun ProtoBuf.Annotation.asString(): String {
val builder = StringBuilder("@${getName(id)}")
argumentList.joinTo(builder, prefix = "(", postfix = ")") { it.value.asString() }
return builder.toString()
}
//-------------------------------------------------------------------------//
private fun ProtoBuf.Annotation.Argument.Value.asString(): String {
val builder = StringBuilder(
when {
hasAnnotation() -> annotation.asString()
hasStringValue() -> "\"${stringTable.getString(stringValue)}\""
hasDoubleValue() -> doubleValue.toString()
hasFloatValue() -> floatValue.toString()
hasIntValue() -> intValue.toString()
hasClassId() -> getName(classId) + "."
hasEnumValueId() -> stringTable.getString(enumValueId)
else -> ""
})
arrayElementList.joinTo(builder) { it.asString() }
return builder.toString()
}
//--- Types ---------------------------------------------------------------//
private fun ProtoBuf.Type.asValueString(): String {
val builder = StringBuilder(name())
// TODO: Use more clever check for <*>
argumentList.joinTo(builder, "<", ">") { it.type(TypeTables.peek())?.asString() ?: "*" }
if (nullable) builder.append("?")
return builder.toString()
}
//-------------------------------------------------------------------------//
private fun ProtoBuf.Type.asCallableString(): String {
val builder = StringBuilder()
argumentList.dropLast(1).joinTo(builder, ", ", "(", ") -> ") {
// TODO: We use Nothing because Function<*, R> is (Nothing) -> R. Can we make a more clever check?
it.type(TypeTables.peek())?.asString() ?: "Nothing"
}
val returnType = argumentList.lastOrNull()
if (returnType != null) {
// TODO: We use Any? because Function<P, *> is (P) -> Any?. Can we make a more clever check?
builder.append(returnType.type(TypeTables.peek())?.asString() ?: "Any?")
}
return builder.toString()
}
//-------------------------------------------------------------------------//
private fun ProtoBuf.Type.name() = when {
hasClassName() -> getName(className)
hasTypeParameter() -> TypeParameterNames.typeParameter(typeParameter)?.resolvedName ?: "undefined"
hasTypeParameterName() -> stringTable.getString(typeParameterName)
hasTypeAliasName() -> stringTable.getString(typeAliasName)
else -> "undefined"
}
//-------------------------------------------------------------------------//
private fun ProtoBuf.Type.isCallable() = name().contains("Function") // TODO: Use FQ name here.
//-------------------------------------------------------------------------//
private fun ProtoBuf.Type.asString() = if (isCallable()) asCallableString() else asValueString()
//--- Helpers -------------------------------------------------------------//
private fun getName(id: Int): String {
val shortQualifiedName = qualifiedNameTable.getQualifiedName(id)
val shortStringId = shortQualifiedName.shortName
return stringTable.getString(shortStringId)
}
//-------------------------------------------------------------------------//
private fun getParentName(id: Int) = qualifiedNameTable.getQualifiedName(id).parentQualifiedName
private fun ProtoBuf.Class.primaryConstructor() = constructorList.firstOrNull { !Flags.IS_SECONDARY.get(it.flags) }
private fun ProtoBuf.Class.secondaryConstructors() = constructorList.filter { Flags.IS_SECONDARY.get(it.flags) }
//-------------------------------------------------------------------------//
private val ProtoBuf.Class.isEnumClass: Boolean
get() = Flags.CLASS_KIND.get(flags) == ProtoBuf.Class.Kind.ENUM_CLASS
private val ProtoBuf.Class.isEnumEntry: Boolean
get() = Flags.CLASS_KIND.get(flags) == ProtoBuf.Class.Kind.ENUM_ENTRY
private val ProtoBuf.Class.isInterface: Boolean
get() = Flags.CLASS_KIND.get(flags) == ProtoBuf.Class.Kind.INTERFACE
private val ProtoBuf.Class.isSealed: Boolean
get() = Flags.MODALITY.get(flags) == ProtoBuf.Modality.SEALED
//-------------------------------------------------------------------------//
private fun ProtoBuf.TypeParameter.Variance.asString(): String {
if (this == ProtoBuf.TypeParameter.Variance.INV) return ""
return toString().toLowerCase() + " "
}
//-------------------------------------------------------------------------//
private fun ProtoBuf.Modality.asString(isInterface: Boolean = false): String {
if (isInterface) {
return ""
}
return when (this) {
ProtoBuf.Modality.FINAL -> ""
ProtoBuf.Modality.OPEN -> "open "
ProtoBuf.Modality.ABSTRACT -> "abstract "
ProtoBuf.Modality.SEALED -> "sealed "
}
}
//-------------------------------------------------------------------------//
private fun ProtoBuf.Visibility.asString() =
when (this) {
ProtoBuf.Visibility.INTERNAL -> "internal "
ProtoBuf.Visibility.PRIVATE -> "private "
ProtoBuf.Visibility.PROTECTED -> "protected "
ProtoBuf.Visibility.PUBLIC -> ""
ProtoBuf.Visibility.PRIVATE_TO_THIS -> "private "
ProtoBuf.Visibility.LOCAL -> "local "
else -> "invalid visibility "
}
//-------------------------------------------------------------------------//
private val ProtoBuf.Class.isObject: Boolean
get() = Flags.CLASS_KIND.get(flags) == ProtoBuf.Class.Kind.OBJECT
private val ProtoBuf.Class.isCompanionObject: Boolean
get() = Flags.CLASS_KIND.get(flags) == ProtoBuf.Class.Kind.COMPANION_OBJECT
private fun ProtoBuf.Class.Kind.asString() =
when (this) {
ProtoBuf.Class.Kind.CLASS -> "class "
ProtoBuf.Class.Kind.INTERFACE -> "interface "
ProtoBuf.Class.Kind.ENUM_CLASS -> "enum class "
ProtoBuf.Class.Kind.ENUM_ENTRY -> "enum "
ProtoBuf.Class.Kind.ANNOTATION_CLASS -> "annotation class "
ProtoBuf.Class.Kind.OBJECT -> "object "
ProtoBuf.Class.Kind.COMPANION_OBJECT -> "companion object "
else -> "invalid member kind "
}
//-------------------------------------------------------------------------//
private fun Flags.BooleanFlagField.asString(flags: Int) =
when(this) {
IS_INLINE -> if (Flags.IS_INLINE .get(flags)) "inline " else ""
IS_VAR -> if (Flags.IS_VAR .get(flags)) "var " else "val "
IS_CROSSINLINE -> if (Flags.IS_CROSSINLINE .get(flags)) "crossinline " else ""
IS_NOINLINE -> if (Flags.IS_NOINLINE .get(flags)) "noinline " else ""
IS_INNER -> if (Flags.IS_INNER .get(flags)) "inner " else ""
IS_EXTERNAL_FUNCTION -> if (Flags.IS_EXTERNAL_FUNCTION.get(flags)) "external " else ""
IS_EXTERNAL_PROPERTY -> if (Flags.IS_EXTERNAL_PROPERTY.get(flags)) "external " else ""
IS_CONST -> if (Flags.IS_CONST .get(flags)) "const " else ""
else -> "unknown flag"
}
//-------------------------------------------------------------------------//
private fun ProtoBuf.Class.isRoot() = getParentName(fqName) == -1
//-------------------------------------------------------------------------//
private fun <T> Collection<T>.joinTo(builder: StringBuilder, prefix: CharSequence = "", postfix: CharSequence = "", transform: ((T) -> CharSequence)? = null): String {
if (isEmpty()) return ""
return joinTo(builder, ", ", prefix, postfix, -1, "...", transform).toString()
}
//-------------------------------------------------------------------------//
private fun <T> Collection<T>.joinToString(prefix: CharSequence = "", postfix: CharSequence = "", transform: ((T) -> CharSequence)? = null): String {
if (isEmpty()) return ""
return joinTo(StringBuilder(), ", ", prefix, postfix, -1, "...", transform).toString()
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
* Copyright 2010-2018 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,13 +17,21 @@
package org.jetbrains.kotlin.cli.klib
// TODO: Extract `library` package as a shared jar?
import org.jetbrains.kotlin.backend.konan.isStdlib
import org.jetbrains.kotlin.backend.konan.library.KonanLibrarySearchPathResolver
import org.jetbrains.kotlin.backend.konan.library.impl.LibraryReaderImpl
import org.jetbrains.kotlin.backend.konan.library.impl.UnzippedKonanLibrary
import org.jetbrains.kotlin.backend.konan.library.impl.ZippedKonanLibrary
import org.jetbrains.kotlin.backend.konan.serialization.parseModuleHeader
import org.jetbrains.kotlin.config.ApiVersion
import org.jetbrains.kotlin.config.LanguageVersion
import org.jetbrains.kotlin.config.LanguageVersionSettingsImpl
import org.jetbrains.kotlin.descriptors.impl.ModuleDescriptorImpl
import org.jetbrains.kotlin.konan.file.File
import org.jetbrains.kotlin.konan.target.Distribution
import org.jetbrains.kotlin.konan.target.PlatformManager
import org.jetbrains.kotlin.konan.target.customerDistribution
import org.jetbrains.kotlin.serialization.KonanLinkData
import java.lang.System.out
import kotlin.system.exitProcess
fun printUsage() {
@@ -79,6 +87,18 @@ fun error(text: String) {
// TODO: Get rid of the hardcoded path.
val defaultRepository = File(File.userHome, ".konan/klib")
open class ModuleDeserializer(val library: ByteArray) {
protected val moduleHeader: KonanLinkData.Library
get() = parseModuleHeader(library)
val moduleName: String
get() = moduleHeader.moduleName
val packageFragmentNameList: List<String>
get() = moduleHeader.packageFragmentNameList
}
class Library(val name: String, val requestedRepository: String?, val target: String) {
val repository = requestedRepository?.File() ?: defaultRepository
@@ -121,14 +141,29 @@ class Library(val name: String, val requestedRepository: String?, val target: St
reader?.libDir?.deleteRecursively()
}
fun contents() {
fun contents(output: Appendable = out) {
val reader = LibraryReaderImpl(libraryInRepoOrCurrentDir(repository, name), currentAbiVersion)
val printer = PrettyPrinter(
reader.moduleHeaderData, {name -> reader.packageMetadata(name)})
printer.packageFragmentNameList.forEach{
printer.printPackageFragment(it)
// TODO: Should the klib tool have arguments like compilerVersion or apiVersion?
val versionSpec = LanguageVersionSettingsImpl(LanguageVersion.LATEST_STABLE, ApiVersion.LATEST_STABLE)
val module = reader.moduleDescriptor(versionSpec)
val defaultModules = mutableListOf<ModuleDescriptorImpl>()
if (!module.isStdlib()) {
val resolver = KonanLibrarySearchPathResolver(emptyList(),
target = null,
distributionKlib = Distribution().klib,
localKonanDir = null,
skipCurrentDir = true)
resolver.defaultLinks(false, true)
.mapTo(defaultModules) {
LibraryReaderImpl(it, currentAbiVersion).moduleDescriptor(versionSpec)
}
}
(defaultModules + module).let { allModules ->
allModules.forEach { it.setDependencies(allModules) }
}
KlibPrinter(output).print(module)
}
}
@@ -0,0 +1,224 @@
/*
* Copyright 2010-2018 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.cli.klib.test
import kotlin.test.*
import org.jetbrains.kotlin.cli.klib.*
import org.jetbrains.kotlin.konan.target.Distribution
import org.jetbrains.kotlin.konan.target.HostManager
import java.nio.file.Paths
class ContentsTest {
private fun testLibrary(name: String) = LIBRARY_DIRECTORY.resolve("$name.klib").toFile().absolutePath
private fun klibContents(library: String, expected: () -> String) {
val output = StringBuilder()
val lib = Library(library, null, "host")
lib.contents(output)
assertEquals(expected(), output.trim().toString(), "klib contents test failed for library: $library")
}
@Test
fun `Stdlib content should be printed without exceptions`() {
val output = StringBuilder()
Library(Distribution().stdlib, null, "host").contents(output)
}
@Test
fun topLevelFunctions() = klibContents(testLibrary("TopLevelFunctions")) {
"""
package <root> {
@A @B fun a()
fun f1(x: Foo)
fun f2(x: Foo, y: Foo): Int
inline fun i1(block: () -> Foo)
inline fun i2(noinline block: () -> Foo)
inline fun i3(crossinline block: () -> Foo)
fun i4(block: (Foo) -> Int)
fun i5(block: (Foo, Foo) -> Int)
fun i6(block: Foo.() -> Int)
fun i7(block: Foo.(Foo) -> Int)
fun <T> t1(x: Foo)
fun <T> t2(x: T)
fun <T, F> t3(x: T, y: F)
inline fun <reified T> t4(x: T)
fun <T : Number> t5(x: T)
fun Foo.e()
final annotation class A constructor() : Annotation
final annotation class B constructor() : Annotation
final class Foo constructor()
}
""".trimIndent()
}
@Test
fun constructors() = klibContents(testLibrary("Constructors")) {
"""
package <root> {
final annotation class A constructor() : Annotation
final class Bar @A constructor(x: Int)
final class Baz private constructor(x: Int)
final class Foo constructor(x: Int) {
constructor()
constructor(x: Double)
constructor(x: Double, y: Int)
protected constructor(x: String)
@A constructor(x: Foo)
}
final class Qux protected constructor(x: Int)
final class Typed<T> constructor(x: Int) {
constructor()
constructor(x: Double)
constructor(x: Double, y: Int)
protected constructor(x: String)
@A constructor(x: Foo)
}
}
""".trimIndent()
}
@Test
fun objects() = klibContents(testLibrary("Objects")) {
"""
package <root> {
object A {
final fun a()
}
final class B constructor() {
object C {
final fun c()
}
companion object {
final fun b()
}
}
final class D constructor() {
companion object E {
final fun e()
}
}
}
""".trimIndent()
}
@Test
fun classes() = klibContents(testLibrary("Classes")) {
"""
package <root> {
final class A constructor() {
final val aVal: Int = 0
final var aVar: String
final fun aFun()
final inner class B constructor() {
final val bVal: Int = 0
final var bVar: String
final fun bFun()
final inner class C constructor() {
final val cVal: Int = 0
final var cVar: String
final fun cFun()
}
}
final class E constructor() {
final val eVal: Int = 0
final var eVar: String
final fun eFun()
}
}
final data class F constructor(fVal: Int, fVar: String) {
final val fVal: Int
final var fVar: String
final operator fun component1(): Int
final operator fun component2(): String
final fun copy(fVal: Int = ..., fVar: String = ...): F
override fun equals(other: Any?): Boolean
final fun fFun()
override fun hashCode(): Int
override fun toString(): String
}
final class FinalImpl constructor() : OpenImpl {
override val iVal: Int = 0
override var iVar: String
override fun iFun()
}
interface Interface {
abstract val iVal: Int
abstract var iVar: String
abstract fun iFun()
}
open class OpenImpl constructor() : Interface {
override val iVal: Int = 0
override var iVar: String
override fun iFun()
}
}
""".trimIndent()
}
@Test
@Ignore // TODO: Do we need to print the overridden method in the C entry?
fun enum() = klibContents(testLibrary("Enum")) {
"""
package <root> {
final enum class E private constructor(x: Int = ...) : Enum<E> {
enum entry A
enum entry B
enum entry C {
override fun enumFun(): Int
}
final val enumVal: Int = 0
final var enumVar: String
final val x: Int
open fun enumFun(): Int
}
}
"""
}
companion object {
val LIBRARY_DIRECTORY = Paths.get("build/konan/libs").resolve( HostManager.host.visibleName)
}
}
+52
View File
@@ -0,0 +1,52 @@
class A {
fun aFun() {}
val aVal = 0
var aVar = ""
inner class B {
fun bFun() {}
val bVal = 0
var bVar = ""
inner class C {
fun cFun() {}
val cVal = 0
var cVar = ""
}
private inner class D {
fun dFun() {}
val dVal = 0
var dVar = ""
}
}
class E {
fun eFun() {}
val eVal = 0
var eVar = ""
}
}
data class F(val fVal: Int, var fVar: String) {
fun fFun() {}
}
interface Interface {
fun iFun()
val iVal: Int
var iVar: String
}
open class OpenImpl: Interface {
override fun iFun() {}
override val iVal = 0
override var iVar = ""
}
class FinalImpl: OpenImpl() {
override fun iFun() {}
override val iVal = 0
override var iVar = ""
}
+27
View File
@@ -0,0 +1,27 @@
@file:Suppress("UNUSED_PARAMETER")
annotation class A
class Foo(x: Int) {
constructor(): this(0)
constructor(x: Double): this(x.toInt())
constructor(x: Double, y: Int): this(y)
private constructor(x: Long): this(0)
protected constructor(x: String): this(0)
@A constructor(x: Foo) : this(0)
}
class Bar @A constructor(x: Int)
class Baz private constructor(x: Int)
class Qux protected constructor(x: Int)
class Typed<T>(x: Int) {
constructor(): this(0)
constructor(x: Double): this(x.toInt())
constructor(x: Double, y: Int): this(y)
private constructor(x: Long): this(0)
protected constructor(x: String): this(0)
@A constructor(x: Foo) : this(0)
}
+11
View File
@@ -0,0 +1,11 @@
enum class E(val x: Int = 0) {
A,
B,
C(1) {
override fun enumFun() = 42
};
open fun enumFun(): Int = 0
val enumVal = 0
var enumVar = ""
}
+19
View File
@@ -0,0 +1,19 @@
object A {
fun a() {}
}
class B {
companion object {
fun b() {}
}
object C {
fun c() {}
}
}
class D {
companion object E {
fun e() {}
}
}
@@ -0,0 +1,33 @@
@file:Suppress("UNUSED_PARAMETER")
annotation class A
annotation class B
class Foo
fun f1(x: Foo): Unit {}
fun f2(x: Foo, y: Foo) = 0
// inline
inline fun i1(block: () -> Foo) {}
inline fun i2(noinline block: () -> Foo) {}
inline fun i3(crossinline block: () -> Foo) {}
// callable args
fun i4(block: (Foo) -> Int) {}
fun i5(block: (Foo, Foo) -> Int) {}
fun i6(block: Foo.() -> Int) {}
fun i7(block: Foo.(Foo) -> Int) {}
// type parameters
fun <T> t1(x: Foo) {}
fun <T> t2(x: T) {}
fun <T, F> t3(x: T, y: F) {}
inline fun <reified T> t4(x: T) {}
fun <T: Number> t5(x: T) {}
// extension
fun Foo.e() {}
// annotations
@A @B fun a() {}