From 70739df0adb7927d5f560d743a251c30876cb3ca Mon Sep 17 00:00:00 2001 From: Leonid Startsev Date: Mon, 3 Apr 2023 19:54:42 +0200 Subject: [PATCH] Improve toString() for KmAnnotationArgument --- .../jvm/api/kotlinx-metadata-jvm.api | 14 +-- .../test/AnnotationsStringFormTest.kt | 113 ++++++++++++++++++ .../kotlinx/metadata/test/JarContentTest.kt | 2 + .../metadata/test/MetadataSmokeTest.kt | 22 ++-- .../jvm/test/kotlinx/metadata/test/Utils.kt | 20 ++++ .../src/kotlinx/metadata/annotations.kt | 35 +++++- 6 files changed, 174 insertions(+), 32 deletions(-) create mode 100644 libraries/kotlinx-metadata/jvm/test/kotlinx/metadata/test/AnnotationsStringFormTest.kt create mode 100644 libraries/kotlinx-metadata/jvm/test/kotlinx/metadata/test/Utils.kt diff --git a/libraries/kotlinx-metadata/jvm/api/kotlinx-metadata-jvm.api b/libraries/kotlinx-metadata/jvm/api/kotlinx-metadata-jvm.api index 059e138aa64..5566f508861 100644 --- a/libraries/kotlinx-metadata/jvm/api/kotlinx-metadata-jvm.api +++ b/libraries/kotlinx-metadata/jvm/api/kotlinx-metadata-jvm.api @@ -177,7 +177,6 @@ public final class kotlinx/metadata/KmAnnotationArgument$BooleanValue : kotlinx/ public fun getValue ()Ljava/lang/Boolean; public synthetic fun getValue ()Ljava/lang/Object; public fun hashCode ()I - public fun toString ()Ljava/lang/String; } public final class kotlinx/metadata/KmAnnotationArgument$ByteValue : kotlinx/metadata/KmAnnotationArgument$LiteralValue { @@ -189,7 +188,6 @@ public final class kotlinx/metadata/KmAnnotationArgument$ByteValue : kotlinx/met public fun getValue ()Ljava/lang/Byte; public synthetic fun getValue ()Ljava/lang/Object; public fun hashCode ()I - public fun toString ()Ljava/lang/String; } public final class kotlinx/metadata/KmAnnotationArgument$CharValue : kotlinx/metadata/KmAnnotationArgument$LiteralValue { @@ -201,7 +199,6 @@ public final class kotlinx/metadata/KmAnnotationArgument$CharValue : kotlinx/met public fun getValue ()Ljava/lang/Character; public synthetic fun getValue ()Ljava/lang/Object; public fun hashCode ()I - public fun toString ()Ljava/lang/String; } public final class kotlinx/metadata/KmAnnotationArgument$DoubleValue : kotlinx/metadata/KmAnnotationArgument$LiteralValue { @@ -213,7 +210,6 @@ public final class kotlinx/metadata/KmAnnotationArgument$DoubleValue : kotlinx/m public fun getValue ()Ljava/lang/Double; public synthetic fun getValue ()Ljava/lang/Object; public fun hashCode ()I - public fun toString ()Ljava/lang/String; } public final class kotlinx/metadata/KmAnnotationArgument$EnumValue : kotlinx/metadata/KmAnnotationArgument { @@ -238,7 +234,6 @@ public final class kotlinx/metadata/KmAnnotationArgument$FloatValue : kotlinx/me public fun getValue ()Ljava/lang/Float; public synthetic fun getValue ()Ljava/lang/Object; public fun hashCode ()I - public fun toString ()Ljava/lang/String; } public final class kotlinx/metadata/KmAnnotationArgument$IntValue : kotlinx/metadata/KmAnnotationArgument$LiteralValue { @@ -250,7 +245,6 @@ public final class kotlinx/metadata/KmAnnotationArgument$IntValue : kotlinx/meta public fun getValue ()Ljava/lang/Integer; public synthetic fun getValue ()Ljava/lang/Object; public fun hashCode ()I - public fun toString ()Ljava/lang/String; } public final class kotlinx/metadata/KmAnnotationArgument$KClassValue : kotlinx/metadata/KmAnnotationArgument { @@ -269,6 +263,7 @@ public final class kotlinx/metadata/KmAnnotationArgument$KClassValue : kotlinx/m public abstract class kotlinx/metadata/KmAnnotationArgument$LiteralValue : kotlinx/metadata/KmAnnotationArgument { public abstract fun getValue ()Ljava/lang/Object; + public final fun toString ()Ljava/lang/String; } public final class kotlinx/metadata/KmAnnotationArgument$LongValue : kotlinx/metadata/KmAnnotationArgument$LiteralValue { @@ -280,7 +275,6 @@ public final class kotlinx/metadata/KmAnnotationArgument$LongValue : kotlinx/met public fun getValue ()Ljava/lang/Long; public synthetic fun getValue ()Ljava/lang/Object; public fun hashCode ()I - public fun toString ()Ljava/lang/String; } public final class kotlinx/metadata/KmAnnotationArgument$ShortValue : kotlinx/metadata/KmAnnotationArgument$LiteralValue { @@ -292,7 +286,6 @@ public final class kotlinx/metadata/KmAnnotationArgument$ShortValue : kotlinx/me public synthetic fun getValue ()Ljava/lang/Object; public fun getValue ()Ljava/lang/Short; public fun hashCode ()I - public fun toString ()Ljava/lang/String; } public final class kotlinx/metadata/KmAnnotationArgument$StringValue : kotlinx/metadata/KmAnnotationArgument$LiteralValue { @@ -304,7 +297,6 @@ public final class kotlinx/metadata/KmAnnotationArgument$StringValue : kotlinx/m public synthetic fun getValue ()Ljava/lang/Object; public fun getValue ()Ljava/lang/String; public fun hashCode ()I - public fun toString ()Ljava/lang/String; } public final class kotlinx/metadata/KmAnnotationArgument$UByteValue : kotlinx/metadata/KmAnnotationArgument$LiteralValue { @@ -316,7 +308,6 @@ public final class kotlinx/metadata/KmAnnotationArgument$UByteValue : kotlinx/me public synthetic fun getValue ()Ljava/lang/Object; public fun getValue-w2LRezQ ()B public fun hashCode ()I - public fun toString ()Ljava/lang/String; } public final class kotlinx/metadata/KmAnnotationArgument$UIntValue : kotlinx/metadata/KmAnnotationArgument$LiteralValue { @@ -328,7 +319,6 @@ public final class kotlinx/metadata/KmAnnotationArgument$UIntValue : kotlinx/met public synthetic fun getValue ()Ljava/lang/Object; public fun getValue-pVg5ArA ()I public fun hashCode ()I - public fun toString ()Ljava/lang/String; } public final class kotlinx/metadata/KmAnnotationArgument$ULongValue : kotlinx/metadata/KmAnnotationArgument$LiteralValue { @@ -340,7 +330,6 @@ public final class kotlinx/metadata/KmAnnotationArgument$ULongValue : kotlinx/me public synthetic fun getValue ()Ljava/lang/Object; public fun getValue-s-VKNKU ()J public fun hashCode ()I - public fun toString ()Ljava/lang/String; } public final class kotlinx/metadata/KmAnnotationArgument$UShortValue : kotlinx/metadata/KmAnnotationArgument$LiteralValue { @@ -352,7 +341,6 @@ public final class kotlinx/metadata/KmAnnotationArgument$UShortValue : kotlinx/m public synthetic fun getValue ()Ljava/lang/Object; public fun getValue-Mh2AYeg ()S public fun hashCode ()I - public fun toString ()Ljava/lang/String; } public final class kotlinx/metadata/KmClass : kotlinx/metadata/KmClassVisitor, kotlinx/metadata/KmDeclarationContainer { diff --git a/libraries/kotlinx-metadata/jvm/test/kotlinx/metadata/test/AnnotationsStringFormTest.kt b/libraries/kotlinx-metadata/jvm/test/kotlinx/metadata/test/AnnotationsStringFormTest.kt new file mode 100644 index 00000000000..a738439130c --- /dev/null +++ b/libraries/kotlinx-metadata/jvm/test/kotlinx/metadata/test/AnnotationsStringFormTest.kt @@ -0,0 +1,113 @@ +/* + * 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. + */ + +package kotlinx.metadata.test + +import kotlinx.metadata.KmAnnotation +import kotlinx.metadata.KmAnnotationArgument +import kotlinx.metadata.jvm.annotations +import org.junit.Test +import kotlin.reflect.KClass +import kotlin.test.assertEquals + +@Target(AnnotationTarget.TYPE) +internal annotation class MyAnn(val s: String, val nested: MyAnnNested, val kClass: WithKClass) + +@Target(AnnotationTarget.TYPE) +internal annotation class WithKClass(val kClass: KClass<*>) + +internal annotation class MyAnnNested(val e: E, val a: Array) + +internal enum class E { A, B } +class AnnotationsStringFormTest { + + private fun doTest(argumentToExpected: Map) { + val valuesArgMap = argumentToExpected.keys.mapIndexed { i, km -> + "value$i" to km + }.toMap() + val valuesExpected = argumentToExpected.values.withIndex().joinToString { (i, s) -> "value$i = $s" } + val anno = KmAnnotation("com/my/Foo", valuesArgMap) + assertEquals("@com/my/Foo($valuesExpected)", anno.toString()) + } + + @Test + fun testSignedLiteralValues() { + val values: Map = mapOf( + KmAnnotationArgument.ByteValue(1) to "ByteValue(1)", + KmAnnotationArgument.ShortValue(2) to "ShortValue(2)", + KmAnnotationArgument.IntValue(3) to "IntValue(3)", + KmAnnotationArgument.LongValue(4) to "LongValue(4)", + ) + doTest(values) + } + + @Test + fun testUnsignedLiteralValues() { + val values: Map = mapOf( + KmAnnotationArgument.UByteValue(1u) to "UByteValue(1)", + KmAnnotationArgument.UShortValue(2u) to "UShortValue(2)", + KmAnnotationArgument.UIntValue(3u) to "UIntValue(3)", + KmAnnotationArgument.ULongValue(4u) to "ULongValue(4)", + ) + doTest(values) + } + + @Test + fun testOtherLiteralValues() { + val values: Map = mapOf( + KmAnnotationArgument.StringValue("foo") to "StringValue(\"foo\")", + KmAnnotationArgument.CharValue('a') to "CharValue(a)", + KmAnnotationArgument.FloatValue(2.1f) to "FloatValue(2.1)", + KmAnnotationArgument.DoubleValue(3.3) to "DoubleValue(3.3)", + KmAnnotationArgument.BooleanValue(true) to "BooleanValue(true)", + ) + doTest(values) + } + + @Test + fun testNonLiteralValues() { + val values = mapOf( + KmAnnotationArgument.EnumValue("foo/bar", "BAZ") to "EnumValue(foo/bar.BAZ)", + KmAnnotationArgument.AnnotationValue(KmAnnotation("com/my/Bar", mapOf())) to "AnnotationValue(@com/my/Bar())", + KmAnnotationArgument.ArrayValue( + listOf( + KmAnnotationArgument.IntValue(1), + KmAnnotationArgument.IntValue(2), + KmAnnotationArgument.IntValue(3) + ) + ) to "ArrayValue([IntValue(1), IntValue(2), IntValue(3)])", + KmAnnotationArgument.KClassValue("com/my/Bar") to "KClassValue(com/my/Bar)", + KmAnnotationArgument.ArrayKClassValue("com/my/Bar", 1) to "ArrayKClassValue(kotlin/Array)" + ) + doTest(values) + } + + @Test + fun testIrlValues() { + // Annotations are stored directly in metadata only for types and type aliases + class Holder(val e: @MyAnn("foo", MyAnnNested(E.B, arrayOf("a", "b", "c")), WithKClass(E::class)) String) + + val md = Holder::class.java.readMetadataAsKmClass() + val annotation = md.properties.first().returnType.annotations.single() + assertEquals( + "@kotlinx/metadata/test/MyAnn(" + + "s = StringValue(\"foo\"), " + + "nested = AnnotationValue(@kotlinx/metadata/test/MyAnnNested(e = EnumValue(kotlinx/metadata/test/E.B), " + + "a = ArrayValue([StringValue(\"a\"), StringValue(\"b\"), StringValue(\"c\")]))), " + + "kClass = AnnotationValue(@kotlinx/metadata/test/WithKClass(kClass = KClassValue(kotlinx/metadata/test/E)))" + + ")", + annotation.toString() + ) + } + + @Test + fun testArrayKClassValue() { + class Holder(val e: @WithKClass(Array::class) String) + + val md = Holder::class.java.readMetadataAsKmClass() + val annotation = md.properties.first().returnType.annotations.single() + assertEquals("@kotlinx/metadata/test/WithKClass(kClass = ArrayKClassValue(kotlin/Array))", annotation.toString()) + } +} diff --git a/libraries/kotlinx-metadata/jvm/test/kotlinx/metadata/test/JarContentTest.kt b/libraries/kotlinx-metadata/jvm/test/kotlinx/metadata/test/JarContentTest.kt index fc88f931198..bb8649d8470 100644 --- a/libraries/kotlinx-metadata/jvm/test/kotlinx/metadata/test/JarContentTest.kt +++ b/libraries/kotlinx-metadata/jvm/test/kotlinx/metadata/test/JarContentTest.kt @@ -44,6 +44,8 @@ class JarContentTest { } for (constant in loadedConstants) { + // kotlin/Array appears as constant because it is used in ArrayKClassValue.toString() + if (constant == "kotlin/Array<") continue // Explicitly checking types that are programmatically built don't appear as string constants. assertNull("$constant found at ${entry.name}", PREDEFINED_STRINGS.find { it in constant }) 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 e04d1f91c58..d404c0a7afa 100644 --- a/libraries/kotlinx-metadata/jvm/test/kotlinx/metadata/test/MetadataSmokeTest.kt +++ b/libraries/kotlinx-metadata/jvm/test/kotlinx/metadata/test/MetadataSmokeTest.kt @@ -17,9 +17,6 @@ import kotlin.test.* @Suppress("DEPRECATION") class MetadataSmokeTest { - private fun Class<*>.readMetadata(): Metadata { - return getAnnotation(Metadata::class.java) - } @Test fun listInlineFunctions() { @@ -30,9 +27,9 @@ class MetadataSmokeTest { fun bar() {} } - val classMetadata = KotlinClassMetadata.read(L::class.java.readMetadata()) as KotlinClassMetadata.Class + val classMetadata = L::class.java.readMetadataAsKmClass() - val inlineFunctions = classMetadata.toKmClass().functions + val inlineFunctions = classMetadata.functions .filter { Flag.Function.IS_INLINE(it.flags) } .mapNotNull { it.signature?.toString() } @@ -135,14 +132,14 @@ class MetadataSmokeTest { class L val l = ClassNameReader().run { - (KotlinClassMetadata.read(L::class.java.readMetadata()) as KotlinClassMetadata.Class).accept(this) + (KotlinClassMetadata.read(L::class.java.getMetadata()) as KotlinClassMetadata.Class).accept(this) className } assertEquals(".kotlinx/metadata/test/MetadataSmokeTest\$jvmInternalName\$L", l) assertEquals("kotlinx/metadata/test/MetadataSmokeTest\$jvmInternalName\$L", l.toJvmInternalName()) val coroutineContextKey = ClassNameReader().run { - (KotlinClassMetadata.read(CoroutineContext.Key::class.java.readMetadata()) as KotlinClassMetadata.Class).accept(this) + (KotlinClassMetadata.read(CoroutineContext.Key::class.java.getMetadata()) as KotlinClassMetadata.Class).accept(this) className } assertEquals("kotlin/coroutines/CoroutineContext.Key", coroutineContextKey) @@ -152,7 +149,7 @@ class MetadataSmokeTest { @Test fun lambdaVersionRequirement() { val x: suspend Int.(String, String) -> Unit = { _, _ -> } - val annotation = x::class.java.getAnnotation(Metadata::class.java)!! + val annotation = x::class.java.getMetadata() val metadata = KotlinClassMetadata.read(annotation) as KotlinClassMetadata.SyntheticClass metadata.accept(KmLambda()) } @@ -164,8 +161,7 @@ class MetadataSmokeTest { fun foo(a: String, b: Int, c: Boolean) = Unit } - val classWithStableParameterNames = - (KotlinClassMetadata.read(Test::class.java.readMetadata()) as KotlinClassMetadata.Class).toKmClass() + val classWithStableParameterNames = Test::class.java.readMetadataAsKmClass() classWithStableParameterNames.constructors.forEach { assertFalse(Flag.Constructor.HAS_NON_STABLE_PARAMETER_NAMES(it.flags)) } classWithStableParameterNames.functions.forEach { assertFalse(Flag.Function.HAS_NON_STABLE_PARAMETER_NAMES(it.flags)) } @@ -193,7 +189,7 @@ class MetadataSmokeTest { @Test @OptIn(UnstableMetadataApi::class) fun metadataVersionEarlierThan1_4() { - val dummy = (KotlinClassMetadata.read(MetadataSmokeTest::class.java.readMetadata()) as KotlinClassMetadata.Class).toKmClass() + val dummy = MetadataSmokeTest::class.java.readMetadataAsKmClass() val mv = intArrayOf(1, 3) assertFailsWith { KotlinClassMetadata.writeClass(dummy, mv) } // We can't write empty KmClass() assertFailsWith { KotlinClassMetadata.writeFileFacade(KmPackage(), mv) } @@ -215,8 +211,8 @@ class MetadataSmokeTest { JvmFlag.Class.HAS_METHOD_BODIES_IN_INTERFACE ) - val metadata = CoroutineContext::class.java.readMetadata() - val kmClass = (KotlinClassMetadata.read(metadata) as KotlinClassMetadata.Class).toKmClass() + val metadata = CoroutineContext::class.java.getMetadata() + val kmClass = metadata.readAsKmClass() kmClass.jvmFlags = jvmClassFlags val kmClassCopy = KotlinClassMetadata diff --git a/libraries/kotlinx-metadata/jvm/test/kotlinx/metadata/test/Utils.kt b/libraries/kotlinx-metadata/jvm/test/kotlinx/metadata/test/Utils.kt new file mode 100644 index 00000000000..df5a6bf6515 --- /dev/null +++ b/libraries/kotlinx-metadata/jvm/test/kotlinx/metadata/test/Utils.kt @@ -0,0 +1,20 @@ +/* + * 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. + */ + +package kotlinx.metadata.test + +import kotlinx.metadata.KmClass +import kotlinx.metadata.jvm.KotlinClassMetadata + +internal fun Class<*>.getMetadata(): Metadata { + return getAnnotation(Metadata::class.java) +} + +internal fun Metadata.readAsKmClass(): KmClass { + val clazz = KotlinClassMetadata.read(this) as? KotlinClassMetadata.Class + return clazz?.toKmClass() ?: error("Not a KotlinClassMetadata.Class: $clazz") +} + +internal fun Class<*>.readMetadataAsKmClass(): KmClass = getMetadata().readAsKmClass() diff --git a/libraries/kotlinx-metadata/src/kotlinx/metadata/annotations.kt b/libraries/kotlinx-metadata/src/kotlinx/metadata/annotations.kt index f0aa311d15e..2ca3c1d1cf5 100644 --- a/libraries/kotlinx-metadata/src/kotlinx/metadata/annotations.kt +++ b/libraries/kotlinx-metadata/src/kotlinx/metadata/annotations.kt @@ -30,13 +30,13 @@ class KmAnnotation(val className: ClassName, val arguments: Map) : KmAnnotationArgument() + data class ArrayValue(val elements: List) : KmAnnotationArgument() { + override fun toString(): String = "ArrayValue($elements)" + } /** * An annotation argument of KClass type. @@ -147,6 +158,8 @@ sealed class KmAnnotationArgument { init { require(arrayDimensionCount == 0) { "KClassValue must not have array dimensions. For Array::class, use ArrayKClassValue." } } + + override fun toString(): String = "KClassValue($className)" } /** @@ -154,7 +167,7 @@ sealed class KmAnnotationArgument { * * 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)`. + * As a result, Kotlin compiler allows using 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`. @@ -173,5 +186,15 @@ sealed class KmAnnotationArgument { init { require(arrayDimensionCount > 0) { "ArrayKClassValue must have at least one dimension. For regular X::class argument, use KClassValue." } } + + private val stringRepresentation = buildString { + append("ArrayKClassValue(") + repeat(arrayDimensionCount) { append("kotlin/Array<") } + append(className) + repeat(arrayDimensionCount) { append(">") } + append(")") + } + + override fun toString(): String = stringRepresentation } }