diff --git a/klib/build.gradle b/klib/build.gradle index a17a8b4b6a0..739c6a9c91a 100644 --- a/klib/build.gradle +++ b/klib/build.gradle @@ -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') } } \ No newline at end of file diff --git a/klib/src/main/kotlin/org/jetbrains/kotlin/cli/klib/KlibPrinter.kt b/klib/src/main/kotlin/org/jetbrains/kotlin/cli/klib/KlibPrinter.kt new file mode 100644 index 00000000000..ea2f4e65fa6 --- /dev/null +++ b/klib/src/main/kotlin/org/jetbrains/kotlin/cli/klib/KlibPrinter.kt @@ -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() { + 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) "" 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)) + } + } + +} diff --git a/klib/src/main/kotlin/org/jetbrains/kotlin/cli/klib/PrettyPrinter.kt b/klib/src/main/kotlin/org/jetbrains/kotlin/cli/klib/PrettyPrinter.kt deleted file mode 100644 index 10e13e7601e..00000000000 --- a/klib/src/main/kotlin/org/jetbrains/kotlin/cli/klib/PrettyPrinter.kt +++ /dev/null @@ -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 - 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() - - 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>().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() - 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>() - fun push(list: List) { - 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): 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): 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, 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 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 Collection.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 Collection.joinToString(prefix: CharSequence = "", postfix: CharSequence = "", transform: ((T) -> CharSequence)? = null): String { - if (isEmpty()) return "" - return joinTo(StringBuilder(), ", ", prefix, postfix, -1, "...", transform).toString() - } -} diff --git a/klib/src/main/kotlin/org/jetbrains/kotlin/cli/klib/main.kt b/klib/src/main/kotlin/org/jetbrains/kotlin/cli/klib/main.kt index 07e1ab052d0..e93c418ef0c 100644 --- a/klib/src/main/kotlin/org/jetbrains/kotlin/cli/klib/main.kt +++ b/klib/src/main/kotlin/org/jetbrains/kotlin/cli/klib/main.kt @@ -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 + 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() + 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) } } diff --git a/klib/src/test/kotlin/org/jetbrains/kotlin/cli/klib/test/ContentsTest.kt b/klib/src/test/kotlin/org/jetbrains/kotlin/cli/klib/test/ContentsTest.kt new file mode 100644 index 00000000000..465ec01069d --- /dev/null +++ b/klib/src/test/kotlin/org/jetbrains/kotlin/cli/klib/test/ContentsTest.kt @@ -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 { + @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 t1(x: Foo) + fun t2(x: T) + fun t3(x: T, y: F) + inline fun t4(x: T) + fun 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 { + 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 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 { + + 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 { + + 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 { + + final enum class E private constructor(x: Int = ...) : Enum { + 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) + } +} \ No newline at end of file diff --git a/klib/src/test/testData/Classes.kt b/klib/src/test/testData/Classes.kt new file mode 100644 index 00000000000..e912e979cdb --- /dev/null +++ b/klib/src/test/testData/Classes.kt @@ -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 = "" +} diff --git a/klib/src/test/testData/Constructors.kt b/klib/src/test/testData/Constructors.kt new file mode 100644 index 00000000000..17bff119793 --- /dev/null +++ b/klib/src/test/testData/Constructors.kt @@ -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(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) +} diff --git a/klib/src/test/testData/Enum.kt b/klib/src/test/testData/Enum.kt new file mode 100644 index 00000000000..e3f89de299d --- /dev/null +++ b/klib/src/test/testData/Enum.kt @@ -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 = "" +} \ No newline at end of file diff --git a/klib/src/test/testData/Objects.kt b/klib/src/test/testData/Objects.kt new file mode 100644 index 00000000000..fadcfef93e9 --- /dev/null +++ b/klib/src/test/testData/Objects.kt @@ -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() {} + } +} diff --git a/klib/src/test/testData/TopLevelFunctions.kt b/klib/src/test/testData/TopLevelFunctions.kt new file mode 100644 index 00000000000..6bec232a3dd --- /dev/null +++ b/klib/src/test/testData/TopLevelFunctions.kt @@ -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 t1(x: Foo) {} +fun t2(x: T) {} +fun t3(x: T, y: F) {} +inline fun t4(x: T) {} +fun t5(x: T) {} + +// extension +fun Foo.e() {} + +// annotations +@A @B fun a() {} \ No newline at end of file