Support new scheme of compilation of OptionalExpectation annotations

Instead of generating these annotation classes as package-private on
JVM, serialize their metadata to the .kotlin_module file, and load it
when compiling dependent multiplatform modules.

The problem with generating them as package-private was that
kotlin-stdlib for JVM would end up declaring symbols from other
platforms, which would include some annotations from package
kotlin.native. But using that package is discouraged by some tools
because it has a Java keyword in its name. In particular, jlink refused
to work with such artifact altogether (KT-21266).

 #KT-38652 Fixed
This commit is contained in:
Alexander Udalov
2019-12-17 17:43:42 +01:00
committed by Alexander Udalov
parent 63e355d979
commit 012ffa2993
42 changed files with 1469 additions and 145 deletions
@@ -10,7 +10,7 @@ import kotlinx.metadata.jvm.KotlinClassMetadata
import kotlinx.metadata.jvm.KotlinModuleMetadata
import java.io.File
class Kotlinp(val settings: KotlinpSettings) {
class Kotlinp(private val settings: KotlinpSettings) {
internal fun renderClassFile(classFile: KotlinClassMetadata?): String =
when (classFile) {
is KotlinClassMetadata.Class -> ClassPrinter(settings).print(classFile)
@@ -35,7 +35,7 @@ class Kotlinp(val settings: KotlinpSettings) {
}
internal fun renderModuleFile(metadata: KotlinModuleMetadata?): String =
if (metadata != null) ModuleFilePrinter().print(metadata)
if (metadata != null) ModuleFilePrinter(settings).print(metadata)
else buildString { appendln("unsupported file") }
internal fun readModuleFile(file: File): KotlinModuleMetadata? =
@@ -689,7 +689,7 @@ interface AbstractPrinter<in T : KotlinClassMetadata> {
class ClassPrinter(private val settings: KotlinpSettings) : KmClassVisitor(), AbstractPrinter<KotlinClassMetadata.Class> {
private val sb = StringBuilder()
private val result = StringBuilder()
internal val result = StringBuilder()
private var flags: Flags? = null
private var name: ClassName? = null
@@ -880,7 +880,9 @@ class MultiFileClassFacadePrinter : AbstractPrinter<KotlinClassMetadata.MultiFil
}
}
class ModuleFilePrinter : KmModuleVisitor() {
class ModuleFilePrinter(private val settings: KotlinpSettings) : KmModuleVisitor() {
private val optionalAnnotations = mutableListOf<ClassPrinter>()
private val sb = StringBuilder().apply {
appendln("module {")
}
@@ -901,7 +903,18 @@ class ModuleFilePrinter : KmModuleVisitor() {
// TODO
}
override fun visitOptionalAnnotationClass(): KmClassVisitor =
ClassPrinter(settings).also(optionalAnnotations::add)
override fun visitEnd() {
if (optionalAnnotations.isNotEmpty()) {
sb.appendln()
sb.appendln(" // Optional annotations")
sb.appendln()
for (element in optionalAnnotations) {
sb.appendln(" " + element.result.toString().replace("\n", "\n ").trimEnd())
}
}
sb.appendln("}")
}
@@ -63,6 +63,11 @@ public class KotlinpTestGenerated extends AbstractKotlinpTest {
runTest("libraries/tools/kotlinp/testData/NestedClasses.kt");
}
@TestMetadata("OptionalAnnotation.kt")
public void testOptionalAnnotation() throws Exception {
runTest("libraries/tools/kotlinp/testData/OptionalAnnotation.kt");
}
@TestMetadata("PlatformType.kt")
public void testPlatformType() throws Exception {
runTest("libraries/tools/kotlinp/testData/PlatformType.kt");
@@ -17,6 +17,7 @@ import org.jetbrains.kotlin.jvm.compiler.AbstractLoadJavaTest
import org.jetbrains.kotlin.kotlinp.Kotlinp
import org.jetbrains.kotlin.kotlinp.KotlinpSettings
import org.jetbrains.kotlin.test.ConfigurationKind
import org.jetbrains.kotlin.test.InTextDirectivesUtils
import org.jetbrains.kotlin.test.KotlinTestUtils
import org.jetbrains.kotlin.test.TestJdkKind
import java.io.File
@@ -63,7 +64,7 @@ fun compileAndPrintAllFiles(file: File, disposable: Disposable, tmpdir: File, co
KotlinTestUtils.assertEqualsToFile(File(file.path.replace(".kt", ".txt")), main.toString())
}
if (readWriteAndCompare) {
if (readWriteAndCompare && InTextDirectivesUtils.findStringWithPrefixes(file.readText(), "// NO_READ_WRITE_COMPARE") == null) {
assertEquals("Metadata is different after transformation with visitors.", main.toString(), afterVisitors.toString())
assertEquals("Metadata is different after transformation with nodes.", main.toString(), afterNodes.toString())
}
+20
View File
@@ -0,0 +1,20 @@
// !LANGUAGE: +MultiPlatformProjects
// !USE_EXPERIMENTAL: kotlin.ExperimentalMultiplatform
// NO_READ_WRITE_COMPARE
package test
@OptionalExpectation
expect annotation class A(val x: Int)
@OptionalExpectation
expect annotation class B(val a: Array<String>)
@OptionalExpectation
expect annotation class C()
@Suppress("OPTIONAL_DECLARATION_USAGE_IN_NON_COMMON_SOURCE")
@A(42)
@B(["OK", ""])
@C
fun ok() {}
+44
View File
@@ -0,0 +1,44 @@
// test/OptionalAnnotationKt.class
// ------------------------------------------
package {
// signature: ok()V
public final fun ok(): kotlin/Unit
}
// META-INF/test-module.kotlin_module
// ------------------------------------------
module {
package test {
test/OptionalAnnotationKt
}
// Optional annotations
public final expect annotation class test/A : kotlin/Annotation {
// signature: <init>(I)V
public /* primary */ constructor(x: kotlin/Int)
public final expect val x: kotlin/Int
public final get
// module name: main
}
public final expect annotation class test/B : kotlin/Annotation {
// signature: <init>(Lkotlin/Array;)V
public /* primary */ constructor(a: kotlin/Array<kotlin/String>)
public final expect val a: kotlin/Array<kotlin/String>
public final get
// module name: main
}
public final expect annotation class test/C : kotlin/Annotation {
// signature: <init>()V
public /* primary */ constructor()
// module name: main
}
}