diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/ExperimentalUsageChecker.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/ExperimentalUsageChecker.kt index 72ff17c337b..b11737e502e 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/ExperimentalUsageChecker.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/ExperimentalUsageChecker.kt @@ -190,8 +190,9 @@ class ExperimentalUsageChecker(project: Project) : CallChecker { if (descriptor?.fqName == USE_EXPERIMENTAL_FQ_NAME) { val annotationClasses = descriptor.allValueArguments[USE_EXPERIMENTAL_ANNOTATION_CLASS] annotationClasses is ArrayValue && annotationClasses.value.any { annotationClass -> - annotationClass is KClassValue && annotationClass.value.let { (classId, arrayDimensions) -> - classId.asSingleFqName() == annotationFqName && arrayDimensions == 0 + annotationClass is KClassValue && annotationClass.value.let { value -> + value is KClassValue.Value.NormalClass && + value.classId.asSingleFqName() == annotationFqName && value.arrayDimensions == 0 } } } else false diff --git a/compiler/light-classes/src/org/jetbrains/kotlin/asJava/classes/ultraLightAnnotations.kt b/compiler/light-classes/src/org/jetbrains/kotlin/asJava/classes/ultraLightAnnotations.kt index a830a95af3f..a35fa1d7987 100644 --- a/compiler/light-classes/src/org/jetbrains/kotlin/asJava/classes/ultraLightAnnotations.kt +++ b/compiler/light-classes/src/org/jetbrains/kotlin/asJava/classes/ultraLightAnnotations.kt @@ -141,6 +141,7 @@ private fun ConstantValue<*>.asStringForPsiLiteral(parent: PsiElement): String = is NullValue -> "null" is StringValue -> "\"$value\"" is KClassValue -> { + val value = (value as KClassValue.Value.NormalClass).value val arrayPart = "[]".repeat(value.arrayNestedness) val fqName = value.classId.asSingleFqName() val canonicalText = psiType( diff --git a/compiler/light-classes/src/org/jetbrains/kotlin/asJava/elements/KtLightAnnotationsValues.kt b/compiler/light-classes/src/org/jetbrains/kotlin/asJava/elements/KtLightAnnotationsValues.kt index ddaaf526b05..b1e9a7a12fa 100644 --- a/compiler/light-classes/src/org/jetbrains/kotlin/asJava/elements/KtLightAnnotationsValues.kt +++ b/compiler/light-classes/src/org/jetbrains/kotlin/asJava/elements/KtLightAnnotationsValues.kt @@ -67,8 +67,9 @@ class KtLightPsiClassObjectAccessExpression(override val kotlinOrigin: KtClassLi override fun getType(): PsiType { val bindingContext = LightClassGenerationSupport.getInstance(this.project).analyze(kotlinOrigin) val (classId, arrayDimensions) = bindingContext[BindingContext.COMPILE_TIME_VALUE, kotlinOrigin] - ?.toConstantValue(TypeUtils.NO_EXPECTED_TYPE)?.safeAs()?.value ?: return PsiType.VOID - var type = psiType(classId.asSingleFqName().asString(), kotlinOrigin, boxPrimitiveType = arrayDimensions > 0) ?: return PsiType.VOID + ?.toConstantValue(TypeUtils.NO_EXPECTED_TYPE)?.safeAs()?.value + ?.safeAs()?.value ?: return PsiType.VOID + var type = psiType(classId.asSingleFqName().asString(), kotlinOrigin, boxPrimitiveType = arrayDimensions > 0) repeat(arrayDimensions) { type = type.createArrayType() } diff --git a/compiler/serialization/src/org/jetbrains/kotlin/serialization/AnnotationSerializer.kt b/compiler/serialization/src/org/jetbrains/kotlin/serialization/AnnotationSerializer.kt index 04288bdcc07..33f6ca7e1c8 100644 --- a/compiler/serialization/src/org/jetbrains/kotlin/serialization/AnnotationSerializer.kt +++ b/compiler/serialization/src/org/jetbrains/kotlin/serialization/AnnotationSerializer.kt @@ -16,6 +16,8 @@ package org.jetbrains.kotlin.serialization +import org.jetbrains.kotlin.builtins.KotlinBuiltIns +import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.descriptors.annotations.AnnotationArgumentVisitor import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor import org.jetbrains.kotlin.metadata.ProtoBuf @@ -99,10 +101,31 @@ class AnnotationSerializer(private val stringTable: DescriptorAwareStringTable) override fun visitKClassValue(value: KClassValue, data: Unit) { type = Type.CLASS - classId = stringTable.getQualifiedClassNameIndex(value.classId) - if (value.arrayDimensions > 0) { - arrayDimensionCount = value.arrayDimensions + when (val classValue = value.value) { + is KClassValue.Value.NormalClass -> { + classId = stringTable.getQualifiedClassNameIndex(classValue.classId) + + if (classValue.arrayDimensions > 0) { + arrayDimensionCount = classValue.arrayDimensions + } + } + is KClassValue.Value.LocalClass -> { + var arrayDimensions = 0 + var type = classValue.type + while (KotlinBuiltIns.isArray(type)) { + arrayDimensions++ + type = type.arguments.single().type + } + + val descriptor = type.constructor.declarationDescriptor as? ClassDescriptor + ?: error("Type parameters are not allowed in class literal annotation arguments: $classValue") + classId = stringTable.getFqNameIndex(descriptor) + + if (arrayDimensions > 0) { + arrayDimensionCount = arrayDimensions + } + } } } diff --git a/compiler/testData/codegen/box/reflection/annotations/localClassLiteral.kt b/compiler/testData/codegen/box/reflection/annotations/localClassLiteral.kt new file mode 100644 index 00000000000..6d9531a02ee --- /dev/null +++ b/compiler/testData/codegen/box/reflection/annotations/localClassLiteral.kt @@ -0,0 +1,24 @@ +// TARGET_BACKEND: JVM +// WITH_REFLECT + +package test + +import kotlin.reflect.KClass +import kotlin.test.assertEquals + +annotation class Anno(val k1: KClass<*>, val k2: KClass<*>, val k3: KClass<*>) + +fun box(): String { + class L + + @Anno(k1 = L::class, k2 = Array::class, k3 = Array>::class) + class M + + val fqName = "test.LocalClassLiteralKt\$box\$L" + assertEquals( + "[@test.Anno(k1=class $fqName, k2=class [L$fqName;, k3=class [[L$fqName;)]", + M::class.annotations.toString() + ) + + return "OK" +} diff --git a/compiler/testData/serialization/local/annotationsInLocalClass.kt b/compiler/testData/serialization/local/annotationsInLocalClass.kt index 41d03a859b3..8933faa7519 100644 --- a/compiler/testData/serialization/local/annotationsInLocalClass.kt +++ b/compiler/testData/serialization/local/annotationsInLocalClass.kt @@ -1,14 +1,25 @@ // CLASS_NAME_SUFFIX: A$foo$Local +import kotlin.reflect.KClass + class A { annotation class Ann(val info: String) - fun foo() { - @Ann("class") class Local { - @Ann("fun") fun foo(): Local = this - @field:Ann("val") val x = foo() + annotation class Bnn(val klass: KClass<*>) - @Ann("inner") inner class Inner + fun foo() { + @Ann("class") + class Local { + @Ann("fun") + fun foo(): Local = this + + @field:Ann("val") + @Bnn(Local::class) + val x = foo() + + @Ann("inner") + @Bnn(Array>::class) + inner class Inner } } } diff --git a/compiler/testData/serialization/local/annotationsInLocalClass.txt b/compiler/testData/serialization/local/annotationsInLocalClass.txt index 914bba315fe..5a412b34f80 100644 --- a/compiler/testData/serialization/local/annotationsInLocalClass.txt +++ b/compiler/testData/serialization/local/annotationsInLocalClass.txt @@ -1,9 +1,9 @@ @A.Ann(info = "class") local final class `A$foo$Local` public constructor `A$foo$Local`() -@field:A.Ann(info = "val") public final val x: `A$foo$Local` +@A.Bnn(klass = A$foo$Local::class) @field:A.Ann(info = "val") public final val x: `A$foo$Local` @A.Ann(info = "fun") public final fun foo(): `A$foo$Local` -@A.Ann(info = "inner") local final inner class Inner { +@A.Ann(info = "inner") @A.Bnn(klass = kotlin.Array>::class) local final inner class Inner { public constructor Inner() } diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java index 63913aa3c7f..e18e6cd3a04 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java @@ -19280,6 +19280,11 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest { runTest("compiler/testData/codegen/box/reflection/annotations/findAnnotation.kt"); } + @TestMetadata("localClassLiteral.kt") + public void testLocalClassLiteral() throws Exception { + runTest("compiler/testData/codegen/box/reflection/annotations/localClassLiteral.kt"); + } + @TestMetadata("localClassParameterAnnotation.kt") public void testLocalClassParameterAnnotation() throws Exception { runTest("compiler/testData/codegen/box/reflection/annotations/localClassParameterAnnotation.kt"); diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java index 6786f237b4f..b235e1baafd 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java @@ -19280,6 +19280,11 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes runTest("compiler/testData/codegen/box/reflection/annotations/findAnnotation.kt"); } + @TestMetadata("localClassLiteral.kt") + public void testLocalClassLiteral() throws Exception { + runTest("compiler/testData/codegen/box/reflection/annotations/localClassLiteral.kt"); + } + @TestMetadata("localClassParameterAnnotation.kt") public void testLocalClassParameterAnnotation() throws Exception { runTest("compiler/testData/codegen/box/reflection/annotations/localClassParameterAnnotation.kt"); diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java index 2e60502cda1..0eb2c2b4c62 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java @@ -19285,6 +19285,11 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes runTest("compiler/testData/codegen/box/reflection/annotations/findAnnotation.kt"); } + @TestMetadata("localClassLiteral.kt") + public void testLocalClassLiteral() throws Exception { + runTest("compiler/testData/codegen/box/reflection/annotations/localClassLiteral.kt"); + } + @TestMetadata("localClassParameterAnnotation.kt") public void testLocalClassParameterAnnotation() throws Exception { runTest("compiler/testData/codegen/box/reflection/annotations/localClassParameterAnnotation.kt"); diff --git a/core/descriptors/src/org/jetbrains/kotlin/renderer/DescriptorRendererImpl.kt b/core/descriptors/src/org/jetbrains/kotlin/renderer/DescriptorRendererImpl.kt index a769cf64694..c897cdd9ae2 100644 --- a/core/descriptors/src/org/jetbrains/kotlin/renderer/DescriptorRendererImpl.kt +++ b/core/descriptors/src/org/jetbrains/kotlin/renderer/DescriptorRendererImpl.kt @@ -462,10 +462,13 @@ internal class DescriptorRendererImpl( return when (value) { is ArrayValue -> value.value.joinToString(", ", "{", "}") { renderConstant(it) } is AnnotationValue -> renderAnnotation(value.value).removePrefix("@") - is KClassValue -> { - var type = value.classId.asSingleFqName().asString() - repeat(value.arrayDimensions) { type = "kotlin.Array<$type>" } - "$type::class" + is KClassValue -> when (val classValue = value.value) { + is KClassValue.Value.LocalClass -> "${classValue.type}::class" + is KClassValue.Value.NormalClass -> { + var type = classValue.classId.asSingleFqName().asString() + repeat(classValue.arrayDimensions) { type = "kotlin.Array<$type>" } + "$type::class" + } } else -> value.toString() } diff --git a/core/descriptors/src/org/jetbrains/kotlin/resolve/constants/constantValues.kt b/core/descriptors/src/org/jetbrains/kotlin/resolve/constants/constantValues.kt index a080605583c..5501e950f92 100644 --- a/core/descriptors/src/org/jetbrains/kotlin/resolve/constants/constantValues.kt +++ b/core/descriptors/src/org/jetbrains/kotlin/resolve/constants/constantValues.kt @@ -158,9 +158,17 @@ class IntValue(value: Int) : IntegerValueConstant(value) { override fun accept(visitor: AnnotationArgumentVisitor, data: D) = visitor.visitIntValue(this, data) } -class KClassValue(value: ClassLiteralValue) : ConstantValue(value) { - val classId: ClassId get() = value.classId - val arrayDimensions: Int get() = value.arrayNestedness +class KClassValue(value: Value) : ConstantValue(value) { + sealed class Value { + data class NormalClass(val value: ClassLiteralValue) : Value() { + val classId: ClassId get() = value.classId + val arrayDimensions: Int get() = value.arrayNestedness + } + + data class LocalClass(val type: KotlinType) : Value() + } + + constructor(value: ClassLiteralValue) : this(Value.NormalClass(value)) constructor(classId: ClassId, arrayDimensions: Int) : this(ClassLiteralValue(classId, arrayDimensions)) @@ -168,17 +176,23 @@ class KClassValue(value: ClassLiteralValue) : ConstantValue(v KotlinTypeFactory.simpleNotNullType(Annotations.EMPTY, module.builtIns.kClass, listOf(TypeProjectionImpl(getArgumentType(module)))) fun getArgumentType(module: ModuleDescriptor): KotlinType { - val descriptor = module.findClassAcrossModuleDependencies(classId) - ?: return ErrorUtils.createErrorType("Unresolved type: $classId (arrayDimensions=$arrayDimensions)") + when (value) { + is Value.LocalClass -> return value.type + is Value.NormalClass -> { + val (classId, arrayDimensions) = value.value + val descriptor = module.findClassAcrossModuleDependencies(classId) + ?: return ErrorUtils.createErrorType("Unresolved type: $classId (arrayDimensions=$arrayDimensions)") - // If this value refers to a class named test.Foo.Bar where both Foo and Bar have generic type parameters, - // we're constructing a type `test.Foo<*>.Bar<*>` below - var type = descriptor.defaultType.replaceArgumentsWithStarProjections() - repeat(arrayDimensions) { - type = module.builtIns.getArrayType(Variance.INVARIANT, type) + // If this value refers to a class named test.Foo.Bar where both Foo and Bar have generic type parameters, + // we're constructing a type `test.Foo<*>.Bar<*>` below + var type = descriptor.defaultType.replaceArgumentsWithStarProjections() + repeat(arrayDimensions) { + type = module.builtIns.getArrayType(Variance.INVARIANT, type) + } + + return type + } } - - return type } override fun accept(visitor: AnnotationArgumentVisitor, data: D) = visitor.visitKClassValue(this, data) @@ -196,7 +210,7 @@ class KClassValue(value: ClassLiteralValue) : ConstantValue(v return when (val descriptor = type.constructor.declarationDescriptor) { is ClassDescriptor -> { - val classId = descriptor.classId ?: return null + val classId = descriptor.classId ?: return KClassValue(KClassValue.Value.LocalClass(argumentType)) KClassValue(classId, arrayDimensions) } is TypeParameterDescriptor -> { diff --git a/core/reflection.jvm/src/kotlin/reflect/jvm/internal/util.kt b/core/reflection.jvm/src/kotlin/reflect/jvm/internal/util.kt index 2ad9415d885..81810e9a967 100644 --- a/core/reflection.jvm/src/kotlin/reflect/jvm/internal/util.kt +++ b/core/reflection.jvm/src/kotlin/reflect/jvm/internal/util.kt @@ -140,9 +140,13 @@ private fun ConstantValue<*>.toRuntimeValue(classLoader: ClassLoader): Any? = wh Util.getEnumConstantByName(enumClass as Class>, entryName.asString()) } } - is KClassValue -> { - val (classId, arrayDimensions) = value - loadClass(classLoader, classId, arrayDimensions) + is KClassValue -> when (val classValue = value) { + is KClassValue.Value.NormalClass -> + loadClass(classLoader, classValue.classId, classValue.arrayDimensions) + is KClassValue.Value.LocalClass -> { + // TODO: this doesn't work because of KT-30013 + (classValue.type.constructor.declarationDescriptor as? ClassDescriptor)?.toJavaClass() + } } is ErrorValue, is NullValue -> null else -> value // Primitives and strings diff --git a/idea/testData/checker/ResolveTypeInAnnotationArgumentRuntime.kt b/idea/testData/checker/ResolveTypeInAnnotationArgumentRuntime.kt index 58e8d2344f6..53547204104 100644 --- a/idea/testData/checker/ResolveTypeInAnnotationArgumentRuntime.kt +++ b/idea/testData/checker/ResolveTypeInAnnotationArgumentRuntime.kt @@ -2,4 +2,4 @@ import kotlin.reflect.KClass annotation class Ann(val value: KClass<*>) -@Ann(Array<String123>::class) class A +@Ann(Array<String123>::class) class A