From 64838d5ec9a2073f927c4ddf47b35e5e1ad88afe Mon Sep 17 00:00:00 2001 From: Leonid Startsev Date: Thu, 19 Jan 2023 18:52:31 +0100 Subject: [PATCH] Extract separate ArrayKClassValue from KClassValue Remove `data` from KmAnnotation --- .../jvm/api/kotlinx-metadata-jvm.api | 18 ++- .../src/kotlinx/metadata/annotations.kt | 131 ++++++++++++++++-- .../kotlinx/metadata/internal/readUtils.kt | 9 +- .../kotlinx/metadata/internal/writeUtils.kt | 4 + .../org/jetbrains/kotlin/kotlinp/printers.kt | 3 +- 5 files changed, 150 insertions(+), 15 deletions(-) diff --git a/libraries/kotlinx-metadata/jvm/api/kotlinx-metadata-jvm.api b/libraries/kotlinx-metadata/jvm/api/kotlinx-metadata-jvm.api index 442f9a0db3b..8cefceaa654 100644 --- a/libraries/kotlinx-metadata/jvm/api/kotlinx-metadata-jvm.api +++ b/libraries/kotlinx-metadata/jvm/api/kotlinx-metadata-jvm.api @@ -128,10 +128,6 @@ public final class kotlinx/metadata/InconsistentKotlinMetadataException : java/l public final class kotlinx/metadata/KmAnnotation { public fun (Ljava/lang/String;Ljava/util/Map;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Ljava/util/Map; - public final fun copy (Ljava/lang/String;Ljava/util/Map;)Lkotlinx/metadata/KmAnnotation; - public static synthetic fun copy$default (Lkotlinx/metadata/KmAnnotation;Ljava/lang/String;Ljava/util/Map;ILjava/lang/Object;)Lkotlinx/metadata/KmAnnotation; public fun equals (Ljava/lang/Object;)Z public final fun getArguments ()Ljava/util/Map; public final fun getClassName ()Ljava/lang/String; @@ -153,6 +149,19 @@ public final class kotlinx/metadata/KmAnnotationArgument$AnnotationValue : kotli public fun toString ()Ljava/lang/String; } +public final class kotlinx/metadata/KmAnnotationArgument$ArrayKClassValue : kotlinx/metadata/KmAnnotationArgument { + public fun (Ljava/lang/String;I)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()I + public final fun copy (Ljava/lang/String;I)Lkotlinx/metadata/KmAnnotationArgument$ArrayKClassValue; + public static synthetic fun copy$default (Lkotlinx/metadata/KmAnnotationArgument$ArrayKClassValue;Ljava/lang/String;IILjava/lang/Object;)Lkotlinx/metadata/KmAnnotationArgument$ArrayKClassValue; + public fun equals (Ljava/lang/Object;)Z + public final fun getArrayDimensionCount ()I + public final fun getClassName ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + public final class kotlinx/metadata/KmAnnotationArgument$ArrayValue : kotlinx/metadata/KmAnnotationArgument { public fun (Ljava/util/List;)V public final fun component1 ()Ljava/util/List; @@ -250,6 +259,7 @@ public final class kotlinx/metadata/KmAnnotationArgument$IntValue : kotlinx/meta } public final class kotlinx/metadata/KmAnnotationArgument$KClassValue : kotlinx/metadata/KmAnnotationArgument { + public fun (Ljava/lang/String;)V public fun (Ljava/lang/String;I)V public final fun component1 ()Ljava/lang/String; public final fun component2 ()I diff --git a/libraries/kotlinx-metadata/src/kotlinx/metadata/annotations.kt b/libraries/kotlinx-metadata/src/kotlinx/metadata/annotations.kt index 73f9cde143b..f0aa311d15e 100644 --- a/libraries/kotlinx-metadata/src/kotlinx/metadata/annotations.kt +++ b/libraries/kotlinx-metadata/src/kotlinx/metadata/annotations.kt @@ -1,5 +1,5 @@ /* - * Copyright 2010-2018 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors. * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. */ @@ -9,22 +9,37 @@ package kotlinx.metadata * Represents an annotation, written to the Kotlin metadata. Note that not all annotations are written to metadata on all platforms. * For example, on JVM most of the annotations are written directly on the corresponding declarations in the class file, * and entries in the metadata only have a flag ([Flag.HAS_ANNOTATIONS]) to signal if they do have annotations in the bytecode. - * On JVM, only annotations on type parameters and types are serialized to the Kotlin metadata. + * On JVM, only annotations on type parameters and types are serialized to the Kotlin metadata + * (see `KmType.annotations` and `KmTypeParameter.annotations` JVM extensions) * * @param className the fully qualified name of the annotation class * @param arguments explicitly specified arguments to the annotation; does not include default values for annotation parameters * (specified in the annotation class declaration) */ -data class KmAnnotation(val className: ClassName, val arguments: Map) +class KmAnnotation(val className: ClassName, val arguments: Map) { + override fun equals(other: Any?): Boolean = + this === other || other is KmAnnotation && className == other.className && arguments == other.arguments + + + override fun hashCode(): Int = 31 * className.hashCode() + arguments.hashCode() + + override fun toString(): String { + val args = arguments.toList().joinToString { (k, v) -> "$k = $v" } + return "@$className($args)" + } +} /** * Represents an argument to the annotation. */ +@Suppress("IncorrectFormatting") // one-line KDoc sealed class KmAnnotationArgument { /** * A kind of annotation argument, whose value is directly accessible via [value]. * This is possible for annotation arguments of primitive types, unsigned types and strings. * + * For example, in `@Foo("bar")`, argument of `Foo` is a [StringValue] with [value] equal to `bar`. + * * @param T the type of the value of this argument */ sealed class LiteralValue : KmAnnotationArgument() { @@ -34,29 +49,129 @@ sealed class KmAnnotationArgument { abstract val value: T } + // For all inheritors of LiteralValue: KDoc is automatically copied from base property `value` + // to the overridden one. However, it does not do this with classes, and we do not have `@inheritdoc` :( + + /** An annotation argument with a [Byte] type. */ data class ByteValue(override val value: Byte) : LiteralValue() + /** An annotation argument with a [Char] type. */ data class CharValue(override val value: Char) : LiteralValue() + /** An annotation argument with a [Short] type. */ data class ShortValue(override val value: Short) : LiteralValue() + /** An annotation argument with a [Int] type. */ data class IntValue(override val value: Int) : LiteralValue() + /** An annotation argument with a [Long] type. */ data class LongValue(override val value: Long) : LiteralValue() + /** An annotation argument with a [Float] type. */ data class FloatValue(override val value: Float) : LiteralValue() + /** An annotation argument with a [Double] type. */ data class DoubleValue(override val value: Double) : LiteralValue() + /** An annotation argument with a [Boolean] type. */ data class BooleanValue(override val value: Boolean) : LiteralValue() + /** An annotation argument with a [UByte] type. */ data class UByteValue(override val value: UByte) : LiteralValue() - + /** An annotation argument with a [UShort] type. */ data class UShortValue(override val value: UShort) : LiteralValue() - + /** An annotation argument with a [UInt] type. */ data class UIntValue(override val value: UInt) : LiteralValue() - + /** An annotation argument with a [ULong] type. */ data class ULongValue(override val value: ULong) : LiteralValue() + /** An annotation argument with a [String] type. */ data class StringValue(override val value: String) : LiteralValue() - data class KClassValue(val className: ClassName, val arrayDimensionCount: Int) : KmAnnotationArgument() - + /** + * An annotation argument with an enumeration type. + * + * For example, in `@Foo(MyEnum.OPTION_A)`, argument of `Foo` is an `EnumValue` + * with [enumClassName] `MyEnum` and [enumEntryName] `OPTION_A`. + * + * @property enumClassName FQ name of the enum class + * @property enumEntryName Name of the enum entry + */ data class EnumValue(val enumClassName: ClassName, val enumEntryName: String) : KmAnnotationArgument() + /** + * An annotation argument which is another annotation value. + * + * For example, with the following classes: + * ``` + * annotation class Bar(val s: String) + * + * annotation class Foo(val b: Bar) + * ``` + * It is possible to apply such annotation: `@Foo(Bar("baz"))`. In this case, argument `Foo.b` is represented by + * `AnnotationValue` which [annotation] property contains all necessary information: the fact that it is a `Bar` annotation ([KmAnnotation.className]) + * and that it has a "baz" argument ([KmAnnotation.arguments]). + * + * @property annotation Annotation instance with all its arguments. + */ data class AnnotationValue(val annotation: KmAnnotation) : KmAnnotationArgument() + + /** + * An annotation argument with an array type, i.e. several values of one arbitrary type. + * + * For example, in `@Foo(["a", "b", "c"])` argument of `Foo` is an `ArrayValue` with [elements] + * being a list of three [StringValue] elements: "a", "b", and "c" (without quotes). + * + * Don't confuse with [ArrayKClassValue], which represents KClass value. + * + * @property elements Values of elements in the array. + */ data class ArrayValue(val elements: List) : KmAnnotationArgument() + + /** + * An annotation argument of KClass type. + * + * For example, in `@Foo(String::class)` argument of `Foo` is a `KClassValue` which [className] is `kotlin/String`. + * + * All the KClasses, except `kotlin.Array`, are represented by this class. + * Arrays are a specific case and represented by [ArrayKClassValue] — see its documentation for details. + * + * @property className FQ name of the referenced class. + */ + @Suppress("DEPRECATION_ERROR") + data class KClassValue @Deprecated( + "Use single-argument constructor instead or ArrayKClassValue", + level = DeprecationLevel.ERROR + ) constructor( + val className: ClassName, + @Deprecated( + "Use ArrayKClassValue instead", + level = DeprecationLevel.ERROR + ) val arrayDimensionCount: Int + ) : KmAnnotationArgument() { + constructor(className: ClassName) : this(className, 0) + + init { + require(arrayDimensionCount == 0) { "KClassValue must not have array dimensions. For Array::class, use ArrayKClassValue." } + } + } + + /** + * Annotation argument whose type is one of the `kotlin.Array` KClasses. + * + * Due to the nature of JVM, Arrays with different arguments are represented by different `kotlin.reflect.KClass` and `java.lang.Class` instances + * (while e.g. `List` has always one KClass instance regardless of generic arguments). + * As a result, Kotlin compiler allows to use generic arguments for the arrays in annotations: `@Foo(Array>::class)`. + * [ArrayKClassValue] allows to distinguish such arguments from regular [KClassValue]. + * + * [className] is the array element type's fully qualified name — in the example above, it is `kotlin/String`. + * [arrayDimensionCount] is the dimension of the array — 1 for `Array::class`, 2 for `Array>::class`, and so on. + * It is guaranteed to be at least 1. + * + * For platforms other than JVM, the value for [className] is always `kotlin/Any` and [arrayDimensionCount] is always 1. + * This represents untyped `Array::class` expression. + * + * See also: https://youtrack.jetbrains.com/issue/KT-31230 + * + * @property className FQ name of the referenced array element type. + * @property arrayDimensionCount Referenced array dimension. + */ + data class ArrayKClassValue(val className: ClassName, val arrayDimensionCount: Int) : KmAnnotationArgument() { + init { + require(arrayDimensionCount > 0) { "ArrayKClassValue must have at least one dimension. For regular X::class argument, use KClassValue." } + } + } } diff --git a/libraries/kotlinx-metadata/src/kotlinx/metadata/internal/readUtils.kt b/libraries/kotlinx-metadata/src/kotlinx/metadata/internal/readUtils.kt index e6c4c9a102a..fb3a0050f29 100644 --- a/libraries/kotlinx-metadata/src/kotlinx/metadata/internal/readUtils.kt +++ b/libraries/kotlinx-metadata/src/kotlinx/metadata/internal/readUtils.kt @@ -1,5 +1,5 @@ /* - * Copyright 2010-2018 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors. * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. */ @@ -44,7 +44,12 @@ fun ProtoBuf.Annotation.Argument.Value.readAnnotationArgument(strings: NameResol DOUBLE -> KmAnnotationArgument.DoubleValue(doubleValue) BOOLEAN -> KmAnnotationArgument.BooleanValue(intValue != 0L) STRING -> KmAnnotationArgument.StringValue(strings.getString(stringValue)) - CLASS -> KmAnnotationArgument.KClassValue(strings.getClassName(classId), arrayDimensionCount) + CLASS -> strings.getClassName(classId).let { className -> + if (arrayDimensionCount == 0) + KmAnnotationArgument.KClassValue(className) + else + KmAnnotationArgument.ArrayKClassValue(className, arrayDimensionCount) + } ENUM -> KmAnnotationArgument.EnumValue(strings.getClassName(classId), strings.getString(enumValueId)) ANNOTATION -> KmAnnotationArgument.AnnotationValue(annotation.readAnnotation(strings)) ARRAY -> KmAnnotationArgument.ArrayValue(arrayElementList.mapNotNull { it.readAnnotationArgument(strings) }) diff --git a/libraries/kotlinx-metadata/src/kotlinx/metadata/internal/writeUtils.kt b/libraries/kotlinx-metadata/src/kotlinx/metadata/internal/writeUtils.kt index 213e7c70e60..985f59c2d8e 100644 --- a/libraries/kotlinx-metadata/src/kotlinx/metadata/internal/writeUtils.kt +++ b/libraries/kotlinx-metadata/src/kotlinx/metadata/internal/writeUtils.kt @@ -86,6 +86,10 @@ fun KmAnnotationArgument.writeAnnotationArgument(strings: StringTable): ProtoBuf is KmAnnotationArgument.KClassValue -> { this.type = ProtoBuf.Annotation.Argument.Value.Type.CLASS this.classId = strings.getClassNameIndex(className) + } + is KmAnnotationArgument.ArrayKClassValue -> { + this.type = ProtoBuf.Annotation.Argument.Value.Type.CLASS + this.classId = strings.getClassNameIndex(className) this.arrayDimensionCount = this@writeAnnotationArgument.arrayDimensionCount } is KmAnnotationArgument.EnumValue -> { diff --git a/libraries/tools/kotlinp/src/org/jetbrains/kotlin/kotlinp/printers.kt b/libraries/tools/kotlinp/src/org/jetbrains/kotlin/kotlinp/printers.kt index b7ae05a7072..b2e4b6e5a05 100644 --- a/libraries/tools/kotlinp/src/org/jetbrains/kotlin/kotlinp/printers.kt +++ b/libraries/tools/kotlinp/src/org/jetbrains/kotlin/kotlinp/printers.kt @@ -285,7 +285,8 @@ private fun renderAnnotationArgument(arg: KmAnnotationArgument): String = is KmAnnotationArgument.ULongValue -> arg.value.toString() + "uL" is KmAnnotationArgument.BooleanValue -> arg.value.toString() is KmAnnotationArgument.StringValue -> "\"${arg.value.sanitize(quote = '"')}\"" - is KmAnnotationArgument.KClassValue -> buildString { + is KmAnnotationArgument.KClassValue -> "${arg.className}::class" + is KmAnnotationArgument.ArrayKClassValue -> buildString { repeat(arg.arrayDimensionCount) { append("kotlin/Array<") } append(arg.className).append("::class") repeat(arg.arrayDimensionCount) { append(">") }