Improve API of annotation arguments in kotlinx-metadata

See changes in ChangeLog.md for more information
This commit is contained in:
Alexander Udalov
2021-03-26 20:51:52 +01:00
parent 1a82158472
commit 5f7f4a4ac8
13 changed files with 99 additions and 70 deletions
@@ -4,6 +4,14 @@
- Update to Kotlin 1.5 with metadata version 1.5.
Note: metadata of version 1.5 is readable by Kotlin compiler/reflection of versions 1.4 and later.
- Breaking change: improve API of annotation arguments.
`KmAnnotationArgument` doesn't have `val value: T` anymore, it was moved to a subclass named `KmAnnotationArgument.LiteralValue<T>`.
The property `value` is:
- renamed to `annotation` in `AnnotationValue`
- renamed to `elements` in `ArrayValue`
- removed in favor of `enumClassName`/`enumEntryName` in `EnumValue`
- removed in favor of `className`/`arrayDimensionCount` in `KClassValue`
- changed type from signed to unsigned integer types in `UByteValue`, `UShortValue`, `UIntValue`, `ULongValue`
- [`KT-44783`](https://youtrack.jetbrains.com/issue/KT-44783) Add Flag.IS_VALUE for value classes
- Breaking change: `Flag.IS_INLINE` is deprecated, use `Flag.IS_VALUE` instead
- Breaking change: deprecate `KotlinClassHeader.bytecodeVersion` and `KotlinClassHeader`'s constructor that takes a bytecode version array.
@@ -118,7 +118,7 @@ internal class KlibPropertyExtension : KlibPropertyExtensionVisitor(), KmPropert
val setterAnnotations: MutableList<KmAnnotation> = mutableListOf()
var uniqId: UniqId? = null
var file: Int? = null
var compileTimeValue: KmAnnotationArgument<*>? = null
var compileTimeValue: KmAnnotationArgument? = null
override fun visitAnnotation(annotation: KmAnnotation) {
annotations += annotation
@@ -140,7 +140,7 @@ internal class KlibPropertyExtension : KlibPropertyExtensionVisitor(), KmPropert
this.uniqId = uniqId
}
override fun visitCompileTimeValue(value: KmAnnotationArgument<*>) {
override fun visitCompileTimeValue(value: KmAnnotationArgument) {
this.compileTimeValue = value
}
@@ -299,7 +299,7 @@ internal class KlibMetadataExtensions : MetadataExtensions {
)
}
override fun visitCompileTimeValue(value: KmAnnotationArgument<*>) {
override fun visitCompileTimeValue(value: KmAnnotationArgument) {
proto.setExtension(
KlibMetadataProtoBuf.compileTimeValue,
value.writeAnnotationArgument(c.strings).build()
@@ -62,7 +62,7 @@ var KmProperty.file: Int?
klibExtensions.file = value
}
var KmProperty.compileTimeValue: KmAnnotationArgument<*>?
var KmProperty.compileTimeValue: KmAnnotationArgument?
get() = klibExtensions.compileTimeValue
set(value) {
klibExtensions.compileTimeValue = value
@@ -65,7 +65,7 @@ abstract class KlibPropertyExtensionVisitor : KmPropertyExtensionVisitor {
abstract fun visitUniqId(uniqId: UniqId)
abstract fun visitCompileTimeValue(value: KmAnnotationArgument<*>)
abstract fun visitCompileTimeValue(value: KmAnnotationArgument)
override val type: KmExtensionType
get() = TYPE
@@ -15,43 +15,53 @@ package kotlinx.metadata
* @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<String, KmAnnotationArgument<*>>)
data class KmAnnotation(val className: ClassName, val arguments: Map<String, KmAnnotationArgument>)
/**
* Represents an argument to the annotation.
*
* @param T the type of the value of this argument
*/
sealed class KmAnnotationArgument<out T : Any> {
sealed class KmAnnotationArgument {
/**
* The value of this argument.
* 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.
*
* @param T the type of the value of this argument
*/
abstract val value: T
data class ByteValue(override val value: Byte) : KmAnnotationArgument<Byte>()
data class CharValue(override val value: Char) : KmAnnotationArgument<Char>()
data class ShortValue(override val value: Short) : KmAnnotationArgument<Short>()
data class IntValue(override val value: Int) : KmAnnotationArgument<Int>()
data class LongValue(override val value: Long) : KmAnnotationArgument<Long>()
data class FloatValue(override val value: Float) : KmAnnotationArgument<Float>()
data class DoubleValue(override val value: Double) : KmAnnotationArgument<Double>()
data class BooleanValue(override val value: Boolean) : KmAnnotationArgument<Boolean>()
data class UByteValue(override val value: Byte) : KmAnnotationArgument<Byte>()
data class UShortValue(override val value: Short) : KmAnnotationArgument<Short>()
data class UIntValue(override val value: Int) : KmAnnotationArgument<Int>()
data class ULongValue(override val value: Long) : KmAnnotationArgument<Long>()
data class StringValue(override val value: String) : KmAnnotationArgument<String>()
data class KClassValue(val className: ClassName, val arrayDimensionCount: Int) : KmAnnotationArgument<String>() {
override val value: String = className + "[]".repeat(arrayDimensionCount)
sealed class LiteralValue<out T : Any> : KmAnnotationArgument() {
/**
* The value of this argument.
*/
abstract val value: T
}
data class EnumValue(val enumClassName: ClassName, val enumEntryName: String) : KmAnnotationArgument<String>() {
override val value: String = "$enumClassName.$enumEntryName"
}
data class ByteValue(override val value: Byte) : LiteralValue<Byte>()
data class CharValue(override val value: Char) : LiteralValue<Char>()
data class ShortValue(override val value: Short) : LiteralValue<Short>()
data class IntValue(override val value: Int) : LiteralValue<Int>()
data class LongValue(override val value: Long) : LiteralValue<Long>()
data class FloatValue(override val value: Float) : LiteralValue<Float>()
data class DoubleValue(override val value: Double) : LiteralValue<Double>()
data class BooleanValue(override val value: Boolean) : LiteralValue<Boolean>()
data class AnnotationValue(override val value: KmAnnotation) : KmAnnotationArgument<KmAnnotation>()
data class ArrayValue(override val value: List<KmAnnotationArgument<*>>) : KmAnnotationArgument<List<KmAnnotationArgument<*>>>()
// TODO: remove @ExperimentalUnsignedTypes once bootstrap stdlib has stable unsigned types.
@ExperimentalUnsignedTypes
data class UByteValue(override val value: UByte) : LiteralValue<UByte>()
@ExperimentalUnsignedTypes
data class UShortValue(override val value: UShort) : LiteralValue<UShort>()
@ExperimentalUnsignedTypes
data class UIntValue(override val value: UInt) : LiteralValue<UInt>()
@ExperimentalUnsignedTypes
data class ULongValue(override val value: ULong) : LiteralValue<ULong>()
data class StringValue(override val value: String) : LiteralValue<String>()
data class KClassValue(val className: ClassName, val arrayDimensionCount: Int) : KmAnnotationArgument()
data class EnumValue(val enumClassName: ClassName, val enumEntryName: String) : KmAnnotationArgument()
data class AnnotationValue(val annotation: KmAnnotation) : KmAnnotationArgument()
data class ArrayValue(val elements: List<KmAnnotationArgument>) : KmAnnotationArgument()
}
@@ -23,13 +23,14 @@ fun ProtoBuf.Annotation.readAnnotation(strings: NameResolver): KmAnnotation =
}.toMap()
)
fun ProtoBuf.Annotation.Argument.Value.readAnnotationArgument(strings: NameResolver): KmAnnotationArgument<*>? {
@OptIn(ExperimentalUnsignedTypes::class)
fun ProtoBuf.Annotation.Argument.Value.readAnnotationArgument(strings: NameResolver): KmAnnotationArgument? {
if (Flags.IS_UNSIGNED[flags]) {
return when (type) {
BYTE -> KmAnnotationArgument.UByteValue(intValue.toByte())
SHORT -> KmAnnotationArgument.UShortValue(intValue.toShort())
INT -> KmAnnotationArgument.UIntValue(intValue.toInt())
LONG -> KmAnnotationArgument.ULongValue(intValue)
BYTE -> KmAnnotationArgument.UByteValue(intValue.toByte().toUByte())
SHORT -> KmAnnotationArgument.UShortValue(intValue.toShort().toUShort())
INT -> KmAnnotationArgument.UIntValue(intValue.toInt().toUInt())
LONG -> KmAnnotationArgument.ULongValue(intValue.toULong())
else -> error("Cannot read value of unsigned type: $type")
}
}
@@ -24,7 +24,8 @@ fun KmAnnotation.writeAnnotation(strings: StringTable): ProtoBuf.Annotation.Buil
}
}
fun KmAnnotationArgument<*>.writeAnnotationArgument(strings: StringTable): ProtoBuf.Annotation.Argument.Value.Builder =
@OptIn(ExperimentalUnsignedTypes::class)
fun KmAnnotationArgument.writeAnnotationArgument(strings: StringTable): ProtoBuf.Annotation.Argument.Value.Builder =
ProtoBuf.Annotation.Argument.Value.newBuilder().apply {
when (this@writeAnnotationArgument) {
is KmAnnotationArgument.ByteValue -> {
@@ -76,7 +77,7 @@ fun KmAnnotationArgument<*>.writeAnnotationArgument(strings: StringTable): Proto
}
is KmAnnotationArgument.ULongValue -> {
this.type = ProtoBuf.Annotation.Argument.Value.Type.LONG
this.intValue = value
this.intValue = value.toLong()
this.flags = Flags.IS_UNSIGNED.toFlags(true)
}
is KmAnnotationArgument.StringValue -> {
@@ -95,11 +96,11 @@ fun KmAnnotationArgument<*>.writeAnnotationArgument(strings: StringTable): Proto
}
is KmAnnotationArgument.AnnotationValue -> {
this.type = ProtoBuf.Annotation.Argument.Value.Type.ANNOTATION
this.annotation = value.writeAnnotation(strings).build()
this.annotation = this@writeAnnotationArgument.annotation.writeAnnotation(strings).build()
}
is KmAnnotationArgument.ArrayValue -> {
this.type = ProtoBuf.Annotation.Argument.Value.Type.ARRAY
for (element in value) {
for (element in elements) {
this.addArrayElement(element.writeAnnotationArgument(strings))
}
}
@@ -446,7 +446,7 @@ private fun renderAnnotation(annotation: KmAnnotation): String =
}
@OptIn(ExperimentalUnsignedTypes::class)
private fun renderAnnotationArgument(arg: KmAnnotationArgument<*>): String =
private fun renderAnnotationArgument(arg: KmAnnotationArgument): String =
when (arg) {
is KmAnnotationArgument.ByteValue -> arg.value.toString() + ".toByte()"
is KmAnnotationArgument.CharValue -> "'${arg.value.toString().sanitize(quote = '\'')}'"
@@ -455,10 +455,10 @@ private fun renderAnnotationArgument(arg: KmAnnotationArgument<*>): String =
is KmAnnotationArgument.LongValue -> arg.value.toString() + "L"
is KmAnnotationArgument.FloatValue -> arg.value.toString() + "f"
is KmAnnotationArgument.DoubleValue -> arg.value.toString()
is KmAnnotationArgument.UByteValue -> arg.value.toUByte().toString() + ".toUByte()"
is KmAnnotationArgument.UShortValue -> arg.value.toUShort().toString() + ".toUShort()"
is KmAnnotationArgument.UIntValue -> arg.value.toUInt().toString() + "u"
is KmAnnotationArgument.ULongValue -> arg.value.toULong().toString() + "uL"
is KmAnnotationArgument.UByteValue -> arg.value.toString() + ".toUByte()"
is KmAnnotationArgument.UShortValue -> arg.value.toString() + ".toUShort()"
is KmAnnotationArgument.UIntValue -> arg.value.toString() + "u"
is KmAnnotationArgument.ULongValue -> arg.value.toString() + "uL"
is KmAnnotationArgument.BooleanValue -> arg.value.toString()
is KmAnnotationArgument.StringValue -> "\"${arg.value.sanitize(quote = '"')}\""
is KmAnnotationArgument.KClassValue -> buildString {
@@ -466,14 +466,14 @@ private fun renderAnnotationArgument(arg: KmAnnotationArgument<*>): String =
append(arg.className).append("::class")
repeat(arg.arrayDimensionCount) { append(">") }
}
is KmAnnotationArgument.EnumValue -> arg.value
is KmAnnotationArgument.AnnotationValue -> arg.value.let { annotation ->
is KmAnnotationArgument.EnumValue -> "${arg.enumClassName}.${arg.enumEntryName}"
is KmAnnotationArgument.AnnotationValue -> arg.annotation.let { annotation ->
val args = annotation.arguments.entries.joinToString { (name, argument) ->
"$name = ${renderAnnotationArgument(argument)}"
}
"${annotation.className}($args)"
}
is KmAnnotationArgument.ArrayValue -> arg.value.joinToString(prefix = "[", postfix = "]", transform = ::renderAnnotationArgument)
is KmAnnotationArgument.ArrayValue -> arg.elements.joinToString(prefix = "[", postfix = "]", transform = ::renderAnnotationArgument)
}
private fun String.sanitize(quote: Char): String =