diff --git a/libraries/kotlinx-metadata/jvm/ReadMe.md b/libraries/kotlinx-metadata/jvm/ReadMe.md index 55a1c07fdd6..34cc3412d75 100644 --- a/libraries/kotlinx-metadata/jvm/ReadMe.md +++ b/libraries/kotlinx-metadata/jvm/ReadMe.md @@ -58,92 +58,56 @@ when (metadata) { } ``` -Let's assume we've obtained an instance of `KotlinClassMetadata.Class`; other kinds of classes are handled similarly, except some of them have metadata in a slightly different form. The main way to make sense of the underlying metadata is to invoke `accept`, passing an instance of [`KmClassVisitor`](../src/kotlinx/metadata/visitors.kt) to handle the incoming information (`Km` is a shorthand for “Kotlin metadata”): +Let's assume we've obtained an instance of `KotlinClassMetadata.Class`; other kinds of classes are handled similarly, except some of them have metadata in a slightly different form. The main way to make sense of the underlying metadata is to invoke `toKmClass`, which returns an instance of `KmClass` (`Km` is a shorthand for “Kotlin metadata”): ```kotlin -metadata.accept(object : KmClassVisitor() { - override fun visitFunction(flags: Flags, name: String): KmFunctionVisitor? { - // This will be called for each function in the class. "name" is the - // function name, and "flags" represent modifier flags (see below) - - ... - - // Return an instance of KmFunctionVisitor for more details, - // or null if this function is of no interest - } -}) +val klass = metadata.toKmClass() +println(klass.functions.map { it.name }) +println(klass.properties.map { it.name }) ``` Please refer to [`MetadataSmokeTest.listInlineFunctions`](test/kotlinx/metadata/test/MetadataSmokeTest.kt) for an example where all inline functions are read from the class metadata along with their JVM signatures. ## Flags -Numerous `visit*` methods take the parameter named `flags`. These flags represent modifiers or other boolean attributes of a declaration or a type. To check if a certain flag is present, call one of the flags in [`Flag`](../src/kotlinx/metadata/Flag.kt) on the given integer value. The set of applicable flags is documented in each `visit*` method. For example, for functions, this is common declaration flags (visibility, modality) plus `Flag.Function` flags: +Numerous objects have a property named `flags` of type `Flags`. These flags represent modifiers or other boolean attributes of a declaration or a type. To check if a certain flag is present, call one of the flags in [`Flag`](../src/kotlinx/metadata/Flag.kt) on the given integer value. The set of applicable flags is documented on each property or the corresponding `visit*` method. For example, for functions, this is common declaration flags (visibility, modality) plus `Flag.Function` flags: ```kotlin -override fun visitFunction(flags: Flags, name: String): KmFunctionVisitor? { - if (Flag.IS_PUBLIC(flags)) { - println("function $name is public") - } - if (Flag.Function.IS_SUSPEND(flags)) { - println("function $name has the 'suspend' modifier") - } - ... +val function: KmFunction = ... +if (Flag.IS_PUBLIC(function.flags)) { + println("function ${function.name} is public") } -``` - -## Extensions - -Certain information in the metadata of Kotlin `.class` files is JVM-only, just like certain information in the `.meta.js` files on Kotlin/JS is JS-only. To retain the possibility to release Kotlin/JS- (and later, Kotlin/Native-) reading metadata library, we've extracted most of the API of this library to a platform-independent [`kotlinx-metadata`](../) (here platform-independent means not that it's agnostic to the platform it's compiled to, but that it's agnostic to the platform it allows to read Kotlin metadata from), and `kotlinx-metadata-jvm` is a small addition with JVM-only data. - -To read platform-specific (in this case, JVM-specific) data, each visitor that has that data declares a `visitExtensions` method, taking the [*extension type*](../src/kotlinx/metadata/extensions.kt) and returning the visitor of that type, capable of reading platform-specific data. The intended way to implement `visitExtensions` for JVM is to check if the given extension type is of the needed JVM extension visitor and return a new instance of that visitor, or return null otherwise. Each JVM extension visitor has its type declared in the `TYPE` variable in the companion object. For example, to read JVM extensions on the property: - -```kotlin -override fun visitExtensions(type: KmExtensionType): KmPropertyExtensionVisitor? { - // If these are JVM property extensions, read them by returning a visitor - if (type == JvmPropertyExtensionVisitor.TYPE) { - return object : JvmPropertyExtensionVisitor() { - // Read JVM property extensions - ... - } - } - - // If these are extensions of some other type, ignore them - return null +if (Flag.Function.IS_SUSPEND(function.flags)) { + println("function ${function.name} has the 'suspend' modifier") } ``` ## Writing metadata -To create metadata of a Kotlin class file from scratch, use one of the `Writer` classes declared in `KotlinClassMetadata`'s subclasses. Writers of relevant classes inherit from the corresponding `Km*Visitor` classes. Invoke the corresponding `visit*` methods successively on the writer to add declarations (do not forget to call `visitEnd` where applicable!), and call `write` in the end to produce the metadata. Finally, use `KotlinClassMetadata.header` to obtain the raw data and write it to the `kotlin.Metadata` annotation on a class file. +To create metadata of a Kotlin class file from scratch, construct an instance of `KmClass`/`KmPackage`/`KmLambda`, fill it with the data and call `accept` with the `Writer` class declared in the corresponding `KotlinClassMetadata` subclass. Finally, use `KotlinClassMetadata.header` to obtain the raw data and write it to the `kotlin.Metadata` annotation on a class file. -When using metadata writers from Kotlin source code, it's very convenient to use Kotlin scoping functions such as `run` to reduce boilerplate: +When using metadata writers from Kotlin source code, it's very convenient to use Kotlin scoping functions such as `apply` to reduce boilerplate: ```kotlin // Writing metadata of a class -val header = KotlinClassMetadata.Class.Writer().run { - // Visiting the name and the modifiers on the class. +val klass = KmClass().apply { + // Setting the name and the modifiers of the class. // Flags are constructed by invoking "flagsOf(...)" - visit(flagsOf(Flag.IS_PUBLIC), "MyClass") - + name = "MyClass" + flags = flagsOf(Flag.IS_PUBLIC) + // Adding one public primary constructor - visitConstructor(flagsOf(Flag.IS_PUBLIC, Flag.Constructor.IS_PRIMARY))!!.run { - // Visiting JVM signature (for example, to be used by kotlin-reflect) - (visitExtensions(JvmConstructorExtensionVisitor.TYPE) as JvmConstructorExtensionVisitor).run { - visit(JvmMethodSignature("", "()V")) - } - - // Not forgetting to call visitEnd at the end of visit of the declaration - visitEnd() + constructors += KmConstructor(flagsOf(Flag.IS_PUBLIC, Flag.Constructor.IS_PRIMARY)).apply { + // Setting the JVM signature (for example, to be used by kotlin-reflect) + signature = JvmMethodSignature("", "()V") } - + ... - ... - - // Finally writing everything to arrays of bytes - write().header } +// Finally writing everything to arrays of bytes +val header = KotlinClassMetadata.Class.Writer().apply(klass::accept).write().header + // Use header.kind, header.data1, header.data2, etc. to write values to kotlin.Metadata ... ``` @@ -158,29 +122,24 @@ Similarly to how `KotlinClassMetadata` is used to read/write metadata of Kotlin // Read the module metadata val bytes = File("META-INF/main.kotlin_module").readBytes() val metadata = KotlinModuleMetadata.read(bytes) -metadata.accept(object : KmModuleVisitor() { - ... -} +val module = metadata.toKmModule() +... // Write the module metadata -val bytes = KotlinModuleMetadata.Writer().run { - visitPackageParts(...) - - write().bytes -} +val bytes = KotlinModuleMetadata.Writer().apply(module::accept).write().bytes File("META-INF/main.kotlin_module").writeBytes(bytes) ``` ## Laziness -Note that until you invoke `accept` on a `KotlinClassMetadata` or `KotlinModuleMetadata` instance, the data is not completely parsed and verified. If you need to check if the data is not horribly corrupted before proceeding, make sure to call `accept`, even with an empty visitor: +Note that until you load the actual underlying data of a `KotlinClassMetadata` or `KotlinModuleMetadata` instance by invoking `accept` or one of the `toKm...` methods, the data is not completely parsed and verified. If you need to check if the data is not horribly corrupted before proceeding, ensure that either of those is called: ```kotlin val metadata: KotlinClassMetadata.Class = ... try { // Guarantees eager parsing of the underlying data - metadata.accept(object : KmClassVisitor() {}) + metadata.toKmClass() } catch (e: Exception) { System.err.println("Metadata is corrupted!") } diff --git a/libraries/kotlinx-metadata/jvm/test/kotlinx/metadata/test/MetadataSmokeTest.kt b/libraries/kotlinx-metadata/jvm/test/kotlinx/metadata/test/MetadataSmokeTest.kt index 739e0bf94de..699cf8c8d98 100644 --- a/libraries/kotlinx-metadata/jvm/test/kotlinx/metadata/test/MetadataSmokeTest.kt +++ b/libraries/kotlinx-metadata/jvm/test/kotlinx/metadata/test/MetadataSmokeTest.kt @@ -31,26 +31,11 @@ class MetadataSmokeTest { fun bar() {} } - val inlineFunctions = mutableListOf() + val classMetadata = KotlinClassMetadata.read(L::class.java.readMetadata()) as KotlinClassMetadata.Class - val klass = KotlinClassMetadata.read(L::class.java.readMetadata()) as KotlinClassMetadata.Class - klass.accept(object : KmClassVisitor() { - override fun visitFunction(flags: Flags, name: String): KmFunctionVisitor? { - return object : KmFunctionVisitor() { - override fun visitExtensions(type: KmExtensionType): KmFunctionExtensionVisitor? { - if (type != JvmFunctionExtensionVisitor.TYPE) return null - - return object : JvmFunctionExtensionVisitor() { - override fun visit(signature: JvmMethodSignature?) { - if (Flag.Function.IS_INLINE(flags) && signature != null) { - inlineFunctions += signature.asString() - } - } - } - } - } - } - }) + val inlineFunctions = classMetadata.toKmClass().functions + .filter { Flag.Function.IS_INLINE(it.flags) } + .mapNotNull { it.signature?.asString() } assertEquals( listOf("foo(Lkotlin/jvm/functions/Function0;)Ljava/lang/String;"), @@ -65,29 +50,22 @@ class MetadataSmokeTest { // fun hello(): String = "Hello, world!" // } - val header = KotlinClassMetadata.Class.Writer().run { - visit(flagsOf(Flag.IS_PUBLIC), "Hello") - visitConstructor(flagsOf(Flag.IS_PUBLIC, Flag.Constructor.IS_PRIMARY))!!.run { - (visitExtensions(JvmConstructorExtensionVisitor.TYPE) as JvmConstructorExtensionVisitor).run { - visit(JvmMethodSignature("", "()V")) - } - visitEnd() + val klass = KmClass().apply { + name = "Hello" + flags = flagsOf(Flag.IS_PUBLIC) + constructors += KmConstructor(flagsOf(Flag.IS_PUBLIC, Flag.Constructor.IS_PRIMARY)).apply { + signature = JvmMethodSignature("", "()V") } - visitFunction(flagsOf(Flag.IS_PUBLIC, Flag.Function.IS_DECLARATION), "hello")!!.run { - visitReturnType(0)!!.run { - visitClass("kotlin/String") - visitEnd() + functions += KmFunction(flagsOf(Flag.IS_PUBLIC, Flag.Function.IS_DECLARATION), "hello").apply { + returnType = KmType(flagsOf()).apply { + classifier = KmClassifier.Class("kotlin/String") } - (visitExtensions(JvmFunctionExtensionVisitor.TYPE) as JvmFunctionExtensionVisitor).run { - visit(JvmMethodSignature("hello", "()Ljava/lang/String;")) - } - visitEnd() + signature = JvmMethodSignature("hello", "()Ljava/lang/String;") } - visitEnd() - - write().header } + val header = KotlinClassMetadata.Class.Writer().apply(klass::accept).write().header + // Then, produce the bytecode of a .class file with ASM val bytes = ClassWriter(0).run { @@ -189,12 +167,6 @@ class MetadataSmokeTest { packageName = annotation.packageName ) ) as KotlinClassMetadata.SyntheticClass - metadata.accept(object : KmLambdaVisitor() { - override fun visitFunction(flags: Flags, name: String) = - object : KmFunctionVisitor() { - override fun visitVersionRequirement() = - object : KmVersionRequirementVisitor() {} - } - }) + metadata.accept(KmLambda()) } }