From 4b284a4890fac7b149d04f80c594fa7acaebae3d Mon Sep 17 00:00:00 2001 From: Alexander Udalov Date: Fri, 9 Mar 2018 14:40:47 +0100 Subject: [PATCH] Add kotlinp, a tool to print Kotlin metadata in class/module files At the moment, it's not published anywhere, but that may change soon #KT-23198 --- .../kotlin/code/CodeConformanceTest.kt | 1 + libraries/tools/kotlinp/build.gradle.kts | 40 + .../org/jetbrains/kotlin/kotlinp/Kotlinp.kt | 35 + .../kotlin/kotlinp/KotlinpException.kt | 8 + .../src/org/jetbrains/kotlin/kotlinp/Main.kt | 78 ++ .../org/jetbrains/kotlin/kotlinp/printers.kt | 731 ++++++++++++++++++ .../kotlin/kotlinp/readKotlinClassHeader.kt | 79 ++ .../kotlinp/test/AbstractKotlinpTest.kt | 15 + .../kotlinp/test/GenerateKotlinpTests.kt | 18 + .../kotlinp/test/KotlinpTestGenerated.java | 86 +++ .../kotlin/kotlinp/test/KotlinpTestUtils.kt | 61 ++ .../tools/kotlinp/testData/Annotations.kt | 52 ++ .../tools/kotlinp/testData/Annotations.txt | 108 +++ libraries/tools/kotlinp/testData/Lambda.kt | 4 + libraries/tools/kotlinp/testData/Lambda.txt | 21 + .../tools/kotlinp/testData/LocalClass.kt | 9 + .../tools/kotlinp/testData/LocalClass.txt | 26 + .../tools/kotlinp/testData/MultiFileClass.kt | 5 + .../tools/kotlinp/testData/MultiFileClass.txt | 14 + .../tools/kotlinp/testData/NestedClasses.kt | 16 + .../tools/kotlinp/testData/NestedClasses.txt | 60 ++ .../tools/kotlinp/testData/PlatformType.kt | 7 + .../tools/kotlinp/testData/PlatformType.txt | 13 + .../tools/kotlinp/testData/SimpleClass.kt | 13 + .../tools/kotlinp/testData/SimpleClass.txt | 29 + .../tools/kotlinp/testData/SimplePackage.kt | 8 + .../tools/kotlinp/testData/SimplePackage.txt | 18 + .../tools/kotlinp/testData/SyntheticClass.kt | 7 + .../tools/kotlinp/testData/SyntheticClass.txt | 10 + .../tools/kotlinp/testData/TypeParameters.kt | 17 + .../tools/kotlinp/testData/TypeParameters.txt | 43 ++ .../kotlinp/testData/VersionRequirement.kt | 19 + .../kotlinp/testData/VersionRequirement.txt | 34 + settings.gradle | 2 + 34 files changed, 1687 insertions(+) create mode 100644 libraries/tools/kotlinp/build.gradle.kts create mode 100644 libraries/tools/kotlinp/src/org/jetbrains/kotlin/kotlinp/Kotlinp.kt create mode 100644 libraries/tools/kotlinp/src/org/jetbrains/kotlin/kotlinp/KotlinpException.kt create mode 100644 libraries/tools/kotlinp/src/org/jetbrains/kotlin/kotlinp/Main.kt create mode 100644 libraries/tools/kotlinp/src/org/jetbrains/kotlin/kotlinp/printers.kt create mode 100644 libraries/tools/kotlinp/src/org/jetbrains/kotlin/kotlinp/readKotlinClassHeader.kt create mode 100644 libraries/tools/kotlinp/test/org/jetbrains/kotlin/kotlinp/test/AbstractKotlinpTest.kt create mode 100644 libraries/tools/kotlinp/test/org/jetbrains/kotlin/kotlinp/test/GenerateKotlinpTests.kt create mode 100644 libraries/tools/kotlinp/test/org/jetbrains/kotlin/kotlinp/test/KotlinpTestGenerated.java create mode 100644 libraries/tools/kotlinp/test/org/jetbrains/kotlin/kotlinp/test/KotlinpTestUtils.kt create mode 100644 libraries/tools/kotlinp/testData/Annotations.kt create mode 100644 libraries/tools/kotlinp/testData/Annotations.txt create mode 100644 libraries/tools/kotlinp/testData/Lambda.kt create mode 100644 libraries/tools/kotlinp/testData/Lambda.txt create mode 100644 libraries/tools/kotlinp/testData/LocalClass.kt create mode 100644 libraries/tools/kotlinp/testData/LocalClass.txt create mode 100644 libraries/tools/kotlinp/testData/MultiFileClass.kt create mode 100644 libraries/tools/kotlinp/testData/MultiFileClass.txt create mode 100644 libraries/tools/kotlinp/testData/NestedClasses.kt create mode 100644 libraries/tools/kotlinp/testData/NestedClasses.txt create mode 100644 libraries/tools/kotlinp/testData/PlatformType.kt create mode 100644 libraries/tools/kotlinp/testData/PlatformType.txt create mode 100644 libraries/tools/kotlinp/testData/SimpleClass.kt create mode 100644 libraries/tools/kotlinp/testData/SimpleClass.txt create mode 100644 libraries/tools/kotlinp/testData/SimplePackage.kt create mode 100644 libraries/tools/kotlinp/testData/SimplePackage.txt create mode 100644 libraries/tools/kotlinp/testData/SyntheticClass.kt create mode 100644 libraries/tools/kotlinp/testData/SyntheticClass.txt create mode 100644 libraries/tools/kotlinp/testData/TypeParameters.kt create mode 100644 libraries/tools/kotlinp/testData/TypeParameters.txt create mode 100644 libraries/tools/kotlinp/testData/VersionRequirement.kt create mode 100644 libraries/tools/kotlinp/testData/VersionRequirement.txt diff --git a/compiler/tests/org/jetbrains/kotlin/code/CodeConformanceTest.kt b/compiler/tests/org/jetbrains/kotlin/code/CodeConformanceTest.kt index f2d99cc7158..6c8c7272c0b 100644 --- a/compiler/tests/org/jetbrains/kotlin/code/CodeConformanceTest.kt +++ b/compiler/tests/org/jetbrains/kotlin/code/CodeConformanceTest.kt @@ -46,6 +46,7 @@ class CodeConformanceTest : TestCase() { "dist", "libraries/tools/kotlin-gradle-plugin-core/gradle_api_jar/build/tmp", "libraries/tools/kotlin-maven-plugin/target", + "libraries/tools/kotlinp/src", "compiler/testData/psi/kdoc", "compiler/tests/org/jetbrains/kotlin/code/CodeConformanceTest.kt", "compiler/util/src/org/jetbrains/kotlin/config/MavenComparableVersion.java" diff --git a/libraries/tools/kotlinp/build.gradle.kts b/libraries/tools/kotlinp/build.gradle.kts new file mode 100644 index 00000000000..e4d6c74bd1d --- /dev/null +++ b/libraries/tools/kotlinp/build.gradle.kts @@ -0,0 +1,40 @@ +import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar + +description = "kotlinp" + +plugins { + kotlin("jvm") +} + +val shadows by configurations.creating +shadows.extendsFrom(configurations.getByName("compile")) + +dependencies { + compile(project(":kotlinx-metadata")) + compile(project(":kotlinx-metadata-jvm")) + // We use an older version of ASM on purpose, to reduce the final application size + compile("org.ow2.asm:asm:4.0") + testCompile(commonDep("junit:junit")) + testCompile(projectTests(":generators:test-generator")) +} + +sourceSets { + "main" { projectDefault() } + "test" { projectDefault() } +} + +projectTest { + workingDir = rootDir +} + +val generateTests by generator("org.jetbrains.kotlin.kotlinp.test.GenerateKotlinpTestsKt") + +val shadowJar by task { + classifier = "shadow" + version = null + configurations = listOf(shadows) + from(the().sourceSets.getByName("main").output) + manifest { + attributes["Main-Class"] = "org.jetbrains.kotlin.kotlinp.Main" + } +} diff --git a/libraries/tools/kotlinp/src/org/jetbrains/kotlin/kotlinp/Kotlinp.kt b/libraries/tools/kotlinp/src/org/jetbrains/kotlin/kotlinp/Kotlinp.kt new file mode 100644 index 00000000000..33f01b316ab --- /dev/null +++ b/libraries/tools/kotlinp/src/org/jetbrains/kotlin/kotlinp/Kotlinp.kt @@ -0,0 +1,35 @@ +/* + * Copyright 2000-2018 JetBrains s.r.o. 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.InconsistentKotlinMetadataException +import kotlinx.metadata.jvm.KotlinClassMetadata +import java.io.File + +object Kotlinp { + internal fun renderClassFile(classFile: KotlinClassMetadata?): String = + when (classFile) { + is KotlinClassMetadata.Class -> ClassPrinter().print(classFile) + is KotlinClassMetadata.FileFacade -> FileFacadePrinter().print(classFile) + is KotlinClassMetadata.SyntheticClass -> { + if (classFile.isLambda) LambdaPrinter().print(classFile) + else buildString { appendln("synthetic class") } + } + is KotlinClassMetadata.MultiFileClassFacade -> MultiFileClassFacadePrinter().print(classFile) + is KotlinClassMetadata.MultiFileClassPart -> MultiFileClassPartPrinter().print(classFile) + is KotlinClassMetadata.Unknown -> buildString { appendln("unknown file (k=${classFile.header.kind})") } + null -> buildString { appendln("unsupported file") } + } + + internal fun readClassFile(file: File): KotlinClassMetadata? { + val header = file.readKotlinClassHeader() ?: throw KotlinpException("file is not a Kotlin class file: $file") + return try { + KotlinClassMetadata.read(header) + } catch (e: InconsistentKotlinMetadataException) { + throw KotlinpException("inconsistent Kotlin metadata: ${e.message}") + } + } +} diff --git a/libraries/tools/kotlinp/src/org/jetbrains/kotlin/kotlinp/KotlinpException.kt b/libraries/tools/kotlinp/src/org/jetbrains/kotlin/kotlinp/KotlinpException.kt new file mode 100644 index 00000000000..c71851d2b80 --- /dev/null +++ b/libraries/tools/kotlinp/src/org/jetbrains/kotlin/kotlinp/KotlinpException.kt @@ -0,0 +1,8 @@ +/* + * Copyright 2000-2018 JetBrains s.r.o. 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 + +class KotlinpException(message: String) : RuntimeException(message) diff --git a/libraries/tools/kotlinp/src/org/jetbrains/kotlin/kotlinp/Main.kt b/libraries/tools/kotlinp/src/org/jetbrains/kotlin/kotlinp/Main.kt new file mode 100644 index 00000000000..f656edf4dfa --- /dev/null +++ b/libraries/tools/kotlinp/src/org/jetbrains/kotlin/kotlinp/Main.kt @@ -0,0 +1,78 @@ +/* + * Copyright 2000-2018 JetBrains s.r.o. 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 java.io.File +import java.io.IOException + +object Main { + private fun run(args: Array) { + val paths = arrayListOf() + + var i = 0 + while (true) { + val arg = args.getOrNull(i++) ?: break + + if ("-help" == arg || "-h" == arg) { + printUsageAndExit() + } else if ("-version" == arg) { + printVersionAndExit() + } else if (arg.startsWith("-")) { + throw KotlinpException("unsupported argument: $arg") + } else { + paths.add(arg) + } + } + + for (path in paths) { + if (!path.endsWith(".class")) throw KotlinpException("only .class files are supported") + + val file = File(path) + if (!file.exists()) throw KotlinpException("file does not exist: $path") + + try { + print(Kotlinp.renderClassFile(Kotlinp.readClassFile(file))) + } catch (e: IOException) { + throw KotlinpException("I/O operation failed: ${e.message}") + } + } + + if (paths.isEmpty()) { + throw KotlinpException("no files specified") + } + } + + @JvmStatic + fun main(args: Array) { + try { + run(args) + } catch (e: KotlinpException) { + System.err.println("error: " + e.message) + System.exit(1) + } + } + + private fun printUsageAndExit() { + println( + """kotlinp: print Kotlin declarations in the given class file. + +Usage: kotlinp +where possible options include: + -version Display Kotlin version + -help (-h) Print a synopsis of options +""" + ) + System.exit(0) + } + + private fun printVersionAndExit() { + // TODO: get version from manifest + val version = "@snapshot@" + + println("Kotlin version " + version + " (JRE " + System.getProperty("java.runtime.version") + ")") + System.exit(0) + } +} diff --git a/libraries/tools/kotlinp/src/org/jetbrains/kotlin/kotlinp/printers.kt b/libraries/tools/kotlinp/src/org/jetbrains/kotlin/kotlinp/printers.kt new file mode 100644 index 00000000000..b017275ed31 --- /dev/null +++ b/libraries/tools/kotlinp/src/org/jetbrains/kotlin/kotlinp/printers.kt @@ -0,0 +1,731 @@ +/* + * Copyright 2000-2018 JetBrains s.r.o. 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.* + +private object SpecialCharacters { + const val TYPE_ALIAS_MARKER = '^' +} + +private fun visitFunction(sb: StringBuilder, flags: Int, name: String): KmFunctionVisitor = + object : KmFunctionVisitor() { + val typeParams = mutableListOf() + val params = mutableListOf() + var receiverParameterType: String? = null + var returnType: String? = null + var versionRequirement: String? = null + var jvmDesc: String? = null + + override fun visitReceiverParameterType(flags: Int): KmTypeVisitor? = + printType(flags) { receiverParameterType = it } + + override fun visitTypeParameter( + flags: Int, name: String, id: Int, variance: KmVariance + ): KmTypeParameterVisitor? = + printTypeParameter(flags, name, id, variance) { typeParams.add(it) } + + override fun visitValueParameter(flags: Int, name: String): KmValueParameterVisitor? = + printValueParameter(flags, name) { params.add(it) } + + override fun visitReturnType(flags: Int): KmTypeVisitor? = + printType(flags) { returnType = it } + + override fun visitVersionRequirement(): KmVersionRequirementVisitor? = + printVersionRequirement { versionRequirement = it } + + override fun visitExtensions(type: KmExtensionType): KmFunctionExtensionVisitor? { + if (type != JvmFunctionExtensionVisitor.TYPE) return null + return object : JvmFunctionExtensionVisitor() { + override fun visit(desc: String?) { + jvmDesc = desc + } + } + } + + override fun visitEnd() { + sb.appendln() + if (versionRequirement != null) { + sb.appendln(" // $versionRequirement") + } + if (jvmDesc != null) { + sb.appendln(" // signature: $jvmDesc") + } + sb.append(" ") + sb.appendFlags(flags, FUNCTION_FLAGS_MAP) + sb.append("fun ") + if (typeParams.isNotEmpty()) { + typeParams.joinTo(sb, prefix = "<", postfix = ">") + sb.append(" ") + } + if (receiverParameterType != null) { + sb.append(receiverParameterType).append(".") + } + sb.append(name) + params.joinTo(sb, prefix = "(", postfix = ")") + if (returnType != null) { + sb.append(": ").append(returnType) + } + sb.appendln() + } + } + +private fun visitProperty(sb: StringBuilder, flags: Int, name: String, getterFlags: Int, setterFlags: Int): KmPropertyVisitor = + object : KmPropertyVisitor() { + val typeParams = mutableListOf() + var receiverParameterType: String? = null + var returnType: String? = null + var setterParameter: String? = null + var versionRequirement: String? = null + var jvmFieldName: String? = null + var jvmFieldTypeDesc: String? = null + var jvmGetterDesc: String? = null + var jvmSetterDesc: String? = null + var jvmSyntheticMethodForAnnotationsDesc: String? = null + + override fun visitReceiverParameterType(flags: Int): KmTypeVisitor? = + printType(flags) { receiverParameterType = it } + + override fun visitTypeParameter(flags: Int, name: String, id: Int, variance: KmVariance): KmTypeParameterVisitor? = + printTypeParameter(flags, name, id, variance) { typeParams.add(it) } + + override fun visitSetterParameter(flags: Int, name: String): KmValueParameterVisitor? = + printValueParameter(flags, name) { setterParameter = it } + + override fun visitReturnType(flags: Int): KmTypeVisitor? = + printType(flags) { returnType = it } + + override fun visitVersionRequirement(): KmVersionRequirementVisitor? = + printVersionRequirement { versionRequirement = it } + + override fun visitExtensions(type: KmExtensionType): KmPropertyExtensionVisitor? { + if (type != JvmPropertyExtensionVisitor.TYPE) return null + return object : JvmPropertyExtensionVisitor() { + override fun visit(fieldName: String?, fieldTypeDesc: String?, getterDesc: String?, setterDesc: String?) { + jvmFieldName = fieldName + jvmFieldTypeDesc = fieldTypeDesc + jvmGetterDesc = getterDesc + jvmSetterDesc = setterDesc + } + + override fun visitSyntheticMethodForAnnotations(desc: String?) { + jvmSyntheticMethodForAnnotationsDesc = desc + } + } + } + + override fun visitEnd() { + sb.appendln() + if (versionRequirement != null) { + sb.appendln(" // $versionRequirement") + } + if (jvmFieldName != null || jvmFieldTypeDesc != null) { + sb.append(" // field: ${jvmFieldName ?: ""}") + if (jvmFieldTypeDesc != null) { + sb.append(":$jvmFieldTypeDesc") + } + sb.appendln() + } + if (jvmGetterDesc != null) { + sb.appendln(" // getter: $jvmGetterDesc") + } + if (jvmSetterDesc != null) { + sb.appendln(" // setter: $jvmSetterDesc") + } + if (jvmSyntheticMethodForAnnotationsDesc != null) { + sb.appendln(" // synthetic method for annotations: $jvmSyntheticMethodForAnnotationsDesc") + } + sb.append(" ") + sb.appendFlags(flags, PROPERTY_FLAGS_MAP) + sb.append(if (Flags.Property.IS_VAR(flags)) "var " else "val ") + if (typeParams.isNotEmpty()) { + typeParams.joinTo(sb, prefix = "<", postfix = ">") + sb.append(" ") + } + if (receiverParameterType != null) { + sb.append(receiverParameterType).append(".") + } + sb.append(name) + if (returnType != null) { + sb.append(": ").append(returnType) + } + if (Flags.Property.HAS_CONSTANT(flags)) { + sb.append(" /* = ... */") + } + sb.appendln() + if (Flags.Property.HAS_GETTER(flags)) { + sb.append(" ") + sb.appendFlags(getterFlags, PROPERTY_ACCESSOR_FLAGS_MAP) + sb.appendln("get") + } + if (Flags.Property.HAS_SETTER(flags)) { + sb.append(" ") + sb.appendFlags(setterFlags, PROPERTY_ACCESSOR_FLAGS_MAP) + sb.append("set") + if (setterParameter != null) { + sb.append("(").append(setterParameter).append(")") + } + sb.appendln() + } + } + } + +private fun visitConstructor(sb: StringBuilder, flags: Int): KmConstructorVisitor = + object : KmConstructorVisitor() { + val params = mutableListOf() + var versionRequirement: String? = null + var jvmDesc: String? = null + + override fun visitValueParameter(flags: Int, name: String): KmValueParameterVisitor? = + printValueParameter(flags, name) { params.add(it) } + + override fun visitVersionRequirement(): KmVersionRequirementVisitor? = + printVersionRequirement { versionRequirement = it } + + override fun visitExtensions(type: KmExtensionType): KmConstructorExtensionVisitor? { + if (type != JvmConstructorExtensionVisitor.TYPE) return null + return object : JvmConstructorExtensionVisitor() { + override fun visit(desc: String?) { + jvmDesc = desc + } + } + } + + override fun visitEnd() { + sb.appendln() + if (versionRequirement != null) { + sb.appendln(" // $versionRequirement") + } + if (jvmDesc != null) { + sb.appendln(" // signature: $jvmDesc") + } + sb.append(" ") + sb.appendFlags(flags, CONSTRUCTOR_FLAGS_MAP) + sb.append("constructor(") + params.joinTo(sb) + sb.appendln(")") + } + } + +private fun visitTypeAlias(sb: StringBuilder, flags: Int, name: String): KmTypeAliasVisitor = + object : KmTypeAliasVisitor() { + val annotations = mutableListOf() + val typeParams = mutableListOf() + var underlyingType: String? = null + var expandedType: String? = null + var versionRequirement: String? = null + + override fun visitTypeParameter(flags: Int, name: String, id: Int, variance: KmVariance): KmTypeParameterVisitor? = + printTypeParameter(flags, name, id, variance) { typeParams.add(it) } + + override fun visitUnderlyingType(flags: Int): KmTypeVisitor? = + printType(flags) { underlyingType = it } + + override fun visitExpandedType(flags: Int): KmTypeVisitor? = + printType(flags) { expandedType = it } + + override fun visitAnnotation(annotation: KmAnnotation) { + annotations += annotation + } + + override fun visitVersionRequirement(): KmVersionRequirementVisitor? = + printVersionRequirement { versionRequirement = it } + + override fun visitEnd() { + sb.appendln() + if (versionRequirement != null) { + sb.appendln(" // $versionRequirement") + } + for (annotation in annotations) { + sb.append(" ").append("@").append(renderAnnotation(annotation)).appendln() + } + sb.append(" ") + sb.appendFlags(flags, VISIBILITY_FLAGS_MAP) + sb.append("typealias ").append(name) + if (typeParams.isNotEmpty()) { + typeParams.joinTo(sb, prefix = "<", postfix = ">") + } + if (underlyingType != null) { + sb.append(" = ").append(underlyingType) + } + if (expandedType != null) { + sb.append(" /* = ").append(expandedType).append(" */") + } + sb.appendln() + } + } + +private fun printType(flags: Int, output: (String) -> Unit): KmTypeVisitor = + object : KmTypeVisitor() { + var classifier: String? = null + val arguments = mutableListOf() + var abbreviatedType: String? = null + var outerType: String? = null + var platformTypeUpperBound: String? = null + var jvmIsRaw: Boolean? = null + var jvmAnnotations = mutableListOf() + + override fun visitClass(name: ClassName) { + classifier = name + } + + override fun visitTypeParameter(id: Int) { + classifier = "T#$id" + } + + override fun visitTypeAlias(name: ClassName) { + classifier = "$name${SpecialCharacters.TYPE_ALIAS_MARKER}" + } + + override fun visitAbbreviatedType(flags: Int): KmTypeVisitor? = + printType(flags) { abbreviatedType = it } + + override fun visitArgument(flags: Int, variance: KmVariance): KmTypeVisitor? = + printType(flags) { argumentTypeString -> + arguments += buildString { + if (variance != KmVariance.INVARIANT) { + append(variance.name.toLowerCase()).append(" ") + } + append(argumentTypeString) + } + } + + override fun visitStarProjection() { + arguments += "*" + } + + override fun visitOuterType(flags: Int): KmTypeVisitor? = + printType(flags) { outerType = it } + + override fun visitFlexibleTypeUpperBound(flags: Int, typeFlexibilityId: String?): KmTypeVisitor? = + if (typeFlexibilityId == JvmTypeExtensionVisitor.PLATFORM_TYPE_ID) { + printType(flags) { platformTypeUpperBound = it } + } else null + + override fun visitExtensions(type: KmExtensionType): KmTypeExtensionVisitor? { + if (type != JvmTypeExtensionVisitor.TYPE) return null + return object : JvmTypeExtensionVisitor() { + override fun visit(isRaw: Boolean) { + jvmIsRaw = isRaw + } + + override fun visitAnnotation(annotation: KmAnnotation) { + jvmAnnotations.add(annotation) + } + } + } + + override fun visitEnd() { + output(buildString { + for (annotation in jvmAnnotations) { + append("@").append(renderAnnotation(annotation)).append(" ") + } + if (jvmIsRaw == true) { + append("/* raw */ ") + } + appendFlags(flags, TYPE_FLAGS_MAP) + if (outerType != null) { + append(outerType).append(".").append(classifier?.substringAfterLast('.')) + } else { + append(classifier) + } + if (arguments.isNotEmpty()) { + arguments.joinTo(this, prefix = "<", postfix = ">") + } + if (Flags.Type.IS_NULLABLE(flags)) { + append("?") + } + if (abbreviatedType != null) { + append(" /* = ").append(abbreviatedType).append(" */") + } + + if (platformTypeUpperBound == "$this?") { + append("!") + } else if (platformTypeUpperBound != null) { + append("..").append(platformTypeUpperBound) + } + }) + } + } + +private fun printTypeParameter(flags: Int, name: String, id: Int, variance: KmVariance, output: (String) -> Unit): KmTypeParameterVisitor = + object : KmTypeParameterVisitor() { + val bounds = mutableListOf() + val jvmAnnotations = mutableListOf() + + override fun visitUpperBound(flags: Int): KmTypeVisitor? = + printType(flags) { bounds += it } + + override fun visitExtensions(type: KmExtensionType): KmTypeParameterExtensionVisitor? { + if (type != JvmTypeParameterExtensionVisitor.TYPE) return null + return object : JvmTypeParameterExtensionVisitor() { + override fun visitAnnotation(annotation: KmAnnotation) { + jvmAnnotations.add(annotation) + } + } + } + + override fun visitEnd() { + output(buildString { + appendFlags(flags, TYPE_PARAMETER_FLAGS_MAP) + for (annotation in jvmAnnotations) { + append("@").append(renderAnnotation(annotation)).append(" ") + } + if (variance != KmVariance.INVARIANT) { + append(variance.name.toLowerCase()).append(" ") + } + append("T#$id /* $name */") + if (bounds.isNotEmpty()) { + bounds.joinTo(this, separator = " & ", prefix = " : ") + } + }) + } + } + +private fun printValueParameter(flags: Int, name: String, output: (String) -> Unit): KmValueParameterVisitor = + object : KmValueParameterVisitor() { + var varargElementType: String? = null + var type: String? = null + + override fun visitType(flags: Int): KmTypeVisitor? = + printType(flags) { type = it } + + override fun visitVarargElementType(flags: Int): KmTypeVisitor? = + printType(flags) { varargElementType = it } + + override fun visitEnd() { + output(buildString { + appendFlags(flags, VALUE_PARAMETER_FLAGS_MAP) + if (varargElementType != null) { + append("vararg ").append(name).append(": ").append(varargElementType).append(" /* ").append(type).append(" */") + } else { + append(name).append(": ").append(type) + } + if (Flags.ValueParameter.DECLARES_DEFAULT_VALUE(flags)) { + append(" /* = ... */") + } + }) + } + } + +private 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.BooleanValue -> arg.value.toString() + is KmAnnotationArgument.StringValue -> "\"${arg.value.sanitize(quote = '"')}\"" + is KmAnnotationArgument.KClassValue -> "${arg.value}::class" + is KmAnnotationArgument.EnumValue -> arg.value + is KmAnnotationArgument.AnnotationValue -> arg.value.let { annotation -> + val args = annotation.arguments.entries.joinToString { (name, argument) -> + "$name = ${renderAnnotationArgument(argument)}" + } + "${annotation.className}($args)" + } + is KmAnnotationArgument.ArrayValue -> arg.value.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.toInt()) else c) + } + } + } + +private fun printVersionRequirement(output: (String) -> Unit): KmVersionRequirementVisitor = + object : KmVersionRequirementVisitor() { + var kind: KmVersionRequirementVersionKind? = null + var level: KmVersionRequirementLevel? = null + var errorCode: Int? = null + var message: String? = null + var version: String? = null + + override fun visit(kind: KmVersionRequirementVersionKind, level: KmVersionRequirementLevel, errorCode: Int?, message: String?) { + this.kind = kind + this.level = level + this.errorCode = errorCode + this.message = message + } + + override fun visitVersion(major: Int, minor: Int, patch: Int) { + version = "$major.$minor.$patch" + } + + override fun visitEnd() { + output(buildString { + append("requires ").append( + when (kind!!) { + KmVersionRequirementVersionKind.LANGUAGE_VERSION -> "language version" + KmVersionRequirementVersionKind.COMPILER_VERSION -> "compiler version" + KmVersionRequirementVersionKind.API_VERSION -> "API version" + } + ).append(" ").append(version) + + listOfNotNull( + "level=$level", + errorCode?.let { "errorCode=$it" }, + message?.let { "message=\"$it\"" } + ).joinTo(this, prefix = " (", postfix = ")") + }) + } + } + +private fun StringBuilder.appendFlags(flags: Int, map: Map) { + for ((modifier, string) in map) { + if (modifier(flags)) { + append(string) + if (string.isNotEmpty()) append(" ") + } + } +} + +interface AbstractPrinter { + fun print(klass: T): String +} + +class ClassPrinter : KmClassVisitor(), AbstractPrinter { + private val sb = StringBuilder() + private val result = StringBuilder() + + private var flags: Int? = null + private var name: ClassName? = null + private val typeParams = mutableListOf() + private val supertypes = mutableListOf() + private var versionRequirement: String? = null + + override fun visit(flags: Int, name: ClassName) { + this.flags = flags + this.name = name + } + + override fun visitEnd() { + if (versionRequirement != null) { + result.appendln(" // $versionRequirement") + } + result.appendFlags(flags!!, CLASS_FLAGS_MAP) + result.append(name) + if (typeParams.isNotEmpty()) { + typeParams.joinTo(result, prefix = "<", postfix = ">") + } + if (supertypes.isNotEmpty()) { + result.append(" : ") + supertypes.joinTo(result) + } + result.appendln(" {") + result.append(sb) + result.appendln("}") + } + + override fun visitTypeParameter(flags: Int, name: String, id: Int, variance: KmVariance): KmTypeParameterVisitor? = + printTypeParameter(flags, name, id, variance) { typeParams.add(it) } + + override fun visitSupertype(flags: Int): KmTypeVisitor? = + printType(flags) { supertypes.add(it) } + + override fun visitConstructor(flags: Int): KmConstructorVisitor? = + visitConstructor(sb, flags) + + override fun visitFunction(flags: Int, name: String): KmFunctionVisitor? = + visitFunction(sb, flags, name) + + override fun visitProperty(flags: Int, name: String, getterFlags: Int, setterFlags: Int): KmPropertyVisitor? = + visitProperty(sb, flags, name, getterFlags, setterFlags) + + override fun visitTypeAlias(flags: Int, name: String): KmTypeAliasVisitor? = + visitTypeAlias(sb, flags, name) + + override fun visitCompanionObject(name: String) { + sb.appendln() + sb.appendln(" // companion object: $name") + } + + override fun visitNestedClass(name: String) { + sb.appendln() + sb.appendln(" // nested class: $name") + } + + override fun visitEnumEntry(name: String) { + sb.appendln() + sb.appendln(" $name,") + } + + override fun visitSealedSubclass(name: ClassName) { + sb.appendln() + sb.appendln(" // sealed subclass: $name") + } + + override fun visitVersionRequirement(): KmVersionRequirementVisitor? = + printVersionRequirement { versionRequirement = it } + + override fun print(klass: KotlinClassMetadata.Class): String { + klass.accept(this) + return result.toString() + } +} + +abstract class PackagePrinter : KmPackageVisitor() { + internal val sb = StringBuilder().apply { + appendln("package {") + } + + override fun visitEnd() { + sb.appendln("}") + } + + override fun visitFunction(flags: Int, name: String): KmFunctionVisitor? = + visitFunction(sb, flags, name) + + override fun visitProperty(flags: Int, name: String, getterFlags: Int, setterFlags: Int): KmPropertyVisitor? = + visitProperty(sb, flags, name, getterFlags, setterFlags) + + override fun visitTypeAlias(flags: Int, name: String): KmTypeAliasVisitor? = + visitTypeAlias(sb, flags, name) +} + +class FileFacadePrinter : PackagePrinter(), AbstractPrinter { + override fun print(klass: KotlinClassMetadata.FileFacade): String { + klass.accept(this) + return sb.toString() + } +} + +class LambdaPrinter : KmLambdaVisitor(), AbstractPrinter { + private val sb = StringBuilder().apply { + appendln("lambda {") + } + + override fun visitFunction(flags: Int, name: String): KmFunctionVisitor? = + visitFunction(sb, flags, name) + + override fun visitEnd() { + sb.appendln("}") + } + + override fun print(klass: KotlinClassMetadata.SyntheticClass): String { + klass.accept(this) + return sb.toString() + } +} + +class MultiFileClassPartPrinter : PackagePrinter(), AbstractPrinter { + override fun print(klass: KotlinClassMetadata.MultiFileClassPart): String { + sb.appendln(" // facade: ${klass.facadeClassName}") + klass.accept(this) + return sb.toString() + } +} + +class MultiFileClassFacadePrinter : AbstractPrinter { + override fun print(klass: KotlinClassMetadata.MultiFileClassFacade): String = + buildString { + appendln("multi-file class {") + for (part in klass.partClassNames) { + appendln(" // $part") + } + appendln("}") + } +} + +private val VISIBILITY_FLAGS_MAP = mapOf( + Flags.IS_INTERNAL to "internal", + Flags.IS_PRIVATE to "private", + Flags.IS_PRIVATE_TO_THIS to "private", + Flags.IS_PROTECTED to "protected", + Flags.IS_PUBLIC to "public", + Flags.IS_LOCAL to "local" +) + +private val COMMON_FLAGS_MAP = VISIBILITY_FLAGS_MAP + mapOf( + Flags.IS_FINAL to "final", + Flags.IS_OPEN to "open", + Flags.IS_ABSTRACT to "abstract", + Flags.IS_SEALED to "sealed" +) + +private val CLASS_FLAGS_MAP = COMMON_FLAGS_MAP + mapOf( + Flags.Class.IS_INNER to "inner", + Flags.Class.IS_DATA to "data", + Flags.Class.IS_EXTERNAL to "external", + Flags.Class.IS_EXPECT to "expect", + Flags.Class.IS_INLINE to "inline", + + Flags.Class.IS_CLASS to "class", + Flags.Class.IS_INTERFACE to "interface", + Flags.Class.IS_ENUM_CLASS to "enum class", + Flags.Class.IS_ENUM_ENTRY to "enum entry", + Flags.Class.IS_ANNOTATION_CLASS to "annotation class", + Flags.Class.IS_OBJECT to "object", + Flags.Class.IS_COMPANION_OBJECT to "companion object" +) + +private val CONSTRUCTOR_FLAGS_MAP = VISIBILITY_FLAGS_MAP + mapOf( + Flags.Constructor.IS_PRIMARY to "/* primary */" +) + +private val FUNCTION_FLAGS_MAP = COMMON_FLAGS_MAP + mapOf( + Flags.Function.IS_DECLARATION to "", + Flags.Function.IS_FAKE_OVERRIDE to "/* fake override */", + Flags.Function.IS_DELEGATION to "/* delegation */", + Flags.Function.IS_SYNTHESIZED to "/* synthesized */", + + Flags.Function.IS_OPERATOR to "operator", + Flags.Function.IS_INFIX to "infix", + Flags.Function.IS_INLINE to "inline", + Flags.Function.IS_TAILREC to "tailrec", + Flags.Function.IS_EXTERNAL to "external", + Flags.Function.IS_SUSPEND to "suspend", + Flags.Function.IS_EXPECT to "expect" +) + +private val PROPERTY_FLAGS_MAP = COMMON_FLAGS_MAP + mapOf( + Flags.Property.IS_DECLARATION to "", + Flags.Property.IS_FAKE_OVERRIDE to "/* fake override */", + Flags.Property.IS_DELEGATION to "/* delegation */", + Flags.Property.IS_SYNTHESIZED to "/* synthesized */", + + Flags.Property.IS_CONST to "const", + Flags.Property.IS_LATEINIT to "lateinit", + Flags.Property.IS_EXTERNAL to "external", + Flags.Property.IS_DELEGATED to "/* delegated */", + Flags.Property.IS_EXPECT to "expect" +) + +private val PROPERTY_ACCESSOR_FLAGS_MAP = COMMON_FLAGS_MAP + mapOf( + Flags.PropertyAccessor.IS_NOT_DEFAULT to "/* non-default */", + Flags.PropertyAccessor.IS_EXTERNAL to "external", + Flags.PropertyAccessor.IS_INLINE to "inline" +) + +private val VALUE_PARAMETER_FLAGS_MAP = mapOf( + Flags.ValueParameter.IS_CROSSINLINE to "crossinline", + Flags.ValueParameter.IS_NOINLINE to "noinline" +) + +private val TYPE_PARAMETER_FLAGS_MAP = mapOf( + Flags.TypeParameter.IS_REIFIED to "reified" +) + +private val TYPE_FLAGS_MAP = mapOf( + Flags.Type.IS_SUSPEND to "suspend" +) diff --git a/libraries/tools/kotlinp/src/org/jetbrains/kotlin/kotlinp/readKotlinClassHeader.kt b/libraries/tools/kotlinp/src/org/jetbrains/kotlin/kotlinp/readKotlinClassHeader.kt new file mode 100644 index 00000000000..f5ec236115e --- /dev/null +++ b/libraries/tools/kotlinp/src/org/jetbrains/kotlin/kotlinp/readKotlinClassHeader.kt @@ -0,0 +1,79 @@ +/* + * Copyright 2000-2018 JetBrains s.r.o. 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.KotlinClassHeader +import org.objectweb.asm.AnnotationVisitor +import org.objectweb.asm.ClassReader +import org.objectweb.asm.ClassVisitor +import org.objectweb.asm.Opcodes +import java.io.File +import java.io.FileInputStream + +internal fun File.readKotlinClassHeader(): KotlinClassHeader? { + var header: KotlinClassHeader? = null + + try { + ClassReader(FileInputStream(this)).accept(object : ClassVisitor(Opcodes.ASM4) { + override fun visitAnnotation(desc: String, visible: Boolean): AnnotationVisitor? = + if (desc == "Lkotlin/Metadata;") readMetadataVisitor { header = it } + else null + }, ClassReader.SKIP_CODE or ClassReader.SKIP_DEBUG or ClassReader.SKIP_FRAMES) + } catch (e: Exception) { + return null + } + + return header +} + +@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") +private fun readMetadataVisitor(output: (KotlinClassHeader) -> Unit): AnnotationVisitor = + object : AnnotationVisitor(Opcodes.ASM4) { + var kind: Int? = null + var metadataVersion: IntArray? = null + var bytecodeVersion: IntArray? = null + var data1: Array? = null + var data2: Array? = null + var extraString: String? = null + var packageName: String? = null + var extraInt: Int? = null + + override fun visit(name: String?, value: Any?) { + when (name) { + Metadata::k.name -> kind = value as? Int + Metadata::mv.name -> metadataVersion = value as? IntArray + Metadata::bv.name -> bytecodeVersion = value as? IntArray + Metadata::xs.name -> extraString = value as? String + Metadata::xi.name -> extraInt = value as? Int + Metadata::pn.name -> packageName = value as? String + } + } + + override fun visitArray(name: String?): AnnotationVisitor? = + when (name) { + Metadata::d1.name -> stringArrayVisitor { data1 = it } + Metadata::d2.name -> stringArrayVisitor { data2 = it } + else -> null + } + + private fun stringArrayVisitor(output: (Array) -> Unit): AnnotationVisitor { + return object : AnnotationVisitor(Opcodes.ASM4) { + val strings = mutableListOf() + + override fun visit(name: String?, value: Any?) { + (value as? String)?.let(strings::add) + } + + override fun visitEnd() { + output(strings.toTypedArray()) + } + } + } + + override fun visitEnd() { + output(KotlinClassHeader(kind, metadataVersion, bytecodeVersion, data1, data2, extraString, packageName, extraInt)) + } + } diff --git a/libraries/tools/kotlinp/test/org/jetbrains/kotlin/kotlinp/test/AbstractKotlinpTest.kt b/libraries/tools/kotlinp/test/org/jetbrains/kotlin/kotlinp/test/AbstractKotlinpTest.kt new file mode 100644 index 00000000000..4bc3b8e6cf1 --- /dev/null +++ b/libraries/tools/kotlinp/test/org/jetbrains/kotlin/kotlinp/test/AbstractKotlinpTest.kt @@ -0,0 +1,15 @@ +/* + * Copyright 2000-2018 JetBrains s.r.o. 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.test + +import org.jetbrains.kotlin.test.TestCaseWithTmpdir +import java.io.File + +abstract class AbstractKotlinpTest : TestCaseWithTmpdir() { + protected fun doTest(fileName: String) { + compileAndPrintAllFiles(File(fileName), testRootDisposable, tmpdir, compareWithTxt = true) + } +} diff --git a/libraries/tools/kotlinp/test/org/jetbrains/kotlin/kotlinp/test/GenerateKotlinpTests.kt b/libraries/tools/kotlinp/test/org/jetbrains/kotlin/kotlinp/test/GenerateKotlinpTests.kt new file mode 100644 index 00000000000..43beeb06fa6 --- /dev/null +++ b/libraries/tools/kotlinp/test/org/jetbrains/kotlin/kotlinp/test/GenerateKotlinpTests.kt @@ -0,0 +1,18 @@ +/* + * Copyright 2000-2018 JetBrains s.r.o. 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.test + +import org.jetbrains.kotlin.generators.tests.generator.testGroup + +fun main(args: Array) { + System.setProperty("java.awt.headless", "true") + + testGroup("libraries/tools/kotlinp/test", "libraries/tools/kotlinp/testData") { + testClass { + model("") + } + } +} diff --git a/libraries/tools/kotlinp/test/org/jetbrains/kotlin/kotlinp/test/KotlinpTestGenerated.java b/libraries/tools/kotlinp/test/org/jetbrains/kotlin/kotlinp/test/KotlinpTestGenerated.java new file mode 100644 index 00000000000..297be0a4cab --- /dev/null +++ b/libraries/tools/kotlinp/test/org/jetbrains/kotlin/kotlinp/test/KotlinpTestGenerated.java @@ -0,0 +1,86 @@ +/* + * Copyright 2010-2018 JetBrains s.r.o. 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.test; + +import com.intellij.testFramework.TestDataPath; +import org.jetbrains.kotlin.test.JUnit3RunnerWithInners; +import org.jetbrains.kotlin.test.KotlinTestUtils; +import org.jetbrains.kotlin.test.TargetBackend; +import org.jetbrains.kotlin.test.TestMetadata; +import org.junit.runner.RunWith; + +import java.io.File; +import java.util.regex.Pattern; + +/** This class is generated by {@link org.jetbrains.kotlin.generators.tests.TestsPackage}. DO NOT MODIFY MANUALLY */ +@SuppressWarnings("all") +@TestMetadata("libraries/tools/kotlinp/testData") +@TestDataPath("$PROJECT_ROOT") +@RunWith(JUnit3RunnerWithInners.class) +public class KotlinpTestGenerated extends AbstractKotlinpTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doTest, TargetBackend.ANY, testDataFilePath); + } + + public void testAllFilesPresentInTestData() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("libraries/tools/kotlinp/testData"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true); + } + + @TestMetadata("Annotations.kt") + public void testAnnotations() throws Exception { + runTest("libraries/tools/kotlinp/testData/Annotations.kt"); + } + + @TestMetadata("Lambda.kt") + public void testLambda() throws Exception { + runTest("libraries/tools/kotlinp/testData/Lambda.kt"); + } + + @TestMetadata("LocalClass.kt") + public void testLocalClass() throws Exception { + runTest("libraries/tools/kotlinp/testData/LocalClass.kt"); + } + + @TestMetadata("MultiFileClass.kt") + public void testMultiFileClass() throws Exception { + runTest("libraries/tools/kotlinp/testData/MultiFileClass.kt"); + } + + @TestMetadata("NestedClasses.kt") + public void testNestedClasses() throws Exception { + runTest("libraries/tools/kotlinp/testData/NestedClasses.kt"); + } + + @TestMetadata("PlatformType.kt") + public void testPlatformType() throws Exception { + runTest("libraries/tools/kotlinp/testData/PlatformType.kt"); + } + + @TestMetadata("SimpleClass.kt") + public void testSimpleClass() throws Exception { + runTest("libraries/tools/kotlinp/testData/SimpleClass.kt"); + } + + @TestMetadata("SimplePackage.kt") + public void testSimplePackage() throws Exception { + runTest("libraries/tools/kotlinp/testData/SimplePackage.kt"); + } + + @TestMetadata("SyntheticClass.kt") + public void testSyntheticClass() throws Exception { + runTest("libraries/tools/kotlinp/testData/SyntheticClass.kt"); + } + + @TestMetadata("TypeParameters.kt") + public void testTypeParameters() throws Exception { + runTest("libraries/tools/kotlinp/testData/TypeParameters.kt"); + } + + @TestMetadata("VersionRequirement.kt") + public void testVersionRequirement() throws Exception { + runTest("libraries/tools/kotlinp/testData/VersionRequirement.kt"); + } +} 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 new file mode 100644 index 00000000000..d61c4a72e02 --- /dev/null +++ b/libraries/tools/kotlinp/test/org/jetbrains/kotlin/kotlinp/test/KotlinpTestUtils.kt @@ -0,0 +1,61 @@ +/* + * Copyright 2010-2018 JetBrains s.r.o. 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.test + +import com.intellij.openapi.Disposable +import org.jetbrains.kotlin.checkers.setupLanguageVersionSettingsForCompilerTests +import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles +import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment +import org.jetbrains.kotlin.codegen.GenerationUtils +import org.jetbrains.kotlin.kotlinp.Kotlinp +import org.jetbrains.kotlin.test.ConfigurationKind +import org.jetbrains.kotlin.test.KotlinTestUtils +import org.jetbrains.kotlin.test.TestJdkKind +import java.io.File +import kotlin.test.fail + +fun compileAndPrintAllFiles(file: File, disposable: Disposable, tmpdir: File, compareWithTxt: Boolean) { + val sb = StringBuilder() + + compile(file, disposable, tmpdir) { outputFile -> + when (outputFile.extension) { + "kotlin_module" -> { + // TODO: support kotlin_module files + } + "class" -> { + sb.appendFileName(outputFile.relativeTo(tmpdir)) + sb.append(Kotlinp.renderClassFile(Kotlinp.readClassFile(outputFile)!!)) + } + else -> fail("Unknown file: $outputFile") + } + } + + if (compareWithTxt) { + KotlinTestUtils.assertEqualsToFile(File(file.path.replace(".kt", ".txt")), sb.toString()) + } +} + +private fun compile(file: File, disposable: Disposable, tmpdir: File, forEachOutputFile: (File) -> Unit) { + val environment = KotlinCoreEnvironment.createForTests( + disposable, + KotlinTestUtils.newConfiguration(ConfigurationKind.ALL, TestJdkKind.MOCK_JDK), + EnvironmentConfigFiles.JVM_CONFIG_FILES + ) + setupLanguageVersionSettingsForCompilerTests(file.readText(), environment) + val ktFile = KotlinTestUtils.createFile(file.name, file.readText(), environment.project) + GenerationUtils.compileFileTo(ktFile, environment, tmpdir) + + for (outputFile in tmpdir.walkTopDown().sortedBy { it.nameWithoutExtension }) { + if (outputFile.isFile) { + forEachOutputFile(outputFile) + } + } +} + +private fun StringBuilder.appendFileName(file: File) { + appendln("// $file") + appendln("// ------------------------------------------") +} diff --git a/libraries/tools/kotlinp/testData/Annotations.kt b/libraries/tools/kotlinp/testData/Annotations.kt new file mode 100644 index 00000000000..dc5004f538e --- /dev/null +++ b/libraries/tools/kotlinp/testData/Annotations.kt @@ -0,0 +1,52 @@ +import kotlin.reflect.KClass + +@Target(AnnotationTarget.TYPE) +annotation class A( + val z: Boolean, + val c: Char, + val b: Byte, + val s: Short, + val i: Int, + val f: Float, + val j: Long, + val d: Double, + val za: BooleanArray, + val ca: CharArray, + val ba: ByteArray, + val sa: ShortArray, + val ia: IntArray, + val fa: FloatArray, + val ja: LongArray, + val da: DoubleArray, + val str: String, + val enum: AnnotationTarget, + val klass: KClass<*>, + val anno: B +) + +annotation class B(val value: String) + +class C { + fun typeAnnotation(): @A( + true, + 'x', + 1.toByte(), + 42.toShort(), + 42424242, + -2.72f, + 239239239239239L, + 3.14, + [true], + ['\''], + [1.toByte()], + [42.toShort()], + [42424242], + [-2.72f], + [239239239239239L], + [3.14], + "aba\ncaba'\"\t\u0001\u0002\uA66E", + AnnotationTarget.CLASS, + C::class, + B(value = "aba\ncaba'\"\t\u0001\u0002\uA66E") + ) Unit {} +} diff --git a/libraries/tools/kotlinp/testData/Annotations.txt b/libraries/tools/kotlinp/testData/Annotations.txt new file mode 100644 index 00000000000..d8dc603913a --- /dev/null +++ b/libraries/tools/kotlinp/testData/Annotations.txt @@ -0,0 +1,108 @@ +// A.class +// ------------------------------------------ +public final annotation class A : kotlin/Annotation { + + // signature: (ZCBSIFJD[Z[C[B[S[I[F[J[DLjava/lang/String;Lkotlin/annotation/AnnotationTarget;Lkotlin/reflect/KClass;LB;)V + public /* primary */ constructor(z: kotlin/Boolean, c: kotlin/Char, b: kotlin/Byte, s: kotlin/Short, i: kotlin/Int, f: kotlin/Float, j: kotlin/Long, d: kotlin/Double, za: kotlin/BooleanArray, ca: kotlin/CharArray, ba: kotlin/ByteArray, sa: kotlin/ShortArray, ia: kotlin/IntArray, fa: kotlin/FloatArray, ja: kotlin/LongArray, da: kotlin/DoubleArray, str: kotlin/String, enum: kotlin/annotation/AnnotationTarget, klass: kotlin/reflect/KClass<*>, anno: B) + + // field: anno:LB; + public final val anno: B + public final get + + // field: b:B + public final val b: kotlin/Byte + public final get + + // field: ba:[B + public final val ba: kotlin/ByteArray + public final get + + // field: c:C + public final val c: kotlin/Char + public final get + + // field: ca:[C + public final val ca: kotlin/CharArray + public final get + + // field: d:D + public final val d: kotlin/Double + public final get + + // field: da:[D + public final val da: kotlin/DoubleArray + public final get + + // field: enum:Lkotlin/annotation/AnnotationTarget; + public final val enum: kotlin/annotation/AnnotationTarget + public final get + + // field: f:F + public final val f: kotlin/Float + public final get + + // field: fa:[F + public final val fa: kotlin/FloatArray + public final get + + // field: i:I + public final val i: kotlin/Int + public final get + + // field: ia:[I + public final val ia: kotlin/IntArray + public final get + + // field: j:J + public final val j: kotlin/Long + public final get + + // field: ja:[J + public final val ja: kotlin/LongArray + public final get + + // field: klass:Lkotlin/reflect/KClass; + public final val klass: kotlin/reflect/KClass<*> + public final get + + // field: s:S + public final val s: kotlin/Short + public final get + + // field: sa:[S + public final val sa: kotlin/ShortArray + public final get + + // field: str:Ljava/lang/String; + public final val str: kotlin/String + public final get + + // field: z:Z + public final val z: kotlin/Boolean + public final get + + // field: za:[Z + public final val za: kotlin/BooleanArray + public final get +} +// B.class +// ------------------------------------------ +public final annotation class B : kotlin/Annotation { + + // signature: (Ljava/lang/String;)V + public /* primary */ constructor(value: kotlin/String) + + // field: value:Ljava/lang/String; + public final val value: kotlin/String + public final get +} +// C.class +// ------------------------------------------ +public final class C : kotlin/Any { + + // signature: ()V + public /* primary */ constructor() + + // signature: typeAnnotation()V + public final fun typeAnnotation(): @A(z = true, c = 'x', b = 1.toByte(), s = 42.toShort(), i = 42424242, f = -2.72f, j = 239239239239239L, d = 3.14, za = [true], ca = ['\''], ba = [1.toByte()], sa = [42.toShort()], ia = [42424242], fa = [-2.72f], ja = [239239239239239L], da = [3.14], str = "aba\ncaba'\"\t\u0001\u0002ꙮ", enum = kotlin/annotation/AnnotationTarget.CLASS, klass = C::class, anno = B(value = "aba\ncaba'\"\t\u0001\u0002ꙮ")) kotlin/Unit +} diff --git a/libraries/tools/kotlinp/testData/Lambda.kt b/libraries/tools/kotlinp/testData/Lambda.kt new file mode 100644 index 00000000000..988706a8b02 --- /dev/null +++ b/libraries/tools/kotlinp/testData/Lambda.kt @@ -0,0 +1,4 @@ +fun test() { + val f = {} + val g = fun Unit.(s: IntArray?, t: Set): String { return "" } +} diff --git a/libraries/tools/kotlinp/testData/Lambda.txt b/libraries/tools/kotlinp/testData/Lambda.txt new file mode 100644 index 00000000000..a1e4a36440e --- /dev/null +++ b/libraries/tools/kotlinp/testData/Lambda.txt @@ -0,0 +1,21 @@ +// LambdaKt.class +// ------------------------------------------ +package { + + // signature: test()V + public final fun test(): kotlin/Unit +} +// LambdaKt$test$f$1.class +// ------------------------------------------ +lambda { + + // signature: invoke()V + local final fun (): kotlin/Unit +} +// LambdaKt$test$g$1.class +// ------------------------------------------ +lambda { + + // signature: invoke(Lkotlin/Unit;[ILjava/util/Set;)Ljava/lang/String; + local final fun kotlin/Unit.(s: kotlin/IntArray?, t: kotlin/collections/Set): kotlin/String +} diff --git a/libraries/tools/kotlinp/testData/LocalClass.kt b/libraries/tools/kotlinp/testData/LocalClass.kt new file mode 100644 index 00000000000..8ae3e83ee37 --- /dev/null +++ b/libraries/tools/kotlinp/testData/LocalClass.kt @@ -0,0 +1,9 @@ +class A { + init { + class L { + inner class I + + fun x(l: L) {} + } + } +} diff --git a/libraries/tools/kotlinp/testData/LocalClass.txt b/libraries/tools/kotlinp/testData/LocalClass.txt new file mode 100644 index 00000000000..6845ed621b8 --- /dev/null +++ b/libraries/tools/kotlinp/testData/LocalClass.txt @@ -0,0 +1,26 @@ +// A.class +// ------------------------------------------ +public final class A : kotlin/Any { + + // signature: ()V + public /* primary */ constructor() +} +// A$L.class +// ------------------------------------------ +local final class .A$L : kotlin/Any { + + // signature: ()V + public /* primary */ constructor() + + // signature: x(LA$L;)V + public final fun x(l: .A$L<.A$L.I>): kotlin/Unit + + // nested class: I +} +// A$L$I.class +// ------------------------------------------ +local final inner class .A$L.I : kotlin/Any { + + // signature: (LA$L;)V + public /* primary */ constructor() +} diff --git a/libraries/tools/kotlinp/testData/MultiFileClass.kt b/libraries/tools/kotlinp/testData/MultiFileClass.kt new file mode 100644 index 00000000000..baef12d94a7 --- /dev/null +++ b/libraries/tools/kotlinp/testData/MultiFileClass.kt @@ -0,0 +1,5 @@ +@file:JvmMultifileClass +@file:JvmName("File") +package test + +const val x = 42 diff --git a/libraries/tools/kotlinp/testData/MultiFileClass.txt b/libraries/tools/kotlinp/testData/MultiFileClass.txt new file mode 100644 index 00000000000..457e3c2c5eb --- /dev/null +++ b/libraries/tools/kotlinp/testData/MultiFileClass.txt @@ -0,0 +1,14 @@ +// test/File.class +// ------------------------------------------ +multi-file class { + // test/File__MultiFileClassKt +} +// test/File__MultiFileClassKt.class +// ------------------------------------------ +package { + // facade: test/File + + // field: x:I + public final const val x: kotlin/Int /* = ... */ + public final get +} diff --git a/libraries/tools/kotlinp/testData/NestedClasses.kt b/libraries/tools/kotlinp/testData/NestedClasses.kt new file mode 100644 index 00000000000..5fce06eaa1b --- /dev/null +++ b/libraries/tools/kotlinp/testData/NestedClasses.kt @@ -0,0 +1,16 @@ +interface A { + interface B { + interface C + } + + companion object D { + enum class E { + E1, + E2, + } + + sealed class F { + class G : F() + } + } +} diff --git a/libraries/tools/kotlinp/testData/NestedClasses.txt b/libraries/tools/kotlinp/testData/NestedClasses.txt new file mode 100644 index 00000000000..e0ff2d096d2 --- /dev/null +++ b/libraries/tools/kotlinp/testData/NestedClasses.txt @@ -0,0 +1,60 @@ +// A.class +// ------------------------------------------ +public abstract interface A : kotlin/Any { + + // companion object: D + + // nested class: B + + // nested class: D +} +// A$B.class +// ------------------------------------------ +public abstract interface A.B : kotlin/Any { + + // nested class: C +} +// A$B$C.class +// ------------------------------------------ +public abstract interface A.B.C : kotlin/Any { +} +// A$D.class +// ------------------------------------------ +public final companion object A.D : kotlin/Any { + + // signature: ()V + private /* primary */ constructor() + + // nested class: E + + // nested class: F +} +// A$D$E.class +// ------------------------------------------ +public final enum class A.D.E : kotlin/Enum { + + // signature: (Ljava/lang/String;I)V + private /* primary */ constructor() + + E1, + + E2, +} +// A$D$F.class +// ------------------------------------------ +public sealed class A.D.F : kotlin/Any { + + // signature: ()V + private /* primary */ constructor() + + // nested class: G + + // sealed subclass: A.D.F.G +} +// A$D$F$G.class +// ------------------------------------------ +public final class A.D.F.G : A.D.F { + + // signature: ()V + public /* primary */ constructor() +} diff --git a/libraries/tools/kotlinp/testData/PlatformType.kt b/libraries/tools/kotlinp/testData/PlatformType.kt new file mode 100644 index 00000000000..b003d689b5a --- /dev/null +++ b/libraries/tools/kotlinp/testData/PlatformType.kt @@ -0,0 +1,7 @@ +import java.io.File + +class PlatformType { + fun nullability() = File(".").absoluteFile + + fun mutability() = File(".").toURI().toURL().openConnection().headerFields +} diff --git a/libraries/tools/kotlinp/testData/PlatformType.txt b/libraries/tools/kotlinp/testData/PlatformType.txt new file mode 100644 index 00000000000..3463b93b8cc --- /dev/null +++ b/libraries/tools/kotlinp/testData/PlatformType.txt @@ -0,0 +1,13 @@ +// PlatformType.class +// ------------------------------------------ +public final class PlatformType : kotlin/Any { + + // signature: ()V + public /* primary */ constructor() + + // signature: mutability()Ljava/util/Map; + public final fun mutability(): kotlin/collections/MutableMap..kotlin/collections/List?>..kotlin/collections/Map..kotlin/collections/List?>? + + // signature: nullability()Ljava/io/File; + public final fun nullability(): java/io/File! +} diff --git a/libraries/tools/kotlinp/testData/SimpleClass.kt b/libraries/tools/kotlinp/testData/SimpleClass.kt new file mode 100644 index 00000000000..057211d43ac --- /dev/null +++ b/libraries/tools/kotlinp/testData/SimpleClass.kt @@ -0,0 +1,13 @@ +class SimpleClass(val p: Int = 42) { + constructor(s: Array?) : this(s?.size ?: 0) + + var x: Long = p.toLong() + external get + @JvmName("SET_X") set + + internal fun A.f(vararg z: Map): Set<*> where V : A { + error("") + } + + protected suspend inline fun g(crossinline a: () -> A, noinline b: suspend () -> T) {} +} diff --git a/libraries/tools/kotlinp/testData/SimpleClass.txt b/libraries/tools/kotlinp/testData/SimpleClass.txt new file mode 100644 index 00000000000..c0c7a52de1e --- /dev/null +++ b/libraries/tools/kotlinp/testData/SimpleClass.txt @@ -0,0 +1,29 @@ +// SimpleClass.class +// ------------------------------------------ +public final class SimpleClass : kotlin/Any { + + // signature: ([Ljava/lang/String;)V + public constructor(s: kotlin/Array?) + + // signature: (I)V + public /* primary */ constructor(p: kotlin/Int /* = ... */) + + // requires language version 1.1.0 (level=ERROR) + // signature: g(Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/experimental/Continuation;)Ljava/lang/Object; + protected final inline suspend fun g(crossinline a: kotlin/Function0, noinline b: suspend kotlin/Function1, kotlin/Any?>): kotlin/Unit + + // signature: f$test_module(Ljava/lang/Object;[Ljava/util/Map;)Ljava/util/Set; + internal final fun T#3.f(vararg z: kotlin/collections/Map /* kotlin/Array> */): kotlin/collections/Set<*> + + // field: p:I + // getter: getP()I + public final val p: kotlin/Int + public final get + + // field: x:J + // getter: getX()J + // setter: SET_X(J)V + public final var x: kotlin/Long + public final /* non-default */ external get + public final /* non-default */ set(: kotlin/Long) +} diff --git a/libraries/tools/kotlinp/testData/SimplePackage.kt b/libraries/tools/kotlinp/testData/SimplePackage.kt new file mode 100644 index 00000000000..9b4e4098fab --- /dev/null +++ b/libraries/tools/kotlinp/testData/SimplePackage.kt @@ -0,0 +1,8 @@ +internal inline fun topLevelFun(x: X) = X::class + +var topLevelProp: String? = null + private set + +typealias F = Map U?> + +typealias G = F, Set> diff --git a/libraries/tools/kotlinp/testData/SimplePackage.txt b/libraries/tools/kotlinp/testData/SimplePackage.txt new file mode 100644 index 00000000000..ee5f1063d05 --- /dev/null +++ b/libraries/tools/kotlinp/testData/SimplePackage.txt @@ -0,0 +1,18 @@ +// SimplePackageKt.class +// ------------------------------------------ +package { + + // signature: topLevelFun(Ljava/lang/Object;)Lkotlin/reflect/KClass; + internal final inline fun topLevelFun(x: T#0): kotlin/reflect/KClass + + // field: topLevelProp:Ljava/lang/String; + // getter: getTopLevelProp()Ljava/lang/String; + // setter: setTopLevelProp(Ljava/lang/String;)V + public final var topLevelProp: kotlin/String? + public final get + private final /* non-default */ set(: kotlin/String?) + + public typealias F = kotlin/collections/Map> /* = kotlin/collections/Map> */ + + public typealias G = F^, kotlin/collections/Set> /* = kotlin/collections/Map, kotlin/Function1?>> */ +} diff --git a/libraries/tools/kotlinp/testData/SyntheticClass.kt b/libraries/tools/kotlinp/testData/SyntheticClass.kt new file mode 100644 index 00000000000..6b6dcdf026d --- /dev/null +++ b/libraries/tools/kotlinp/testData/SyntheticClass.kt @@ -0,0 +1,7 @@ +fun f(a: AnnotationTarget): String? { + return when (a) { + AnnotationTarget.FUNCTION -> "1" + AnnotationTarget.PROPERTY -> "2" + else -> null + } +} diff --git a/libraries/tools/kotlinp/testData/SyntheticClass.txt b/libraries/tools/kotlinp/testData/SyntheticClass.txt new file mode 100644 index 00000000000..3d943ded838 --- /dev/null +++ b/libraries/tools/kotlinp/testData/SyntheticClass.txt @@ -0,0 +1,10 @@ +// SyntheticClassKt.class +// ------------------------------------------ +package { + + // signature: f(Lkotlin/annotation/AnnotationTarget;)Ljava/lang/String; + public final fun f(a: kotlin/annotation/AnnotationTarget): kotlin/String? +} +// SyntheticClassKt$WhenMappings.class +// ------------------------------------------ +synthetic class diff --git a/libraries/tools/kotlinp/testData/TypeParameters.kt b/libraries/tools/kotlinp/testData/TypeParameters.kt new file mode 100644 index 00000000000..e68d01219de --- /dev/null +++ b/libraries/tools/kotlinp/testData/TypeParameters.kt @@ -0,0 +1,17 @@ +class A { + fun a(t: T) {} + + inner class B { + fun b(t: T, u: U, v: V) where T : Comparable, T : Cloneable {} + + fun bb(t: T) {} + + inner class C { + fun c(t: T?, u: U?) {} + + fun cc(t: T?, u: U?) {} + + fun z(c: A.B.C) {} + } + } +} diff --git a/libraries/tools/kotlinp/testData/TypeParameters.txt b/libraries/tools/kotlinp/testData/TypeParameters.txt new file mode 100644 index 00000000000..7172b3151f6 --- /dev/null +++ b/libraries/tools/kotlinp/testData/TypeParameters.txt @@ -0,0 +1,43 @@ +// A.class +// ------------------------------------------ +public final class A : kotlin/Any { + + // signature: ()V + public /* primary */ constructor() + + // signature: a(Ljava/lang/Object;)V + public final fun a(t: T#1): kotlin/Unit + + // nested class: B +} +// A$B.class +// ------------------------------------------ +public final inner class A.B : kotlin/Any { + + // signature: (LA;)V + public /* primary */ constructor() + + // signature: b(Ljava/lang/Comparable;Ljava/lang/Object;Ljava/lang/Object;)V + public final fun & kotlin/Cloneable, T#4 /* U */> b(t: T#3, u: T#4, v: T#2): kotlin/Unit + + // signature: bb(Ljava/lang/Object;)V + public final fun bb(t: T#0): kotlin/Unit + + // nested class: C +} +// A$B$C.class +// ------------------------------------------ +public final inner class A.B.C : kotlin/Any { + + // signature: (LA$B;)V + public /* primary */ constructor() + + // signature: c(Ljava/lang/Object;Ljava/lang/Object;)V + public final fun c(t: T#5?, u: T#6?): kotlin/Unit + + // signature: cc(Ljava/lang/Object;Ljava/lang/Object;)V + public final fun cc(t: T#3?, u: T#4?): kotlin/Unit + + // signature: z(LA$B$C;)V + public final fun z(c: A.B.C): kotlin/Unit +} diff --git a/libraries/tools/kotlinp/testData/VersionRequirement.kt b/libraries/tools/kotlinp/testData/VersionRequirement.kt new file mode 100644 index 00000000000..849eb85cb93 --- /dev/null +++ b/libraries/tools/kotlinp/testData/VersionRequirement.kt @@ -0,0 +1,19 @@ +@file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") +package test + +import kotlin.internal.RequireKotlin +import kotlin.internal.RequireKotlinVersionKind + +@RequireKotlin("1.2", "Klass must not be used!", DeprecationLevel.WARNING, RequireKotlinVersionKind.API_VERSION) +class Klass + +class Konstructor @RequireKotlin("42.0", "Konstructor must not be used!", DeprecationLevel.WARNING, RequireKotlinVersionKind.LANGUAGE_VERSION, 42) constructor() + +@RequireKotlin("1.1", level = DeprecationLevel.HIDDEN, versionKind = RequireKotlinVersionKind.API_VERSION, errorCode = 314) +typealias Typealias = String + +@RequireKotlin("1.2.40", level = DeprecationLevel.ERROR, versionKind = RequireKotlinVersionKind.COMPILER_VERSION) +fun function() {} + +@RequireKotlin("1.3", "property must not be used!") +val property = "" diff --git a/libraries/tools/kotlinp/testData/VersionRequirement.txt b/libraries/tools/kotlinp/testData/VersionRequirement.txt new file mode 100644 index 00000000000..24773b08494 --- /dev/null +++ b/libraries/tools/kotlinp/testData/VersionRequirement.txt @@ -0,0 +1,34 @@ +// test/Klass.class +// ------------------------------------------ + // requires API version 1.2.0 (level=WARNING, message="Klass must not be used!") +public final class test/Klass : kotlin/Any { + + // signature: ()V + public /* primary */ constructor() +} +// test/Konstructor.class +// ------------------------------------------ +public final class test/Konstructor : kotlin/Any { + + // requires language version 42.0.0 (level=WARNING, errorCode=42, message="Konstructor must not be used!") + // signature: ()V + public /* primary */ constructor() +} +// test/VersionRequirementKt.class +// ------------------------------------------ +package { + + // requires compiler version 1.2.40 (level=ERROR) + // signature: function()V + public final fun function(): kotlin/Unit + + // requires language version 1.3.0 (level=ERROR, message="property must not be used!") + // field: property:Ljava/lang/String; + // getter: getProperty()Ljava/lang/String; + // synthetic method for annotations: property$annotations()V + public final val property: kotlin/String /* = ... */ + public final get + + // requires API version 1.1.0 (level=HIDDEN, errorCode=314) + public typealias Typealias = kotlin/String /* = kotlin/String */ +} diff --git a/settings.gradle b/settings.gradle index 08f701816a7..e736977389c 100644 --- a/settings.gradle +++ b/settings.gradle @@ -134,6 +134,7 @@ include ":kotlin-build-common", ":tools:binary-compatibility-validator", ":tools:kotlin-stdlib-js-merger", ":tools:kotlin-stdlib-gen", + ":tools:kotlinp", ":kotlin-gradle-plugin-api", ":kotlin-gradle-plugin", ":kotlin-gradle-plugin-test-utils-embeddable", @@ -240,6 +241,7 @@ project(':kotlin-source-sections-compiler-plugin').projectDir = "$rootDir/plugin project(':tools:binary-compatibility-validator').projectDir = "$rootDir/libraries/tools/binary-compatibility-validator" as File project(':tools:kotlin-stdlib-js-merger').projectDir = "$rootDir/libraries/tools/kotlin-stdlib-js-merger" as File project(':tools:kotlin-stdlib-gen').projectDir = "$rootDir/libraries/tools/kotlin-stdlib-gen" as File +project(':tools:kotlinp').projectDir = "$rootDir/libraries/tools/kotlinp" as File project(':kotlin-gradle-plugin-api').projectDir = "$rootDir/libraries/tools/kotlin-gradle-plugin-api" as File project(':kotlin-gradle-plugin').projectDir = "$rootDir/libraries/tools/kotlin-gradle-plugin" as File project(':kotlin-gradle-plugin-test-utils-embeddable').projectDir = "$rootDir/libraries/tools/kotlin-gradle-plugin-test-utils-embeddable" as File