Restore support for local class literals in annotation arguments
This was broken in c1ab08c8ce where we started to represent KClassValue
as a ClassId of the referenced class + number of times it's been wrapped
into kotlin.Array. Local classes do not have a sane ClassId, so in this
change we restore the old behavior by representing KClassValue with a
sealed class value instead
#KT-29891 Fixed
This commit is contained in:
+3
-2
@@ -190,8 +190,9 @@ class ExperimentalUsageChecker(project: Project) : CallChecker {
|
|||||||
if (descriptor?.fqName == USE_EXPERIMENTAL_FQ_NAME) {
|
if (descriptor?.fqName == USE_EXPERIMENTAL_FQ_NAME) {
|
||||||
val annotationClasses = descriptor.allValueArguments[USE_EXPERIMENTAL_ANNOTATION_CLASS]
|
val annotationClasses = descriptor.allValueArguments[USE_EXPERIMENTAL_ANNOTATION_CLASS]
|
||||||
annotationClasses is ArrayValue && annotationClasses.value.any { annotationClass ->
|
annotationClasses is ArrayValue && annotationClasses.value.any { annotationClass ->
|
||||||
annotationClass is KClassValue && annotationClass.value.let { (classId, arrayDimensions) ->
|
annotationClass is KClassValue && annotationClass.value.let { value ->
|
||||||
classId.asSingleFqName() == annotationFqName && arrayDimensions == 0
|
value is KClassValue.Value.NormalClass &&
|
||||||
|
value.classId.asSingleFqName() == annotationFqName && value.arrayDimensions == 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else false
|
} else false
|
||||||
|
|||||||
+1
@@ -141,6 +141,7 @@ private fun ConstantValue<*>.asStringForPsiLiteral(parent: PsiElement): String =
|
|||||||
is NullValue -> "null"
|
is NullValue -> "null"
|
||||||
is StringValue -> "\"$value\""
|
is StringValue -> "\"$value\""
|
||||||
is KClassValue -> {
|
is KClassValue -> {
|
||||||
|
val value = (value as KClassValue.Value.NormalClass).value
|
||||||
val arrayPart = "[]".repeat(value.arrayNestedness)
|
val arrayPart = "[]".repeat(value.arrayNestedness)
|
||||||
val fqName = value.classId.asSingleFqName()
|
val fqName = value.classId.asSingleFqName()
|
||||||
val canonicalText = psiType(
|
val canonicalText = psiType(
|
||||||
|
|||||||
+3
-2
@@ -67,8 +67,9 @@ class KtLightPsiClassObjectAccessExpression(override val kotlinOrigin: KtClassLi
|
|||||||
override fun getType(): PsiType {
|
override fun getType(): PsiType {
|
||||||
val bindingContext = LightClassGenerationSupport.getInstance(this.project).analyze(kotlinOrigin)
|
val bindingContext = LightClassGenerationSupport.getInstance(this.project).analyze(kotlinOrigin)
|
||||||
val (classId, arrayDimensions) = bindingContext[BindingContext.COMPILE_TIME_VALUE, kotlinOrigin]
|
val (classId, arrayDimensions) = bindingContext[BindingContext.COMPILE_TIME_VALUE, kotlinOrigin]
|
||||||
?.toConstantValue(TypeUtils.NO_EXPECTED_TYPE)?.safeAs<KClassValue>()?.value ?: return PsiType.VOID
|
?.toConstantValue(TypeUtils.NO_EXPECTED_TYPE)?.safeAs<KClassValue>()?.value
|
||||||
var type = psiType(classId.asSingleFqName().asString(), kotlinOrigin, boxPrimitiveType = arrayDimensions > 0) ?: return PsiType.VOID
|
?.safeAs<KClassValue.Value.NormalClass>()?.value ?: return PsiType.VOID
|
||||||
|
var type = psiType(classId.asSingleFqName().asString(), kotlinOrigin, boxPrimitiveType = arrayDimensions > 0)
|
||||||
repeat(arrayDimensions) {
|
repeat(arrayDimensions) {
|
||||||
type = type.createArrayType()
|
type = type.createArrayType()
|
||||||
}
|
}
|
||||||
|
|||||||
+26
-3
@@ -16,6 +16,8 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.serialization
|
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.AnnotationArgumentVisitor
|
||||||
import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor
|
import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor
|
||||||
import org.jetbrains.kotlin.metadata.ProtoBuf
|
import org.jetbrains.kotlin.metadata.ProtoBuf
|
||||||
@@ -99,10 +101,31 @@ class AnnotationSerializer(private val stringTable: DescriptorAwareStringTable)
|
|||||||
|
|
||||||
override fun visitKClassValue(value: KClassValue, data: Unit) {
|
override fun visitKClassValue(value: KClassValue, data: Unit) {
|
||||||
type = Type.CLASS
|
type = Type.CLASS
|
||||||
classId = stringTable.getQualifiedClassNameIndex(value.classId)
|
|
||||||
|
|
||||||
if (value.arrayDimensions > 0) {
|
when (val classValue = value.value) {
|
||||||
arrayDimensionCount = value.arrayDimensions
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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<L?>::class, k3 = Array<out Array<L>>::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"
|
||||||
|
}
|
||||||
@@ -1,14 +1,25 @@
|
|||||||
// CLASS_NAME_SUFFIX: A$foo$Local
|
// CLASS_NAME_SUFFIX: A$foo$Local
|
||||||
|
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
class A {
|
class A {
|
||||||
annotation class Ann(val info: String)
|
annotation class Ann(val info: String)
|
||||||
|
|
||||||
fun foo() {
|
annotation class Bnn(val klass: KClass<*>)
|
||||||
@Ann("class") class Local {
|
|
||||||
@Ann("fun") fun foo(): Local = this
|
|
||||||
@field:Ann("val") val x = foo()
|
|
||||||
|
|
||||||
@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<Array<out Local>>::class)
|
||||||
|
inner class Inner
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
@A.Ann(info = "class") local final class `A$foo$Local`
|
@A.Ann(info = "class") local final class `A$foo$Local`
|
||||||
|
|
||||||
public constructor `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 = "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<kotlin.Array<A$foo$Local>>::class) local final inner class Inner {
|
||||||
public constructor Inner()
|
public constructor Inner()
|
||||||
}
|
}
|
||||||
|
|||||||
+5
@@ -19280,6 +19280,11 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
|
|||||||
runTest("compiler/testData/codegen/box/reflection/annotations/findAnnotation.kt");
|
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")
|
@TestMetadata("localClassParameterAnnotation.kt")
|
||||||
public void testLocalClassParameterAnnotation() throws Exception {
|
public void testLocalClassParameterAnnotation() throws Exception {
|
||||||
runTest("compiler/testData/codegen/box/reflection/annotations/localClassParameterAnnotation.kt");
|
runTest("compiler/testData/codegen/box/reflection/annotations/localClassParameterAnnotation.kt");
|
||||||
|
|||||||
+5
@@ -19280,6 +19280,11 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes
|
|||||||
runTest("compiler/testData/codegen/box/reflection/annotations/findAnnotation.kt");
|
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")
|
@TestMetadata("localClassParameterAnnotation.kt")
|
||||||
public void testLocalClassParameterAnnotation() throws Exception {
|
public void testLocalClassParameterAnnotation() throws Exception {
|
||||||
runTest("compiler/testData/codegen/box/reflection/annotations/localClassParameterAnnotation.kt");
|
runTest("compiler/testData/codegen/box/reflection/annotations/localClassParameterAnnotation.kt");
|
||||||
|
|||||||
+5
@@ -19285,6 +19285,11 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes
|
|||||||
runTest("compiler/testData/codegen/box/reflection/annotations/findAnnotation.kt");
|
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")
|
@TestMetadata("localClassParameterAnnotation.kt")
|
||||||
public void testLocalClassParameterAnnotation() throws Exception {
|
public void testLocalClassParameterAnnotation() throws Exception {
|
||||||
runTest("compiler/testData/codegen/box/reflection/annotations/localClassParameterAnnotation.kt");
|
runTest("compiler/testData/codegen/box/reflection/annotations/localClassParameterAnnotation.kt");
|
||||||
|
|||||||
@@ -462,10 +462,13 @@ internal class DescriptorRendererImpl(
|
|||||||
return when (value) {
|
return when (value) {
|
||||||
is ArrayValue -> value.value.joinToString(", ", "{", "}") { renderConstant(it) }
|
is ArrayValue -> value.value.joinToString(", ", "{", "}") { renderConstant(it) }
|
||||||
is AnnotationValue -> renderAnnotation(value.value).removePrefix("@")
|
is AnnotationValue -> renderAnnotation(value.value).removePrefix("@")
|
||||||
is KClassValue -> {
|
is KClassValue -> when (val classValue = value.value) {
|
||||||
var type = value.classId.asSingleFqName().asString()
|
is KClassValue.Value.LocalClass -> "${classValue.type}::class"
|
||||||
repeat(value.arrayDimensions) { type = "kotlin.Array<$type>" }
|
is KClassValue.Value.NormalClass -> {
|
||||||
"$type::class"
|
var type = classValue.classId.asSingleFqName().asString()
|
||||||
|
repeat(classValue.arrayDimensions) { type = "kotlin.Array<$type>" }
|
||||||
|
"$type::class"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else -> value.toString()
|
else -> value.toString()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -158,9 +158,17 @@ class IntValue(value: Int) : IntegerValueConstant<Int>(value) {
|
|||||||
override fun <R, D> accept(visitor: AnnotationArgumentVisitor<R, D>, data: D) = visitor.visitIntValue(this, data)
|
override fun <R, D> accept(visitor: AnnotationArgumentVisitor<R, D>, data: D) = visitor.visitIntValue(this, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
class KClassValue(value: ClassLiteralValue) : ConstantValue<ClassLiteralValue>(value) {
|
class KClassValue(value: Value) : ConstantValue<KClassValue.Value>(value) {
|
||||||
val classId: ClassId get() = value.classId
|
sealed class Value {
|
||||||
val arrayDimensions: Int get() = value.arrayNestedness
|
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))
|
constructor(classId: ClassId, arrayDimensions: Int) : this(ClassLiteralValue(classId, arrayDimensions))
|
||||||
|
|
||||||
@@ -168,17 +176,23 @@ class KClassValue(value: ClassLiteralValue) : ConstantValue<ClassLiteralValue>(v
|
|||||||
KotlinTypeFactory.simpleNotNullType(Annotations.EMPTY, module.builtIns.kClass, listOf(TypeProjectionImpl(getArgumentType(module))))
|
KotlinTypeFactory.simpleNotNullType(Annotations.EMPTY, module.builtIns.kClass, listOf(TypeProjectionImpl(getArgumentType(module))))
|
||||||
|
|
||||||
fun getArgumentType(module: ModuleDescriptor): KotlinType {
|
fun getArgumentType(module: ModuleDescriptor): KotlinType {
|
||||||
val descriptor = module.findClassAcrossModuleDependencies(classId)
|
when (value) {
|
||||||
?: return ErrorUtils.createErrorType("Unresolved type: $classId (arrayDimensions=$arrayDimensions)")
|
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,
|
// 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
|
// we're constructing a type `test.Foo<*>.Bar<*>` below
|
||||||
var type = descriptor.defaultType.replaceArgumentsWithStarProjections()
|
var type = descriptor.defaultType.replaceArgumentsWithStarProjections()
|
||||||
repeat(arrayDimensions) {
|
repeat(arrayDimensions) {
|
||||||
type = module.builtIns.getArrayType(Variance.INVARIANT, type)
|
type = module.builtIns.getArrayType(Variance.INVARIANT, type)
|
||||||
|
}
|
||||||
|
|
||||||
|
return type
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return type
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun <R, D> accept(visitor: AnnotationArgumentVisitor<R, D>, data: D) = visitor.visitKClassValue(this, data)
|
override fun <R, D> accept(visitor: AnnotationArgumentVisitor<R, D>, data: D) = visitor.visitKClassValue(this, data)
|
||||||
@@ -196,7 +210,7 @@ class KClassValue(value: ClassLiteralValue) : ConstantValue<ClassLiteralValue>(v
|
|||||||
|
|
||||||
return when (val descriptor = type.constructor.declarationDescriptor) {
|
return when (val descriptor = type.constructor.declarationDescriptor) {
|
||||||
is ClassDescriptor -> {
|
is ClassDescriptor -> {
|
||||||
val classId = descriptor.classId ?: return null
|
val classId = descriptor.classId ?: return KClassValue(KClassValue.Value.LocalClass(argumentType))
|
||||||
KClassValue(classId, arrayDimensions)
|
KClassValue(classId, arrayDimensions)
|
||||||
}
|
}
|
||||||
is TypeParameterDescriptor -> {
|
is TypeParameterDescriptor -> {
|
||||||
|
|||||||
@@ -140,9 +140,13 @@ private fun ConstantValue<*>.toRuntimeValue(classLoader: ClassLoader): Any? = wh
|
|||||||
Util.getEnumConstantByName(enumClass as Class<out Enum<*>>, entryName.asString())
|
Util.getEnumConstantByName(enumClass as Class<out Enum<*>>, entryName.asString())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is KClassValue -> {
|
is KClassValue -> when (val classValue = value) {
|
||||||
val (classId, arrayDimensions) = value
|
is KClassValue.Value.NormalClass ->
|
||||||
loadClass(classLoader, classId, arrayDimensions)
|
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
|
is ErrorValue, is NullValue -> null
|
||||||
else -> value // Primitives and strings
|
else -> value // Primitives and strings
|
||||||
|
|||||||
@@ -2,4 +2,4 @@ import kotlin.reflect.KClass
|
|||||||
|
|
||||||
annotation class Ann(val value: KClass<*>)
|
annotation class Ann(val value: KClass<*>)
|
||||||
|
|
||||||
@Ann(<error descr="[ANNOTATION_ARGUMENT_MUST_BE_KCLASS_LITERAL] An annotation argument must be a class literal (T::class)">Array<<error descr="[UNRESOLVED_REFERENCE] Unresolved reference: String123">String123</error>>::class</error>) class A
|
@Ann(Array<<error descr="[UNRESOLVED_REFERENCE] Unresolved reference: String123">String123</error>>::class) class A
|
||||||
|
|||||||
Reference in New Issue
Block a user