diff --git a/compiler/testData/loadJava/compiledJava/annotations/EnumArgumentWithCustomToString.java b/compiler/testData/loadJava/compiledJava/annotations/EnumArgumentWithCustomToString.java new file mode 100644 index 00000000000..640fa197c44 --- /dev/null +++ b/compiler/testData/loadJava/compiledJava/annotations/EnumArgumentWithCustomToString.java @@ -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() {} +} diff --git a/compiler/testData/loadJava/compiledJava/annotations/EnumArgumentWithCustomToString.txt b/compiler/testData/loadJava/compiledJava/annotations/EnumArgumentWithCustomToString.txt new file mode 100644 index 00000000000..aae9e4b0bbe --- /dev/null +++ b/compiler/testData/loadJava/compiledJava/annotations/EnumArgumentWithCustomToString.txt @@ -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) public/*package*/ open fun annotated(): kotlin.Unit + + public open enum class E : kotlin.Enum { + 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 + } + + 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*/) + public abstract fun value(): kotlin.Array + } +} diff --git a/compiler/testData/loadJava/compiledKotlin/annotations/EnumArgumentWithCustomToString.kt b/compiler/testData/loadJava/compiledKotlin/annotations/EnumArgumentWithCustomToString.kt new file mode 100644 index 00000000000..0f086e488ed --- /dev/null +++ b/compiler/testData/loadJava/compiledKotlin/annotations/EnumArgumentWithCustomToString.kt @@ -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() {} +} diff --git a/compiler/testData/loadJava/compiledKotlin/annotations/EnumArgumentWithCustomToString.txt b/compiler/testData/loadJava/compiledKotlin/annotations/EnumArgumentWithCustomToString.txt new file mode 100644 index 00000000000..844e3493b8c --- /dev/null +++ b/compiler/testData/loadJava/compiledKotlin/annotations/EnumArgumentWithCustomToString.txt @@ -0,0 +1,36 @@ +package test + +internal final enum class E : kotlin.Enum { + 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 +} + +internal final annotation class EnumAnno : kotlin.Annotation { + /*primary*/ public constructor EnumAnno(/*0*/ value: test.E) + internal final val value: test.E + internal final fun (): 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) internal final fun annotated(): kotlin.Unit +} + +internal final annotation class EnumArrayAnno : kotlin.Annotation { + /*primary*/ public constructor EnumArrayAnno(/*0*/ vararg value: test.E /*kotlin.Array*/) + internal final val value: kotlin.Array + internal final fun (): kotlin.Array +} diff --git a/compiler/tests/org/jetbrains/kotlin/jvm/compiler/LoadJavaTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/jvm/compiler/LoadJavaTestGenerated.java index 5afc73e5202..cc37c22efce 100644 --- a/compiler/tests/org/jetbrains/kotlin/jvm/compiler/LoadJavaTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/jvm/compiler/LoadJavaTestGenerated.java @@ -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"); diff --git a/compiler/tests/org/jetbrains/kotlin/jvm/runtime/AbstractJvmRuntimeDescriptorLoaderTest.kt b/compiler/tests/org/jetbrains/kotlin/jvm/runtime/AbstractJvmRuntimeDescriptorLoaderTest.kt index f7bf3490066..4751dff752b 100644 --- a/compiler/tests/org/jetbrains/kotlin/jvm/runtime/AbstractJvmRuntimeDescriptorLoaderTest.kt +++ b/compiler/tests/org/jetbrains/kotlin/jvm/runtime/AbstractJvmRuntimeDescriptorLoaderTest.kt @@ -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) + } } } } diff --git a/compiler/tests/org/jetbrains/kotlin/jvm/runtime/JvmRuntimeDescriptorLoaderTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/jvm/runtime/JvmRuntimeDescriptorLoaderTestGenerated.java index fc4071181ad..a4985c8947d 100644 --- a/compiler/tests/org/jetbrains/kotlin/jvm/runtime/JvmRuntimeDescriptorLoaderTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/jvm/runtime/JvmRuntimeDescriptorLoaderTestGenerated.java @@ -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"); diff --git a/core/descriptors.runtime/src/org/jetbrains/kotlin/load/java/structure/reflect/ReflectJavaAnnotationArguments.kt b/core/descriptors.runtime/src/org/jetbrains/kotlin/load/java/structure/reflect/ReflectJavaAnnotationArguments.kt index 3600ecde408..313843bf22e 100644 --- a/core/descriptors.runtime/src/org/jetbrains/kotlin/load/java/structure/reflect/ReflectJavaAnnotationArguments.kt +++ b/core/descriptors.runtime/src/org/jetbrains/kotlin/load/java/structure/reflect/ReflectJavaAnnotationArguments.kt @@ -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 -> 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( diff --git a/core/descriptors.runtime/src/org/jetbrains/kotlin/load/java/structure/reflect/ReflectJavaClass.kt b/core/descriptors.runtime/src/org/jetbrains/kotlin/load/java/structure/reflect/ReflectJavaClass.kt index f1cbcc0f075..79d1fe0770c 100644 --- a/core/descriptors.runtime/src/org/jetbrains/kotlin/load/java/structure/reflect/ReflectJavaClass.kt +++ b/core/descriptors.runtime/src/org/jetbrains/kotlin/load/java/structure/reflect/ReflectJavaClass.kt @@ -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() diff --git a/core/descriptors.runtime/src/org/jetbrains/kotlin/load/java/structure/reflect/reflectStructureUtil.kt b/core/descriptors.runtime/src/org/jetbrains/kotlin/load/java/structure/reflect/reflectStructureUtil.kt index 2a33983d2b2..6766f8aa41a 100644 --- a/core/descriptors.runtime/src/org/jetbrains/kotlin/load/java/structure/reflect/reflectStructureUtil.kt +++ b/core/descriptors.runtime/src/org/jetbrains/kotlin/load/java/structure/reflect/reflectStructureUtil.kt @@ -34,6 +34,9 @@ private fun calculateVisibility(modifiers: Int): Visibility { } } +public fun Class<*>.isEnumClassOrSpecializedEnumEntryClass(): Boolean = + javaClass>().isAssignableFrom(this) + public val Class<*>.fqName: FqName get() = classId.asSingleFqName().toSafe() diff --git a/core/descriptors.runtime/src/org/jetbrains/kotlin/load/kotlin/reflect/ReflectKotlinClass.kt b/core/descriptors.runtime/src/org/jetbrains/kotlin/load/kotlin/reflect/ReflectKotlinClass.kt index b441cc556a8..d718ea8a953 100644 --- a/core/descriptors.runtime/src/org/jetbrains/kotlin/load/kotlin/reflect/ReflectKotlinClass.kt +++ b/core/descriptors.runtime/src/org/jetbrains/kotlin/load/kotlin/reflect/ReflectKotlinClass.kt @@ -159,19 +159,21 @@ private object ReflectClassStructure { visitor.visit(name, value) } javaClass>().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().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>().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 { diff --git a/idea/tests/org/jetbrains/kotlin/idea/stubs/ResolveByStubTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/stubs/ResolveByStubTestGenerated.java index 5feb6f6915d..48f65fb53c0 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/stubs/ResolveByStubTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/stubs/ResolveByStubTestGenerated.java @@ -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");