Fix deserialization of enum annotation arguments at runtime
This commit is contained in:
+28
@@ -0,0 +1,28 @@
|
||||
//ALLOW_AST_ACCESS
|
||||
package test;
|
||||
|
||||
// This test checks that we don't accidentally call toString() on an enum value
|
||||
// to determine which enum entry appears in the annotation, and call name() instead
|
||||
|
||||
public class EnumArgumentWithCustomToString {
|
||||
public enum E {
|
||||
CAKE {
|
||||
@Override
|
||||
public String toString() {
|
||||
return "LIE";
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public @interface EnumAnno {
|
||||
E value();
|
||||
}
|
||||
|
||||
public @interface EnumArrayAnno {
|
||||
E[] value();
|
||||
}
|
||||
|
||||
@EnumAnno(E.CAKE)
|
||||
@EnumArrayAnno({E.CAKE, E.CAKE})
|
||||
void annotated() {}
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
package test
|
||||
|
||||
public open class EnumArgumentWithCustomToString {
|
||||
public constructor EnumArgumentWithCustomToString()
|
||||
test.EnumArgumentWithCustomToString.EnumAnno(value = E.CAKE: test.EnumArgumentWithCustomToString.E) test.EnumArgumentWithCustomToString.EnumArrayAnno(value = {E.CAKE, E.CAKE}: kotlin.Array<out test.EnumArgumentWithCustomToString.E>) public/*package*/ open fun annotated(): kotlin.Unit
|
||||
|
||||
public open enum class E : kotlin.Enum<test.EnumArgumentWithCustomToString.E!> {
|
||||
public enum entry CAKE : test.EnumArgumentWithCustomToString.E {
|
||||
private constructor CAKE()
|
||||
public final override /*1*/ /*fake_override*/ fun compareTo(/*0*/ other: test.EnumArgumentWithCustomToString.E!): kotlin.Int
|
||||
public final override /*1*/ /*fake_override*/ fun name(): kotlin.String
|
||||
public final override /*1*/ /*fake_override*/ fun ordinal(): kotlin.Int
|
||||
}
|
||||
|
||||
private constructor E()
|
||||
public final override /*1*/ /*fake_override*/ fun compareTo(/*0*/ other: test.EnumArgumentWithCustomToString.E!): kotlin.Int
|
||||
public final override /*1*/ /*fake_override*/ fun name(): kotlin.String
|
||||
public final override /*1*/ /*fake_override*/ fun ordinal(): kotlin.Int
|
||||
|
||||
// Static members
|
||||
public final /*synthesized*/ fun valueOf(/*0*/ value: kotlin.String): test.EnumArgumentWithCustomToString.E
|
||||
public final /*synthesized*/ fun values(): kotlin.Array<test.EnumArgumentWithCustomToString.E>
|
||||
}
|
||||
|
||||
public final annotation class EnumAnno : kotlin.Annotation {
|
||||
public constructor EnumAnno(/*0*/ value: test.EnumArgumentWithCustomToString.E)
|
||||
public abstract fun value(): test.EnumArgumentWithCustomToString.E
|
||||
}
|
||||
|
||||
public final annotation class EnumArrayAnno : kotlin.Annotation {
|
||||
public constructor EnumArrayAnno(/*0*/ vararg value: test.EnumArgumentWithCustomToString.E /*kotlin.Array<out test.EnumArgumentWithCustomToString.E>*/)
|
||||
public abstract fun value(): kotlin.Array<test.EnumArgumentWithCustomToString.E>
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
//ALLOW_AST_ACCESS
|
||||
package test
|
||||
|
||||
// This test checks that we don't accidentally call toString() on an enum value
|
||||
// to determine which enum entry appears in the annotation, and call name() instead
|
||||
|
||||
enum class E {
|
||||
CAKE {
|
||||
override fun toString() = "LIE"
|
||||
}
|
||||
}
|
||||
|
||||
annotation class EnumAnno(val value: E)
|
||||
annotation class EnumArrayAnno(vararg val value: E)
|
||||
|
||||
public class EnumArgumentWithCustomToString {
|
||||
EnumAnno(E.CAKE)
|
||||
EnumArrayAnno(E.CAKE, E.CAKE)
|
||||
fun annotated() {}
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
package test
|
||||
|
||||
internal final enum class E : kotlin.Enum<test.E> {
|
||||
public enum entry CAKE : test.E {
|
||||
/*primary*/ private constructor CAKE()
|
||||
public final override /*1*/ /*fake_override*/ fun compareTo(/*0*/ other: test.E): kotlin.Int
|
||||
public final override /*1*/ /*fake_override*/ fun name(): kotlin.String
|
||||
public final override /*1*/ /*fake_override*/ fun ordinal(): kotlin.Int
|
||||
}
|
||||
|
||||
/*primary*/ private constructor E()
|
||||
public final override /*1*/ /*fake_override*/ fun compareTo(/*0*/ other: test.E): kotlin.Int
|
||||
public final override /*1*/ /*fake_override*/ fun name(): kotlin.String
|
||||
public final override /*1*/ /*fake_override*/ fun ordinal(): kotlin.Int
|
||||
|
||||
// Static members
|
||||
public final /*synthesized*/ fun valueOf(/*0*/ value: kotlin.String): test.E
|
||||
public final /*synthesized*/ fun values(): kotlin.Array<test.E>
|
||||
}
|
||||
|
||||
internal final annotation class EnumAnno : kotlin.Annotation {
|
||||
/*primary*/ public constructor EnumAnno(/*0*/ value: test.E)
|
||||
internal final val value: test.E
|
||||
internal final fun <get-value>(): test.E
|
||||
}
|
||||
|
||||
public final class EnumArgumentWithCustomToString {
|
||||
/*primary*/ public constructor EnumArgumentWithCustomToString()
|
||||
test.EnumAnno(value = E.CAKE: test.E) test.EnumArrayAnno(value = {E.CAKE, E.CAKE}: kotlin.Array<out test.E>) internal final fun annotated(): kotlin.Unit
|
||||
}
|
||||
|
||||
internal final annotation class EnumArrayAnno : kotlin.Annotation {
|
||||
/*primary*/ public constructor EnumArrayAnno(/*0*/ vararg value: test.E /*kotlin.Array<out test.E>*/)
|
||||
internal final val value: kotlin.Array<out test.E>
|
||||
internal final fun <get-value>(): kotlin.Array<out test.E>
|
||||
}
|
||||
@@ -409,6 +409,12 @@ public class LoadJavaTestGenerated extends AbstractLoadJavaTest {
|
||||
doTestCompiledJava(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("EnumArgumentWithCustomToString.java")
|
||||
public void testEnumArgumentWithCustomToString() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("compiler/testData/loadJava/compiledJava/annotations/EnumArgumentWithCustomToString.java");
|
||||
doTestCompiledJava(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("EnumInParam.java")
|
||||
public void testEnumInParam() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("compiler/testData/loadJava/compiledJava/annotations/EnumInParam.java");
|
||||
@@ -1907,6 +1913,12 @@ public class LoadJavaTestGenerated extends AbstractLoadJavaTest {
|
||||
doTestCompiledKotlin(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("EnumArgumentWithCustomToString.kt")
|
||||
public void testEnumArgumentWithCustomToString() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("compiler/testData/loadJava/compiledKotlin/annotations/EnumArgumentWithCustomToString.kt");
|
||||
doTestCompiledKotlin(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("SimpleAnnotation.kt")
|
||||
public void testSimpleAnnotation() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("compiler/testData/loadJava/compiledKotlin/annotations/SimpleAnnotation.kt");
|
||||
|
||||
+6
-3
@@ -149,9 +149,12 @@ public abstract class AbstractJvmRuntimeDescriptorLoaderTest : TestCaseWithTmpdi
|
||||
else if (header == null ||
|
||||
(header.kind == KotlinClassHeader.Kind.CLASS && header.classKind == JvmAnnotationNames.KotlinClass.Kind.CLASS)) {
|
||||
// Either a normal Kotlin class or a Java class
|
||||
val classDescriptor = module.findClassAcrossModuleDependencies(klass.classId).sure("Couldn't resolve class $className")
|
||||
if (DescriptorUtils.isTopLevelDeclaration(classDescriptor)) {
|
||||
scope.addClassifierDescriptor(classDescriptor)
|
||||
val classId = klass.classId
|
||||
if (!classId.isLocal()) {
|
||||
val classDescriptor = module.findClassAcrossModuleDependencies(classId).sure("Couldn't resolve class $className")
|
||||
if (DescriptorUtils.isTopLevelDeclaration(classDescriptor)) {
|
||||
scope.addClassifierDescriptor(classDescriptor)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+12
@@ -87,6 +87,12 @@ public class JvmRuntimeDescriptorLoaderTestGenerated extends AbstractJvmRuntimeD
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("EnumArgumentWithCustomToString.kt")
|
||||
public void testEnumArgumentWithCustomToString() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("compiler/testData/loadJava/compiledKotlin/annotations/EnumArgumentWithCustomToString.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("SimpleAnnotation.kt")
|
||||
public void testSimpleAnnotation() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("compiler/testData/loadJava/compiledKotlin/annotations/SimpleAnnotation.kt");
|
||||
@@ -3433,6 +3439,12 @@ public class JvmRuntimeDescriptorLoaderTestGenerated extends AbstractJvmRuntimeD
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("EnumArgumentWithCustomToString.java")
|
||||
public void testEnumArgumentWithCustomToString() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("compiler/testData/loadJava/compiledJava/annotations/EnumArgumentWithCustomToString.java");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("EnumInParam.java")
|
||||
public void testEnumInParam() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("compiler/testData/loadJava/compiledJava/annotations/EnumInParam.java");
|
||||
|
||||
+8
-4
@@ -22,10 +22,10 @@ import org.jetbrains.kotlin.name.Name
|
||||
abstract class ReflectJavaAnnotationArgument(
|
||||
override val name: Name?
|
||||
) : JavaAnnotationArgument {
|
||||
class object {
|
||||
default object Factory {
|
||||
fun create(value: Any, name: Name?): ReflectJavaAnnotationArgument {
|
||||
return when {
|
||||
value.javaClass.isEnum() -> ReflectJavaEnumValueAnnotationArgument(name, value)
|
||||
value.javaClass.isEnumClassOrSpecializedEnumEntryClass() -> ReflectJavaEnumValueAnnotationArgument(name, value as Enum<*>)
|
||||
value is Annotation -> ReflectJavaAnnotationAsAnnotationArgument(name, value)
|
||||
value is Array<Any> -> ReflectJavaArrayAnnotationArgument(name, value)
|
||||
value is Class<*> -> ReflectJavaClassObjectAnnotationArgument(name, value)
|
||||
@@ -49,9 +49,13 @@ class ReflectJavaArrayAnnotationArgument(
|
||||
|
||||
class ReflectJavaEnumValueAnnotationArgument(
|
||||
name: Name?,
|
||||
private val value: Any
|
||||
private val value: Enum<*>
|
||||
) : ReflectJavaAnnotationArgument(name), JavaEnumValueAnnotationArgument {
|
||||
override fun resolve() = ReflectJavaField(value.javaClass.getDeclaredField(value.toString()))
|
||||
override fun resolve(): ReflectJavaField {
|
||||
val clazz = value.javaClass
|
||||
val enumClass = if (clazz.isEnum()) clazz else clazz.getEnclosingClass()
|
||||
return ReflectJavaField(enumClass.getDeclaredField(value.name()))
|
||||
}
|
||||
}
|
||||
|
||||
class ReflectJavaClassObjectAnnotationArgument(
|
||||
|
||||
+5
-1
@@ -80,7 +80,11 @@ public class ReflectJavaClass(private val klass: Class<*>) : ReflectJavaElement(
|
||||
.map(::ReflectJavaField)
|
||||
.toList()
|
||||
|
||||
override fun getConstructors() = klass.getDeclaredConstructors().map(::ReflectJavaConstructor)
|
||||
override fun getConstructors() = klass.getDeclaredConstructors()
|
||||
.stream()
|
||||
.filter { constructor -> !constructor.isSynthetic() }
|
||||
.map(::ReflectJavaConstructor)
|
||||
.toList()
|
||||
|
||||
override fun getDefaultType(): ReflectJavaClassifierType = throw UnsupportedOperationException()
|
||||
|
||||
|
||||
+3
@@ -34,6 +34,9 @@ private fun calculateVisibility(modifiers: Int): Visibility {
|
||||
}
|
||||
}
|
||||
|
||||
public fun Class<*>.isEnumClassOrSpecializedEnumEntryClass(): Boolean =
|
||||
javaClass<Enum<*>>().isAssignableFrom(this)
|
||||
|
||||
public val Class<*>.fqName: FqName
|
||||
get() = classId.asSingleFqName().toSafe()
|
||||
|
||||
|
||||
+7
-5
@@ -159,19 +159,21 @@ private object ReflectClassStructure {
|
||||
visitor.visit(name, value)
|
||||
}
|
||||
javaClass<Enum<*>>().isAssignableFrom(clazz) -> {
|
||||
visitor.visitEnum(name, clazz.classId, Name.identifier(value.toString()))
|
||||
// isEnum returns false for specialized enum constants (enum entries which are subclasses)
|
||||
val classId = (if (clazz.isEnum()) clazz else clazz.getEnclosingClass()).classId
|
||||
visitor.visitEnum(name, classId, Name.identifier((value as Enum<*>).name()))
|
||||
}
|
||||
javaClass<Annotation>().isAssignableFrom(clazz) -> {
|
||||
clazz.isAnnotation() -> {
|
||||
// TODO: support values of annotation types
|
||||
throw UnsupportedOperationException("Values of annotation types are not yet supported in Kotlin reflection: $value")
|
||||
}
|
||||
clazz.isArray() -> {
|
||||
val elementVisitor = visitor.visitArray(name) ?: return
|
||||
val componentType = clazz.getComponentType()
|
||||
if (javaClass<Enum<*>>().isAssignableFrom(componentType)) {
|
||||
val componentClassName = componentType.classId
|
||||
if (componentType.isEnum()) {
|
||||
val enumClassId = componentType.classId
|
||||
for (element in value as Array<*>) {
|
||||
elementVisitor.visitEnum(componentClassName, Name.identifier(element.toString()))
|
||||
elementVisitor.visitEnum(enumClassId, Name.identifier((element as Enum<*>).name()))
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
||||
@@ -81,6 +81,12 @@ public class ResolveByStubTestGenerated extends AbstractResolveByStubTest {
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("EnumArgumentWithCustomToString.kt")
|
||||
public void testEnumArgumentWithCustomToString() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("compiler/testData/loadJava/compiledKotlin/annotations/EnumArgumentWithCustomToString.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("SimpleAnnotation.kt")
|
||||
public void testSimpleAnnotation() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("compiler/testData/loadJava/compiledKotlin/annotations/SimpleAnnotation.kt");
|
||||
|
||||
Reference in New Issue
Block a user