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:
committed by
Alexander Udalov
parent
63e355d979
commit
012ffa2993
@@ -3,10 +3,15 @@
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
@file:Suppress("MemberVisibilityCanBePrivate")
|
||||
|
||||
package kotlinx.metadata.jvm
|
||||
|
||||
import kotlinx.metadata.InconsistentKotlinMetadataException
|
||||
import kotlinx.metadata.KmAnnotation
|
||||
import kotlinx.metadata.KmClass
|
||||
import kotlinx.metadata.KmClassVisitor
|
||||
import kotlinx.metadata.impl.accept
|
||||
import org.jetbrains.kotlin.metadata.jvm.JvmModuleProtoBuf
|
||||
import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmMetadataVersion
|
||||
import org.jetbrains.kotlin.metadata.jvm.deserialization.ModuleMapping
|
||||
@@ -62,6 +67,17 @@ class KotlinModuleMetadata(@Suppress("CanBeParameter", "MemberVisibilityCanBePri
|
||||
*/
|
||||
}
|
||||
|
||||
override fun visitOptionalAnnotationClass(): KmClassVisitor? {
|
||||
/*
|
||||
return object : ClassWriter(TODO() /* use StringTableImpl here */) {
|
||||
override fun visitEnd() {
|
||||
b.addOptionalAnnotationClass(t)
|
||||
}
|
||||
}
|
||||
*/
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the metadata of the module file that was written with this writer.
|
||||
*
|
||||
@@ -80,13 +96,19 @@ class KotlinModuleMetadata(@Suppress("CanBeParameter", "MemberVisibilityCanBePri
|
||||
fun accept(v: KmModuleVisitor) {
|
||||
for ((fqName, parts) in data.packageFqName2Parts) {
|
||||
val (fileFacades, multiFileClassParts) = parts.parts.partition { parts.getMultifileFacadeName(it) == null }
|
||||
v.visitPackageParts(fqName, fileFacades, multiFileClassParts.associate { it to parts.getMultifileFacadeName(it)!! })
|
||||
v.visitPackageParts(fqName, fileFacades, multiFileClassParts.associateWith { parts.getMultifileFacadeName(it)!! })
|
||||
}
|
||||
|
||||
for (annotation in data.moduleData.annotations) {
|
||||
v.visitAnnotation(KmAnnotation(annotation, emptyMap()))
|
||||
}
|
||||
|
||||
for (classProto in data.moduleData.optionalAnnotations) {
|
||||
v.visitOptionalAnnotationClass()?.let {
|
||||
classProto.accept(it, data.moduleData.nameResolver)
|
||||
}
|
||||
}
|
||||
|
||||
v.visitEnd()
|
||||
}
|
||||
|
||||
@@ -147,6 +169,17 @@ abstract class KmModuleVisitor(private val delegate: KmModuleVisitor? = null) {
|
||||
delegate?.visitAnnotation(annotation)
|
||||
}
|
||||
|
||||
/**
|
||||
* Visits an `@OptionalExpectation`-annotated annotation class declared in this module.
|
||||
* Such classes are not materialized to bytecode on JVM, but the Kotlin compiler stores their metadata in the module file on JVM,
|
||||
* and loads it during compilation of dependent modules, in order to avoid reporting "unresolved reference" errors on usages.
|
||||
*
|
||||
* Multiplatform projects are an experimental feature of Kotlin, and their behavior and/or binary format
|
||||
* may change in a subsequent release.
|
||||
*/
|
||||
open fun visitOptionalAnnotationClass(): KmClassVisitor? =
|
||||
delegate?.visitOptionalAnnotationClass()
|
||||
|
||||
/**
|
||||
* Visits the end of the module.
|
||||
*/
|
||||
@@ -171,6 +204,16 @@ class KmModule : KmModuleVisitor() {
|
||||
*/
|
||||
val annotations: MutableList<KmAnnotation> = ArrayList(0)
|
||||
|
||||
/**
|
||||
* `@OptionalExpectation`-annotated annotation classes declared in this module.
|
||||
* Such classes are not materialized to bytecode on JVM, but the Kotlin compiler stores their metadata in the module file on JVM,
|
||||
* and loads it during compilation of dependent modules, in order to avoid reporting "unresolved reference" errors on usages.
|
||||
*
|
||||
* Multiplatform projects are an experimental feature of Kotlin, and their behavior and/or binary format
|
||||
* may change in a subsequent release.
|
||||
*/
|
||||
val optionalAnnotationClasses: MutableList<KmClass> = ArrayList(0)
|
||||
|
||||
override fun visitPackageParts(fqName: String, fileFacades: List<String>, multiFileClassParts: Map<String, String>) {
|
||||
packageParts[fqName] = KmPackageParts(fileFacades.toMutableList(), multiFileClassParts.toMutableMap())
|
||||
}
|
||||
@@ -179,6 +222,9 @@ class KmModule : KmModuleVisitor() {
|
||||
annotations.add(annotation)
|
||||
}
|
||||
|
||||
override fun visitOptionalAnnotationClass(): KmClass =
|
||||
KmClass().also(optionalAnnotationClasses::add)
|
||||
|
||||
/**
|
||||
* Populates the given visitor with data in this module.
|
||||
*
|
||||
@@ -189,6 +235,7 @@ class KmModule : KmModuleVisitor() {
|
||||
visitor.visitPackageParts(fqName, parts.fileFacades, parts.multiFileClassParts)
|
||||
}
|
||||
annotations.forEach(visitor::visitAnnotation)
|
||||
optionalAnnotationClasses.forEach { visitor.visitOptionalAnnotationClass()?.let(it::accept) }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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("}")
|
||||
}
|
||||
|
||||
|
||||
+5
@@ -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())
|
||||
}
|
||||
|
||||
@@ -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() {}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user