diff --git a/libraries/kotlinx-metadata/jvm/ChangeLog.md b/libraries/kotlinx-metadata/jvm/ChangeLog.md index d02b4f4ff61..f9aabbb2f66 100644 --- a/libraries/kotlinx-metadata/jvm/ChangeLog.md +++ b/libraries/kotlinx-metadata/jvm/ChangeLog.md @@ -1,15 +1,29 @@ # kotlinx-metadata-jvm +## 0.7.0 + +This release features several significant API changes. To help with migration, we've prepared a special [guide](Migration.md#migrating-from-06x-to-070). + +- Update to Kotlin 1.9 with metadata version 1.9, support reading/writing metadata of version 2.0 which will be used in Kotlin 2.0 +- Rework flags API (see [migration from Flags API to Attributes API](Migration.md#migration-from-flags-api-to-attributes-api)). +- Restructure `KotlinClass(Module)Metadata.write/read` (see [changes in reading and writing API](Migration.md#changes-in-reading-and-writing-api)). +- Add `@JvmStatic` + `@JvmOverloads` to writing functions in `KotlinClassMetadata` +- Deprecate `KmModule.annotations` for removal because it is always empty and should not be used. +- Move `KmModuleFragment` to an `kotlinx.metadata.internal.common` package. This class is intended for internal use only. If you have use-cases for it, please report an issue to YouTrack. +- Improve `toString()` for `KmAnnotationArgument` +- Add missing deprecation for `KmExtensionType` and experimentality for `KmConstantValue`. +- Enhance kotlinx-metadata-jvm KDoc and set up Dokka. + ## 0.6.2 This release uses Kotlin 1.8.20 with metadata version 1.8, and as a special case, is able to read metadata of version 2.0. This is done as an incentive to test K2 compiler and 2.0 language version. No other changes were made and no migration is needed. -Note: 0.6.1 was released with incorrect fix for this problem. Do not use 0.6.1. +Note: 0.6.1 was released with an incorrect fix for this problem. Do not use 0.6.1. ## 0.6.0 -This release features several significant API changes. To help with migration, we've prepared a special [guide](Migration.md). +This release features several significant API changes. To help with migration, we've prepared a special [guide](Migration.md#migrating-from-050-to-06x). - Update to Kotlin 1.8 with metadata version 1.8, support reading/writing metadata of version 1.9 which will be used in Kotlin 1.9 - Deprecate Visitors API diff --git a/libraries/kotlinx-metadata/jvm/Migration.md b/libraries/kotlinx-metadata/jvm/Migration.md index f34d2e336a6..71dd5dea1ba 100644 --- a/libraries/kotlinx-metadata/jvm/Migration.md +++ b/libraries/kotlinx-metadata/jvm/Migration.md @@ -1,9 +1,122 @@ # Kotlinx-metadata migration guide -Starting with 0.6.0 release, Kotlin team is focused on revisiting and improving kotlinx-metadata API, with an aim to provide a stable release +Starting with 0.6.0 release, Kotlin team is focused on revisiting and improving kotlinx-metadata-jvm API, with an aim to provide a stable release in the near future. As a result, the API was reshaped, with cuts here and there, so we've provided a migration guide to help you with updates. -## Migrating from 0.5.0 to 0.6.0+ +## Migrating from 0.6.x to 0.7.0 + +### Migration from Flags API to Attributes API + +There are a lot of various modifiers that can be applied to various Kotlin declarations: `public`, `sealed`, `data`, `inline`, and so on. +Introspecting them is one of the major use cases for the kotlinx-metadata library. +In previous versions, they were represented as a bit mask and a `Flag.invoke` function: + +```kotlin +fun nameOfPublicDataClass(kmClass: KmClass): ClassName? { + return if (Flag.Common.IS_PUBLIC(kmClass.flags) && Flag.Class.IS_DATA(kmClass.flags)) kmClass.name else null +} +``` + +Such an API is based on implementation details and has problems, such as: + +* Discoverability; it is hard to stumble across this API while looking into autocompletion pop-up for `kmClass.`. +* Non-OOP style and counterintuitivity; naturally, one wants to call something like `function.isPublic()`, and not `isPublic(function)`. +* Applicability and soundness; it is not a compiler error to call `Flag.IS_PUBLIC(kmType.flags)`, while `KmType` obviously does not have a notion of visibility. + +To solve these problems, Flags API **is deprecated completely** for replacement with the new Attributes API. +Attributes API is fairly simple and essentially is a broad set of extensions on Km nodes, +such as `KmClass.visibility`, `KmClass.isData`, `KmFunction.isInline`, and so on. + +For almost every deprecated `Flag` instance, there is a corresponding mutable extension property. +There are some exceptions to this rule, notably visibility and modality. +For them, all flags are replaced with a single extension that returns an enum value. +For example, `Flag.IS_PUBLIC(): Boolean` and `Flag.IS_PRIVATE(): Boolean` +are both replaced by `KmClass.visibility: Visibility` or `KmFunction.visibility: Visibility`. + +For migration, replace `Flag` usages with access to corresponding extension properties. +Deprecation message for a particular `Flag` instance should help you identify a correct extension. + +The function above can now be rewritten in a more clear and idiomatic way: + +```kotlin +fun nameOfPublicDataClass(kmClass: KmClass): ClassName? { + return if (kmClass.visibility == Visibility.PUBLIC && kmClass.isData) kmClass.name else null +} +``` + +### Changes in reading and writing API + +After collecting some feedback from our users, we have decided to implement the following changes: + +#### `KotlinClassMetadata.read()` now has a non-nullable return type of `KotlinClassMetadata` + +Previously, `null` value was returned in case a metadata version was not compatible. +It was not very convenient, as `null` value does not state the exact version of the metadata and why it is incompatible (is it too old or too new). +Now, an `IllegalStateException` with appropriate message is thrown in this case. +To migrate, simply remove null-checks around `KotlinClassMetadata.read()`. In case you need special logic for the incompatible version case, add a try-catch block. + +The same is applicable to `KotlinModuleMetadata.read()`. + +#### Metadata validation moved from `toKmClass()` methods to `KotlinClassMetadata.read()` + +Checks related to validation of metadata encoding that used to happen in conversion methods +(like `KotlinClassMetadata.Class.toKmClass()`, `KotlinClassMetadata.FileFacade.toKmPackage()`, etc) +are moved to the `KotlinClassMetadata.read()`. +As a result, these conversion methods are deprecated and replaced by the similarly named properties because they are no longer throw exceptions and simply return a cached result, +while actual conversion is moved to `KotlinClassMetadata.read()`. +To migrate, use provided replacements: + +**Before:** +```kotlin +when (val metadata = KotlinClassMetadata.read(header)) { + is KotlinClassMetadata.Class -> handleClass(metadata.toKmClass()) + is KotlinClassMetadata.FileFacade -> handleFileFacade(metadata.toKmPackage()) + is KotlinClassMetadata.MultiFileClassPart -> handleMFClassPart(metadata.facadeClassName, metadata.toKmPackage()) + ... +} +``` + +**After:** +```kotlin +when (val metadata = KotlinClassMetadata.read(header)) { + is KotlinClassMetadata.Class -> handleClass(metadata.kmClass) + is KotlinClassMetadata.FileFacade -> handleFileFacade(metadata.kmPackage) + is KotlinClassMetadata.MultiFileClassPart -> handleMFClassPart(metadata.facadeClassName, metadata.kmPackage) + ... +} +``` + +The same is applicable to `KotlinModuleMetadata.toKmModule()`. + +#### Writing API returns the encoded result directly + +`KotlinClassMetadata.writeClass` and other similar functions now return a `Metadata` instance directly +instead of returning a new `KotlinClassMetadata` instance. +Previous behavior caused confusion because it was not clear what operations are valid on a returned instance and +how exactly it is supposed to be used. + +As a result, `KotlinClassMetadata.annotationData: Metadata` property has been made private because there is no longer need for it to be exposed. +To migrate, simply remove `.annotationData` access from your writing logic: + +**Before:** +```kotlin +fun save(kmClass: KmClass) { + val metadata: Metadata = KotlinClassMetadata.writeClass(kmClass).annotationData + writeToClassFile(metadata) +} +``` + +**After:** +```kotlin +fun save(kmClass: KmClass) { + val metadata: Metadata = KotlinClassMetadata.writeClass(kmClass) + writeToClassFile(metadata) +} +``` + +The same is applicable to `KotlinModuleMetadata.write`: it returns `ByteArray` directly. + +## Migrating from 0.5.0 to 0.6.x There are several significant changes between 0.5.0 and 0.6.0: @@ -91,4 +204,4 @@ fun saveClass(kmClass: KmClass) { // Write Metadata.data1, data2, etc using ASM } -``` \ No newline at end of file +``` diff --git a/libraries/kotlinx-metadata/jvm/ReadMe.md b/libraries/kotlinx-metadata/jvm/ReadMe.md index 2f927d31e98..06ceba5aa85 100644 --- a/libraries/kotlinx-metadata/jvm/ReadMe.md +++ b/libraries/kotlinx-metadata/jvm/ReadMe.md @@ -48,7 +48,7 @@ val metadata = KotlinClassMetadata.read(metadataAnnotation) ``` `KotlinClassMetadata` is a sealed class, with subclasses representing all the different kinds of classes generated by the Kotlin compiler. -Unless you're sure that you're reading a class of a specific kind and can do a simple cast, a `when` is a good choice to handle all the possibilities: +Unless you are sure that you are reading a class of a specific kind and can do a simple cast, a `when` is a good choice to handle all the possibilities: ```kotlin when (metadata) { @@ -61,7 +61,7 @@ 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. +Let us assume we have 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 access the `kmClass` property, which returns an instance of `KmClass` (`Km` is a shorthand for “Kotlin metadata”): ```kotlin @@ -72,29 +72,28 @@ 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 +## Attributes -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 for each Node property which has type `Flags`. -For example, functions have common declaration flags (visibility, modality) plus `Flag.Function` flags: +Most of the Km nodes (`KmClass`, `KmFunction`, `KmType`, and so on) have a set of extension properties that allow to get and set various attributes. +Most of these attributes are boolean values, but some of them, such as visibility, are represented by enum classes. +For example, you can check function visibility and presence of `suspend` modifier with corresponding extension properties: ```kotlin val function: KmFunction = ... -if (Flag.IS_PUBLIC(function.flags)) { +if (function.visibility == Visibility.PUBLIC) { println("function ${function.name} is public") } -if (Flag.Function.IS_SUSPEND(function.flags)) { +if (function.isSuspend) { println("function ${function.name} has the 'suspend' modifier") } ``` ## Writing metadata -To create metadata of a Kotlin class file from scratch, construct an instance of `KmClass`/`KmPackage`/`KmLambda`, fill it with the data and call corresponding `KotlinClassMetadata.write` function. +To create metadata of a Kotlin class file from scratch, construct an instance of `KmClass`/`KmPackage`/`KmLambda`, fill it with the data, and call corresponding `KotlinClassMetadata.write` function. Resulting `kotlin.Metadata` annotation can be written to a class file. -When using metadata writers from Kotlin source code, it's very convenient to use Kotlin scoping functions such as `apply` to reduce boilerplate: +When using metadata writers from Kotlin source code, it is very convenient to use Kotlin scoping functions such as `apply` to reduce boilerplate: ```kotlin // Writing metadata of a class