From dc3cd01fad9d7bbb08bcefaf097385596f7ae439 Mon Sep 17 00:00:00 2001 From: Yan Zhulanow Date: Wed, 28 Mar 2018 18:27:19 +0300 Subject: [PATCH] Kapt: Find the right annotation descriptor in case of repeatable annotations (#KT-23427) --- .../stubs/ClassFileToSourceStubConverter.kt | 71 ++++- ...ileToSourceStubConverterTestGenerated.java | 6 + .../converter/repeatableAnnotations.kt | 58 ++++ .../converter/repeatableAnnotations.txt | 256 ++++++++++++++++++ 4 files changed, 381 insertions(+), 10 deletions(-) create mode 100644 plugins/kapt3/kapt3-compiler/testData/converter/repeatableAnnotations.kt create mode 100644 plugins/kapt3/kapt3-compiler/testData/converter/repeatableAnnotations.txt diff --git a/plugins/kapt3/kapt3-compiler/src/org/jetbrains/kotlin/kapt3/stubs/ClassFileToSourceStubConverter.kt b/plugins/kapt3/kapt3-compiler/src/org/jetbrains/kotlin/kapt3/stubs/ClassFileToSourceStubConverter.kt index 2c077a5979e..a3758fc8d85 100644 --- a/plugins/kapt3/kapt3-compiler/src/org/jetbrains/kotlin/kapt3/stubs/ClassFileToSourceStubConverter.kt +++ b/plugins/kapt3/kapt3-compiler/src/org/jetbrains/kotlin/kapt3/stubs/ClassFileToSourceStubConverter.kt @@ -41,6 +41,7 @@ import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall import org.jetbrains.kotlin.resolve.calls.model.DefaultValueArgument import org.jetbrains.kotlin.resolve.calls.model.ResolvedValueArgument +import org.jetbrains.kotlin.resolve.constants.* import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameOrNull import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe import org.jetbrains.kotlin.resolve.descriptorUtil.getSuperClassOrAny @@ -94,6 +95,9 @@ class ClassFileToSourceStubConverter( val bindings: Map get() = _bindings + private val typeMapper + get() = kaptContext.generationState.typeMapper + val treeMaker = TreeMaker.instance(kaptContext.context) as KaptTreeMaker private val signatureParser = SignatureParser(treeMaker) @@ -563,7 +567,7 @@ class ClassFileToSourceStubConverter( val superClassConstructorCall = if (superClassConstructor != null) { val args = mapJList(superClassConstructor.valueParameters) { param -> - convertLiteralExpression(getDefaultValue(kaptContext.generationState.typeMapper.mapType(param.type))) + convertLiteralExpression(getDefaultValue(typeMapper.mapType(param.type))) } val call = treeMaker.Apply(JavacList.nil(), treeMaker.SimpleName("super"), args) JavacList.of(treeMaker.Exec(call)) @@ -714,17 +718,14 @@ class ClassFileToSourceStubConverter( invisibleAnnotations: List?, descriptorAnnotations: Annotations ): JCModifiers { - fun findDescriptorAnnotation(anno: AnnotationNode): AnnotationDescriptor? { - val annoFqName = treeMaker.getQualifiedName(Type.getType(anno.desc)) - return descriptorAnnotations.findAnnotation(FqName(annoFqName)) + fun convertAndAdd(list: JavacList, anno: AnnotationNode): JavacList { + val annotationDescriptor = descriptorAnnotations.singleOrNull { checkIfAnnotationValueMatches(anno, AnnotationValue(it)) } + val annotationTree = convertAnnotation(anno, packageFqName, annotationDescriptor) ?: return list + return list.prepend(annotationTree) } - var annotations = visibleAnnotations?.fold(JavacList.nil()) { list, anno -> - convertAnnotation(anno, packageFqName, findDescriptorAnnotation(anno))?.let { list.prepend(it) } ?: list - } ?: JavacList.nil() - annotations = invisibleAnnotations?.fold(annotations) { list, anno -> - convertAnnotation(anno, packageFqName, findDescriptorAnnotation(anno))?.let { list.prepend(it) } ?: list - } ?: annotations + var annotations = visibleAnnotations?.fold(JavacList.nil(), ::convertAndAdd) ?: JavacList.nil() + annotations = invisibleAnnotations?.fold(annotations, ::convertAndAdd) ?: annotations val flags = when (kind) { ElementKind.ENUM -> access and CLASS_MODIFIERS and Opcodes.ACC_ABSTRACT.inv().toLong() @@ -877,6 +878,56 @@ class ClassFileToSourceStubConverter( } } + private fun checkIfAnnotationValueMatches(asm: Any?, desc: ConstantValue<*>): Boolean { + return when (asm) { + null -> desc.value == null + is Char -> desc is CharValue && desc.value == asm + is Byte -> desc is ByteValue && desc.value == asm + is Short -> desc is ShortValue && desc.value == asm + is Boolean -> desc is BooleanValue && desc.value == asm + is Int -> desc is IntValue && desc.value == asm + is Long -> desc is LongValue && desc.value == asm + is Float -> desc is FloatValue && desc.value == asm + is Double -> desc is DoubleValue && desc.value == asm + is String -> desc is StringValue && desc.value == asm + is ByteArray -> desc is ArrayValue && desc.value.size == asm.size + is BooleanArray -> desc is ArrayValue && desc.value.size == asm.size + is CharArray -> desc is ArrayValue && desc.value.size == asm.size + is ShortArray -> desc is ArrayValue && desc.value.size == asm.size + is IntArray -> desc is ArrayValue && desc.value.size == asm.size + is LongArray -> desc is ArrayValue && desc.value.size == asm.size + is FloatArray -> desc is ArrayValue && desc.value.size == asm.size + is DoubleArray -> desc is ArrayValue && desc.value.size == asm.size + is Array<*> -> { // Two-element String array for enumerations ([desc, fieldName]) + assert(asm.size == 2) + val valueName = (asm[1] as String).takeIf { isValidIdentifier(it) } ?: return false + // It's not that easy to check types here because of fqName/internalName differences. + // But enums can't extend other enums, so this should be enough. + desc is EnumValue && desc.enumEntryName.asString() == valueName + } + is List<*> -> { + desc is ArrayValue + && asm.size == desc.value.size + && asm.zip(desc.value).all { (eAsm, eDesc) -> checkIfAnnotationValueMatches(eAsm, eDesc) } + } + is Type -> desc is KClassValue && typeMapper.mapType(desc.value) == asm + is AnnotationNode -> { + val annotationDescriptor = (desc as? AnnotationValue)?.value ?: return false + if (typeMapper.mapType(annotationDescriptor.type).descriptor != asm.desc) return false + val asmAnnotationArgs = pairedListToMap(asm.values) + if (annotationDescriptor.allValueArguments.size != asmAnnotationArgs.size) return false + + for ((descName, descValue) in annotationDescriptor.allValueArguments) { + val asmValue = asmAnnotationArgs[descName.asString()] ?: return false + if (!checkIfAnnotationValueMatches(asmValue, descValue)) return false + } + + true + } + else -> false + } + } + private fun convertLiteralExpression(value: Any?): JCExpression { convertValueOfPrimitiveTypeOrString(value)?.let { return it } diff --git a/plugins/kapt3/kapt3-compiler/test/org/jetbrains/kotlin/kapt3/test/ClassFileToSourceStubConverterTestGenerated.java b/plugins/kapt3/kapt3-compiler/test/org/jetbrains/kotlin/kapt3/test/ClassFileToSourceStubConverterTestGenerated.java index 280161651cf..e1ce0fc5303 100644 --- a/plugins/kapt3/kapt3-compiler/test/org/jetbrains/kotlin/kapt3/test/ClassFileToSourceStubConverterTestGenerated.java +++ b/plugins/kapt3/kapt3-compiler/test/org/jetbrains/kotlin/kapt3/test/ClassFileToSourceStubConverterTestGenerated.java @@ -343,6 +343,12 @@ public class ClassFileToSourceStubConverterTestGenerated extends AbstractClassFi doTest(fileName); } + @TestMetadata("repeatableAnnotations.kt") + public void testRepeatableAnnotations() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("plugins/kapt3/kapt3-compiler/testData/converter/repeatableAnnotations.kt"); + doTest(fileName); + } + @TestMetadata("severalPackageParts.kt") public void testSeveralPackageParts() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("plugins/kapt3/kapt3-compiler/testData/converter/severalPackageParts.kt"); diff --git a/plugins/kapt3/kapt3-compiler/testData/converter/repeatableAnnotations.kt b/plugins/kapt3/kapt3-compiler/testData/converter/repeatableAnnotations.kt new file mode 100644 index 00000000000..3db376e18db --- /dev/null +++ b/plugins/kapt3/kapt3-compiler/testData/converter/repeatableAnnotations.kt @@ -0,0 +1,58 @@ +// FILE: lib/Anno.java +package lib; +public @interface Anno { + String[] construct() default {}; + String value(); +} + +//FILE: lib/R.java +package lib; + +public class R { + public static class id { + public final static int textView = 100; + } +} + +// FILE: test.kt +// WITH_RUNTIME +import lib.Anno +import kotlin.reflect.KClass + +class Test { + @Anno("1") + @Anno(value = "2", construct = ["A", "B"]) + @Anno("3", construct = ["C"]) + val value: String = "" +} + +annotation class AnnoChar(val x: Int, val chr: Char) +annotation class AnnoBoolean(val x: Int, val bool: Boolean) +annotation class AnnoInt(val x: Int, val i: Int) +annotation class AnnoLong(val x: Int, val l: Long) +annotation class AnnoFloat(val x: Int, val flt: Float) +annotation class AnnoDouble(val x: Int, val dbl: Double) + +annotation class AnnoString(val x: Int, val s: String) + +annotation class AnnoIntArray(val x: Int, val b: IntArray) +annotation class AnnoLongArray(val x: Int, val b: LongArray) + +annotation class AnnoArray(val x: Int, val a: Array) + +annotation class AnnoClass(val x: Int, val c: KClass) + +enum class Color { BLACK } +annotation class AnnoEnum(val x: Int, val c: Color) + +@AnnoChar(lib.R.id.textView, 'c') +@AnnoBoolean(lib.R.id.textView, false) +@AnnoInt(lib.R.id.textView, 5) +@AnnoFloat(lib.R.id.textView, 1.0f) +@AnnoDouble(lib.R.id.textView, 4.0) +@AnnoString(lib.R.id.textView, "AAA") +@AnnoIntArray(lib.R.id.textView, [1, 2, 3]) +@AnnoLongArray(lib.R.id.textView, [1L, 3L]) +@AnnoArray(lib.R.id.textView, [ "A", "B" ]) +@AnnoClass(lib.R.id.textView, Color::class) +class Test2 \ No newline at end of file diff --git a/plugins/kapt3/kapt3-compiler/testData/converter/repeatableAnnotations.txt b/plugins/kapt3/kapt3-compiler/testData/converter/repeatableAnnotations.txt new file mode 100644 index 00000000000..2779b066bb0 --- /dev/null +++ b/plugins/kapt3/kapt3-compiler/testData/converter/repeatableAnnotations.txt @@ -0,0 +1,256 @@ +import java.lang.System; + +@kotlin.Metadata() +@java.lang.annotation.Retention(value = java.lang.annotation.RetentionPolicy.RUNTIME) +public abstract @interface AnnoArray { + + public abstract int x(); + + public abstract java.lang.String[] a(); +} + +//////////////////// + + +import java.lang.System; + +@kotlin.Metadata() +@java.lang.annotation.Retention(value = java.lang.annotation.RetentionPolicy.RUNTIME) +public abstract @interface AnnoBoolean { + + public abstract int x(); + + public abstract boolean bool(); +} + +//////////////////// + + +import java.lang.System; + +@kotlin.Metadata() +@java.lang.annotation.Retention(value = java.lang.annotation.RetentionPolicy.RUNTIME) +public abstract @interface AnnoChar { + + public abstract int x(); + + public abstract char chr(); +} + +//////////////////// + + +import java.lang.System; + +@kotlin.Metadata() +@java.lang.annotation.Retention(value = java.lang.annotation.RetentionPolicy.RUNTIME) +public abstract @interface AnnoClass { + + public abstract int x(); + + public abstract java.lang.Class c(); +} + +//////////////////// + + +import java.lang.System; + +@kotlin.Metadata() +@java.lang.annotation.Retention(value = java.lang.annotation.RetentionPolicy.RUNTIME) +public abstract @interface AnnoDouble { + + public abstract int x(); + + public abstract double dbl(); +} + +//////////////////// + + +import java.lang.System; + +@kotlin.Metadata() +@java.lang.annotation.Retention(value = java.lang.annotation.RetentionPolicy.RUNTIME) +public abstract @interface AnnoEnum { + + public abstract int x(); + + public abstract Color c(); +} + +//////////////////// + + +import java.lang.System; + +@kotlin.Metadata() +@java.lang.annotation.Retention(value = java.lang.annotation.RetentionPolicy.RUNTIME) +public abstract @interface AnnoFloat { + + public abstract int x(); + + public abstract float flt(); +} + +//////////////////// + + +import java.lang.System; + +@kotlin.Metadata() +@java.lang.annotation.Retention(value = java.lang.annotation.RetentionPolicy.RUNTIME) +public abstract @interface AnnoInt { + + public abstract int x(); + + public abstract int i(); +} + +//////////////////// + + +import java.lang.System; + +@kotlin.Metadata() +@java.lang.annotation.Retention(value = java.lang.annotation.RetentionPolicy.RUNTIME) +public abstract @interface AnnoIntArray { + + public abstract int x(); + + public abstract int[] b(); +} + +//////////////////// + + +import java.lang.System; + +@kotlin.Metadata() +@java.lang.annotation.Retention(value = java.lang.annotation.RetentionPolicy.RUNTIME) +public abstract @interface AnnoLong { + + public abstract int x(); + + public abstract long l(); +} + +//////////////////// + + +import java.lang.System; + +@kotlin.Metadata() +@java.lang.annotation.Retention(value = java.lang.annotation.RetentionPolicy.RUNTIME) +public abstract @interface AnnoLongArray { + + public abstract int x(); + + public abstract long[] b(); +} + +//////////////////// + + +import java.lang.System; + +@kotlin.Metadata() +@java.lang.annotation.Retention(value = java.lang.annotation.RetentionPolicy.RUNTIME) +public abstract @interface AnnoString { + + public abstract int x(); + + public abstract java.lang.String s(); +} + +//////////////////// + + +import java.lang.System; + +@kotlin.Metadata() +public enum Color { + /*public static final*/ BLACK /* = new Color() */; + + Color() { + } +} + +//////////////////// + + +import java.lang.System; + +@kotlin.Metadata() +public final class Test { + @org.jetbrains.annotations.NotNull() + private final java.lang.String value = ""; + + @lib.Anno(value = "3", construct = {"C"}) + @lib.Anno(value = "2", construct = {"A", "B"}) + @lib.Anno(value = "1") + public static void value$annotations() { + } + + @org.jetbrains.annotations.NotNull() + public final java.lang.String getValue() { + return null; + } + + public Test() { + super(); + } +} + +//////////////////// + + +import java.lang.System; + +@kotlin.Metadata() +@AnnoClass(x = lib.R.id.textView, c = Color.class) +@AnnoArray(x = lib.R.id.textView, a = {"A", "B"}) +@AnnoLongArray(x = lib.R.id.textView, b = {1L, 3L}) +@AnnoIntArray(x = lib.R.id.textView, b = {1, 2, 3}) +@AnnoString(x = lib.R.id.textView, s = "AAA") +@AnnoDouble(x = lib.R.id.textView, dbl = 4.0) +@AnnoFloat(x = lib.R.id.textView, flt = 1.0F) +@AnnoInt(x = lib.R.id.textView, i = 5) +@AnnoBoolean(x = lib.R.id.textView, bool = false) +@AnnoChar(x = lib.R.id.textView, chr = 'c') +public final class Test2 { + + public Test2() { + super(); + } +} + +//////////////////// + +package lib; + +public @interface Anno { + + String[] construct() default {}; + + String value(); +} + +//////////////////// + +package lib; + +public class R { + + public R() { + super(); + } + + public static class id { + + public id() { + super(); + } + public static final int textView = 100; + } +}