Merge "Make XType extend XAnnotated to support type annotations" into androidx-main
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XType.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XType.kt
index b1a65c0..9ad98bf 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XType.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XType.kt
@@ -29,7 +29,7 @@
* @see javax.lang.model.type.TypeMirror
* @see [XArrayType]
*/
-interface XType {
+interface XType : XAnnotated {
/**
* The Javapoet [TypeName] representation of the type
*/
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacKmAnnotation.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacKmAnnotation.kt
new file mode 100644
index 0000000..ac2d325
--- /dev/null
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacKmAnnotation.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.compiler.processing.javac
+
+import androidx.room.compiler.processing.InternalXAnnotation
+import androidx.room.compiler.processing.XAnnotationBox
+import androidx.room.compiler.processing.XAnnotationValue
+import androidx.room.compiler.processing.XType
+import androidx.room.compiler.processing.javac.kotlin.KmAnnotationContainer
+
+internal class JavacKmAnnotation(
+ private val env: JavacProcessingEnv,
+ private val kmAnnotation: KmAnnotationContainer
+) : InternalXAnnotation() {
+ override fun <T : Annotation> asAnnotationBox(annotationClass: Class<T>): XAnnotationBox<T> {
+ throw UnsupportedOperationException("No plan to support XAnnotationBox.")
+ }
+
+ override val name: String
+ get() = typeElement.name
+
+ override val qualifiedName: String
+ get() = typeElement.qualifiedName
+
+ override val typeElement: JavacTypeElement by lazy {
+ requireNotNull(env.findTypeElement(kmAnnotation.className))
+ }
+
+ override val type: XType
+ get() = typeElement.type
+
+ override val annotationValues: List<XAnnotationValue> by lazy {
+ val methods = typeElement.getDeclaredMethods()
+ methods.map { method ->
+ JavacKmAnnotationValue(
+ method = method,
+ kmAnnotationArgumentContainer =
+ kmAnnotation.getArguments(env).getValue(method.jvmName)
+ )
+ }
+ }
+}
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacKmAnnotationValue.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacKmAnnotationValue.kt
new file mode 100644
index 0000000..c100d0b
--- /dev/null
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacKmAnnotationValue.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.compiler.processing.javac
+
+import androidx.room.compiler.processing.InternalXAnnotationValue
+import androidx.room.compiler.processing.XMethodElement
+import androidx.room.compiler.processing.XType
+import androidx.room.compiler.processing.javac.kotlin.KmAnnotationArgumentContainer
+
+internal class JavacKmAnnotationValue(
+ private val method: XMethodElement,
+ override val valueType: XType = method.returnType,
+ private val kmAnnotationArgumentContainer: KmAnnotationArgumentContainer
+) : InternalXAnnotationValue() {
+ override val name: String = method.jvmName
+ override val value: Any? by lazy {
+ kmAnnotationArgumentContainer.getValue(method)
+ }
+}
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacType.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacType.kt
index 3dd55ea..89420be 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacType.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacType.kt
@@ -17,6 +17,9 @@
package androidx.room.compiler.processing.javac
import androidx.room.compiler.codegen.XTypeName
+import androidx.room.compiler.processing.InternalXAnnotated
+import androidx.room.compiler.processing.XAnnotation
+import androidx.room.compiler.processing.XAnnotationBox
import androidx.room.compiler.processing.XEquality
import androidx.room.compiler.processing.XNullability
import androidx.room.compiler.processing.XRawType
@@ -25,6 +28,8 @@
import androidx.room.compiler.processing.javac.kotlin.KmTypeContainer
import androidx.room.compiler.processing.ksp.ERROR_JTYPE_NAME
import androidx.room.compiler.processing.safeTypeName
+import androidx.room.compiler.processing.unwrapRepeatedAnnotationsFromContainer
+import com.google.auto.common.MoreElements
import com.google.auto.common.MoreTypes
import javax.lang.model.type.TypeKind
import javax.lang.model.type.TypeMirror
@@ -34,7 +39,8 @@
internal val env: JavacProcessingEnv,
open val typeMirror: TypeMirror,
internal val maybeNullability: XNullability?,
-) : XType, XEquality {
+) : XType, XEquality, InternalXAnnotated {
+
// Kotlin type information about the type if this type is driven from Kotlin code.
abstract val kotlinType: KmTypeContainer?
@@ -85,6 +91,37 @@
override fun asTypeName() = xTypeName
+ override fun <T : Annotation> getAnnotations(
+ annotation: KClass<T>,
+ containerAnnotation: KClass<out Annotation>?
+ ): List<XAnnotationBox<T>> {
+ throw UnsupportedOperationException("No plan to support XAnnotationBox.")
+ }
+
+ override fun hasAnnotation(
+ annotation: KClass<out Annotation>,
+ containerAnnotation: KClass<out Annotation>?
+ ): Boolean {
+ val annotationClassName: String = annotation.java.canonicalName!!
+ return getAllAnnotations().any { it.qualifiedName == annotationClassName }
+ }
+
+ override fun getAllAnnotations(): List<XAnnotation> {
+ return kotlinType?.annotations?.map {
+ JavacKmAnnotation(env, it)
+ } ?: typeMirror.annotationMirrors.map { mirror -> JavacAnnotation(env, mirror) }
+ .flatMap { annotation ->
+ annotation.unwrapRepeatedAnnotationsFromContainer() ?: listOf(annotation)
+ }
+ }
+
+ override fun hasAnnotationWithPackage(pkg: String): Boolean {
+ return getAllAnnotations().any {
+ val element = (it.typeElement as JavacTypeElement).element
+ MoreElements.getPackage(element).toString() == pkg
+ }
+ }
+
override fun equals(other: Any?): Boolean {
return XEquality.equals(this, other)
}
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacTypeElement.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacTypeElement.kt
index 5b09016..1029947 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacTypeElement.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacTypeElement.kt
@@ -37,6 +37,7 @@
import com.squareup.kotlinpoet.javapoet.JClassName
import javax.lang.model.element.ElementKind
import javax.lang.model.element.TypeElement
+import javax.lang.model.type.DeclaredType
import javax.lang.model.type.TypeKind
import javax.lang.model.util.ElementFilter
@@ -218,11 +219,15 @@
}
override val superInterfaces by lazy {
+ val superTypesFromKotlinMetadata = kotlinMetadata?.superTypes
+ ?.associateBy { it.className } ?: emptyMap()
element.interfaces.map {
+ check(it is DeclaredType)
+ val interfaceName = ClassName.get(MoreElements.asType(it.asElement()))
val element = MoreTypes.asTypeElement(it)
env.wrap<JavacType>(
typeMirror = it,
- kotlinType = KmClassContainer.createFor(env, element)?.type,
+ kotlinType = superTypesFromKotlinMetadata[interfaceName.canonicalName()],
elementNullability = element.nullability
)
}
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/kotlin/KotlinClassMetadataUtils.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/kotlin/KotlinClassMetadataUtils.kt
index fdf221a..9e8131c 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/kotlin/KotlinClassMetadataUtils.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/kotlin/KotlinClassMetadataUtils.kt
@@ -16,7 +16,12 @@
package androidx.room.compiler.processing.javac.kotlin
+import androidx.room.compiler.processing.XArrayType
+import androidx.room.compiler.processing.XEnumTypeElement
+import androidx.room.compiler.processing.XMethodElement
import androidx.room.compiler.processing.XNullability
+import androidx.room.compiler.processing.javac.JavacKmAnnotation
+import androidx.room.compiler.processing.javac.JavacKmAnnotationValue
import androidx.room.compiler.processing.javac.JavacProcessingEnv
import androidx.room.compiler.processing.util.sanitizeAsJavaParameterName
import javax.lang.model.element.Element
@@ -25,7 +30,10 @@
import javax.tools.Diagnostic
import kotlinx.metadata.Flag
import kotlinx.metadata.Flags
+import kotlinx.metadata.KmAnnotation
+import kotlinx.metadata.KmAnnotationArgument
import kotlinx.metadata.KmClass
+import kotlinx.metadata.KmClassifier
import kotlinx.metadata.KmConstructor
import kotlinx.metadata.KmFunction
import kotlinx.metadata.KmProperty
@@ -50,10 +58,14 @@
val type: KmTypeContainer by lazy {
KmTypeContainer(
- kmType = KmType(flags),
+ kmType = KmType(flags).apply {
+ classifier = KmClassifier.Class(kmClass.name)
+ },
typeArguments = kmClass.typeParameters.map { kmTypeParameter ->
KmTypeContainer(
- kmType = KmType(kmTypeParameter.flags),
+ kmType = KmType(kmTypeParameter.flags).apply {
+ classifier = KmClassifier.Class(kmTypeParameter.name)
+ },
typeArguments = emptyList(),
upperBounds = kmTypeParameter.upperBounds.map { it.asContainer() }
)
@@ -65,6 +77,10 @@
kmClass.supertypes.firstOrNull()?.asContainer()
}
+ val superTypes: List<KmTypeContainer> by lazy {
+ kmClass.supertypes.map { it.asContainer() }
+ }
+
val typeParameters: List<KmTypeParameterContainer> by lazy {
kmClass.typeParameters.map { it.asContainer() }
}
@@ -265,6 +281,16 @@
) : KmFlags {
override val flags: Flags
get() = kmType.flags
+
+ val className: String? = kmType.classifier.let {
+ when (it) {
+ is KmClassifier.Class -> it.name.replace('/', '.')
+ else -> null
+ }
+ }
+
+ val annotations = kmType.annotations.map { it.asContainer() }
+
fun isExtensionType() =
kmType.annotations.any { it.className == "kotlin/ExtensionFunctionType" }
fun isNullable() = Flag.Type.IS_NULLABLE(flags)
@@ -278,6 +304,49 @@
)
}
+internal class KmAnnotationContainer(private val kmAnnotation: KmAnnotation) {
+ val className = kmAnnotation.className.replace('/', '.')
+ fun getArguments(env: JavacProcessingEnv): Map<String, KmAnnotationArgumentContainer> {
+ return kmAnnotation.arguments.mapValues { (_, arg) ->
+ arg.asContainer(env)
+ }
+ }
+}
+
+internal class KmAnnotationArgumentContainer(
+ private val env: JavacProcessingEnv,
+ private val kmAnnotationArgument: KmAnnotationArgument
+) {
+ fun getValue(method: XMethodElement): Any? {
+ return kmAnnotationArgument.let {
+ when (it) {
+ is KmAnnotationArgument.LiteralValue<*> -> it.value
+ is KmAnnotationArgument.ArrayValue -> {
+ it.elements.map {
+ val valueType = (method.returnType as XArrayType).componentType
+ JavacKmAnnotationValue(method, valueType, it.asContainer(env))
+ }
+ }
+ is KmAnnotationArgument.EnumValue -> {
+ val enumTypeElement = env.findTypeElement(
+ it.enumClassName.replace('/', '.')) as XEnumTypeElement
+ enumTypeElement.entries.associateBy { it.name }[it.enumEntryName]
+ }
+ is KmAnnotationArgument.AnnotationValue -> {
+ val kmAnnotation = KmAnnotation(
+ it.annotation.className,
+ it.annotation.arguments
+ ).asContainer()
+ JavacKmAnnotation(env, kmAnnotation)
+ }
+ is KmAnnotationArgument.KClassValue -> {
+ env.requireType(it.className.replace('/', '.'))
+ }
+ }
+ }
+ }
+}
+
internal val KmTypeContainer.nullability: XNullability
get() = if (isNullable()) {
XNullability.NULLABLE
@@ -346,13 +415,14 @@
// it here since it is not valid name
name = "set-?".sanitizeAsJavaParameterName(0)
).apply { type = this@asContainer.returnType }
+ val returnType = KmType(0).apply { classifier = KmClassifier.Class("Unit") }
KmPropertyFunctionContainerImpl(
flags = this.setterFlags,
name = JvmAbi.computeSetterName(this.name),
jvmName = it.name,
descriptor = it.asString(),
parameters = listOf(param.asContainer()),
- returnType = KmType(0).asContainer(),
+ returnType = returnType.asContainer(),
)
},
)
@@ -373,4 +443,9 @@
KmValueParameterContainer(
kmValueParameter = this,
type = this.type.asContainer()
- )
\ No newline at end of file
+ )
+
+private fun KmAnnotation.asContainer() = KmAnnotationContainer(this)
+
+private fun KmAnnotationArgument.asContainer(env: JavacProcessingEnv) =
+ KmAnnotationArgumentContainer(env, this)
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspAnnotated.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspAnnotated.kt
index 1080617..4039290 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspAnnotated.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspAnnotated.kt
@@ -72,7 +72,7 @@
override fun hasAnnotationWithPackage(pkg: String): Boolean {
return annotations().any {
- it.annotationType.resolve().declaration.qualifiedName?.getQualifier() == pkg
+ it.annotationType.resolve().declaration.packageName.asString() == pkg
}
}
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspAnnotation.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspAnnotation.kt
index 51a0c6e..4e48afc 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspAnnotation.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspAnnotation.kt
@@ -28,7 +28,9 @@
val ksAnnotated: KSAnnotation
) : InternalXAnnotation() {
- val ksType: KSType by lazy { ksAnnotated.annotationType.resolve() }
+ val ksType: KSType by lazy {
+ ksAnnotated.annotationType.resolve()
+ }
override val name: String
get() = ksAnnotated.shortName.asString()
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspType.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspType.kt
index b0d03e1..d6b75dd 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspType.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspType.kt
@@ -24,6 +24,7 @@
import androidx.room.compiler.processing.tryBox
import androidx.room.compiler.processing.tryUnbox
import com.google.devtools.ksp.KspExperimental
+import com.google.devtools.ksp.symbol.KSAnnotation
import com.google.devtools.ksp.symbol.KSClassDeclaration
import com.google.devtools.ksp.symbol.KSType
import com.google.devtools.ksp.symbol.KSTypeArgument
@@ -46,13 +47,13 @@
* Similarly, we may not be able to get a [KSType] (e.g. if it resolves to error).
*/
internal abstract class KspType(
- val env: KspProcessingEnv,
+ env: KspProcessingEnv,
val ksType: KSType,
/**
* Type resolver to convert KSType into its JVM representation.
*/
protected val jvmTypeResolver: KspJvmTypeResolver?
-) : XType, XEquality {
+) : KspAnnotated(env), XType, XEquality {
override val rawType by lazy {
KspRawType(this)
}
@@ -213,6 +214,8 @@
}
}
+ override fun annotations(): Sequence<KSAnnotation> = ksType.annotations
+
override fun isNone(): Boolean {
// even void is converted to Unit so we don't have none type in KSP
// see: KspTypeTest.noneType
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XAnnotationTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XAnnotationTest.kt
index d96e81d..2b79b8b 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XAnnotationTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XAnnotationTest.kt
@@ -1106,6 +1106,158 @@
}
}
+ @Test
+ fun typeAnnotations() {
+ val kotlinSource = Source.kotlin(
+ "foo.bar.Subject.kt",
+ """
+ package foo.bar
+
+ interface MyInterface
+ open class Base
+
+ @Target(AnnotationTarget.TYPE)
+ annotation class A
+
+ class Subject(i: @A MyInterface) : @A Base(), @A MyInterface {
+ val p: @A MyInterface = TODO()
+ fun f(a: @A MyInterface): @A MyInterface = TODO()
+ }
+ """.trimIndent()
+ )
+ val javaSource = Source.java(
+ "foo.bar.Subject.java",
+ """
+ package foo.bar;
+ import java.lang.annotation.ElementType;
+ import java.lang.annotation.Target;
+ import java.lang.annotation.Repeatable;
+
+ interface MyInterface {}
+ class Base {}
+
+ @Target(ElementType.TYPE_USE)
+ @interface A {}
+
+ class Subject extends @A Base implements @A MyInterface {
+ Subject(@A MyInterface i) {}
+ @A MyInterface p;
+ @A MyInterface f(@A MyInterface a) {
+ throw new RuntimeException();
+ }
+ }
+ """.trimIndent()
+ )
+
+ listOf(javaSource, kotlinSource).forEach { source ->
+ runTest(
+ sources = listOf(source)
+ ) { invocation ->
+ // We can't see type annotations from precompiled Java classes. Skipping it for now:
+ // https://github.com/google/ksp/issues/1296
+ if (source == javaSource && preCompiled) {
+ return@runTest
+ }
+ val subject = invocation.processingEnv.requireTypeElement("foo.bar.Subject")
+ // There's an issue in KSP that prevents us from getting type annotations in
+ // places other than supertypes: https://github.com/google/ksp/issues/1325
+ val annotations = if (invocation.isKsp) {
+ listOf(
+ subject.superClass!!.getAllAnnotations().first(),
+ subject.superInterfaces.first().getAllAnnotations().first(),
+ )
+ } else {
+ listOf(
+ subject.superClass!!.getAllAnnotations().first(),
+ subject.superInterfaces.first().getAllAnnotations().first(),
+ subject.getDeclaredField("p").type.getAllAnnotations().first(),
+ subject.getConstructors().first().parameters.first().type
+ .getAllAnnotations().first(),
+ subject.getMethodByJvmName("f").returnType
+ .getAllAnnotations().first(),
+ subject.getMethodByJvmName("f").parameters.first().type
+ .getAllAnnotations().first()
+ )
+ }
+ annotations.forEach { annotation ->
+ assertThat(annotation.qualifiedName).isEqualTo("foo.bar.A")
+ }
+ assertThat(subject.superClass!!.hasAnnotationWithPackage("foo.bar")).isTrue()
+ subject.superInterfaces.forEach {
+ assertThat(it.hasAnnotationWithPackage("foo.bar")).isTrue()
+ }
+ }
+ }
+ }
+
+ @Test
+ fun repeatedTypeAnnotations() {
+ val kotlinSource = Source.kotlin(
+ "foo.bar.Subject.kt",
+ """
+ package foo.bar
+
+ @Target(AnnotationTarget.TYPE)
+ @Repeatable
+ annotation class A(val value: Int)
+
+ open class Base
+
+ class Subject : @A(0) @A(1) Base()
+ """.trimIndent()
+ )
+ val javaSource = Source.java(
+ "foo.bar.Subject.java",
+ """
+ package foo.bar;
+ import java.lang.annotation.ElementType;
+ import java.lang.annotation.Target;
+ import java.lang.annotation.Repeatable;
+
+ class Base {}
+
+ @Repeatable(AContainer.class)
+ @Target(ElementType.TYPE_USE)
+ @interface A {
+ int value();
+ }
+
+ @Target(ElementType.TYPE_USE)
+ @interface AContainer {
+ A[] value();
+ }
+
+ class Subject extends @A(0) @A(1) Base {}
+ """.trimIndent()
+ )
+
+ listOf(javaSource, kotlinSource).forEach { source ->
+ runTest(
+ sources = listOf(source)
+ ) { invocation ->
+ // We can't see type annotations from precompiled Java classes. Skipping it for now:
+ // https://github.com/google/ksp/issues/1296
+ if (source == javaSource && preCompiled) {
+ return@runTest
+ }
+ val subject = invocation.processingEnv.requireTypeElement("foo.bar.Subject")
+ val base = subject.superClass!!
+ assertThat(base.getAllAnnotations()[0].name)
+ .isEqualTo("A")
+ assertThat(base.getAllAnnotations()[0].qualifiedName)
+ .isEqualTo("foo.bar.A")
+ assertThat(base.getAllAnnotations()[0].annotationValues.first().asInt())
+ .isEqualTo(0)
+ assertThat(base.getAllAnnotations()[1].name)
+ .isEqualTo("A")
+ assertThat(base.getAllAnnotations()[1].qualifiedName)
+ .isEqualTo("foo.bar.A")
+ assertThat(base.getAllAnnotations()[1].annotationValues.first().asInt())
+ .isEqualTo(1)
+ }
+ }
+ }
+
// helper function to read what we need
private fun XAnnotated.getSuppressValues(): List<String>? {
return this.findAnnotation<TestSuppressWarnings>()
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XAnnotationValueTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XAnnotationValueTest.kt
index 3d05e0b..0ca863f 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XAnnotationValueTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XAnnotationValueTest.kt
@@ -57,6 +57,7 @@
class XAnnotationValueTest(
private val isPreCompiled: Boolean,
private val sourceKind: SourceKind,
+ private val isTypeAnnotation: Boolean,
) {
private fun runTest(
javaSource: Source.JavaSource,
@@ -104,34 +105,52 @@
"test.MyClass",
"""
package test;
+ import java.lang.annotation.ElementType;
+ import java.lang.annotation.Target;
+ @Target({ElementType.TYPE, ElementType.TYPE_USE})
@interface MyAnnotation {
boolean booleanParam();
boolean[] booleanArrayParam();
boolean[] booleanVarArgsParam(); // There's no varargs in java so use array
}
+ interface MyInterface {}
@MyAnnotation(
booleanParam = true,
booleanArrayParam = {true, false, true},
booleanVarArgsParam = {false, true, false}
)
- class MyClass {}
+ class MyClass implements
+ @MyAnnotation(
+ booleanParam = true,
+ booleanArrayParam = {true, false, true},
+ booleanVarArgsParam = {false, true, false}
+ )
+ MyInterface {}
""".trimIndent()
) as Source.JavaSource,
kotlinSource = Source.kotlin(
"test.MyClass.kt",
"""
package test
+ @Target(AnnotationTarget.CLASS, AnnotationTarget.TYPE)
annotation class MyAnnotation(
val booleanParam: Boolean,
val booleanArrayParam: BooleanArray,
vararg val booleanVarArgsParam: Boolean,
)
+ interface MyInterface
@MyAnnotation(
booleanParam = true,
booleanArrayParam = [true, false, true],
booleanVarArgsParam = [false, true, false],
)
- class MyClass
+ class MyClass :
+ @MyAnnotation(
+ booleanParam = true,
+ booleanArrayParam = [true, false, true],
+ booleanVarArgsParam = [false, true, false],
+ )
+ MyInterface
""".trimIndent()
) as Source.KotlinSource
) { invocation ->
@@ -161,11 +180,7 @@
checkSingleValue(value, expectedValues[i])
}
}
-
- val annotation = invocation.processingEnv.requireTypeElement("test.MyClass")
- .getAllAnnotations()
- .single { it.qualifiedName == "test.MyAnnotation" }
-
+ val annotation = getAnnotation(invocation)
// Compare the AnnotationSpec string ignoring whitespace
assertThat(annotation.toAnnotationSpec().toString().removeWhiteSpace())
.isEqualTo("""
@@ -194,34 +209,52 @@
"test.MyClass",
"""
package test;
+ import java.lang.annotation.ElementType;
+ import java.lang.annotation.Target;
+ @Target({ElementType.TYPE, ElementType.TYPE_USE})
@interface MyAnnotation {
int intParam();
int[] intArrayParam();
int[] intVarArgsParam(); // There's no varargs in java so use array
}
+ interface MyInterface {}
@MyAnnotation(
intParam = (short) 1,
intArrayParam = {(byte) 3, (short) 5, 7},
intVarArgsParam = {(byte) 9, (short) 11, 13}
)
- class MyClass {}
+ class MyClass implements
+ @MyAnnotation(
+ intParam = (short) 1,
+ intArrayParam = {(byte) 3, (short) 5, 7},
+ intVarArgsParam = {(byte) 9, (short) 11, 13}
+ )
+ MyInterface {}
""".trimIndent()
) as Source.JavaSource,
kotlinSource = Source.kotlin(
"test.MyClass.kt",
"""
package test
+ @Target(AnnotationTarget.CLASS, AnnotationTarget.TYPE)
annotation class MyAnnotation(
val intParam: Int,
val intArrayParam: IntArray,
vararg val intVarArgsParam: Int,
)
+ interface MyInterface
@MyAnnotation(
intParam = 1,
intArrayParam = [3, 5, 7],
intVarArgsParam = [9, 11, 13],
)
- class MyClass
+ class MyClass :
+ @MyAnnotation(
+ intParam = 1,
+ intArrayParam = [3, 5, 7],
+ intVarArgsParam = [9, 11, 13],
+ )
+ MyInterface
""".trimIndent()
) as Source.KotlinSource
) { invocation ->
@@ -252,10 +285,7 @@
}
}
- val annotation = invocation.processingEnv.requireTypeElement("test.MyClass")
- .getAllAnnotations()
- .single { it.qualifiedName == "test.MyAnnotation" }
-
+ val annotation = getAnnotation(invocation)
// Compare the AnnotationSpec string ignoring whitespace
assertThat(annotation.toAnnotationSpec().toString().removeWhiteSpace())
.isEqualTo("""
@@ -284,34 +314,52 @@
"test.MyClass",
"""
package test;
+ import java.lang.annotation.ElementType;
+ import java.lang.annotation.Target;
+ @Target({ElementType.TYPE, ElementType.TYPE_USE})
@interface MyAnnotation {
short shortParam();
short[] shortArrayParam();
short[] shortVarArgsParam(); // There's no varargs in java so use array
}
+ interface MyInterface {}
@MyAnnotation(
shortParam = (byte) 1,
shortArrayParam = {(byte) 3, (short) 5, 7},
shortVarArgsParam = {(byte) 9, (short) 11, 13}
)
- class MyClass {}
+ class MyClass implements
+ @MyAnnotation(
+ shortParam = (byte) 1,
+ shortArrayParam = {(byte) 3, (short) 5, 7},
+ shortVarArgsParam = {(byte) 9, (short) 11, 13}
+ )
+ MyInterface {}
""".trimIndent()
) as Source.JavaSource,
kotlinSource = Source.kotlin(
"test.MyClass.kt",
"""
package test
+ @Target(AnnotationTarget.CLASS, AnnotationTarget.TYPE)
annotation class MyAnnotation(
val shortParam: Short,
val shortArrayParam: ShortArray,
vararg val shortVarArgsParam: Short,
)
+ interface MyInterface
@MyAnnotation(
shortParam = 1,
shortArrayParam = [3, 5, 7],
shortVarArgsParam = [9, 11, 13],
)
- class MyClass
+ class MyClass :
+ @MyAnnotation(
+ shortParam = 1,
+ shortArrayParam = [3, 5, 7],
+ shortVarArgsParam = [9, 11, 13],
+ )
+ MyInterface
""".trimIndent()
) as Source.KotlinSource
) { invocation ->
@@ -342,10 +390,7 @@
}
}
- val annotation = invocation.processingEnv.requireTypeElement("test.MyClass")
- .getAllAnnotations()
- .single { it.qualifiedName == "test.MyAnnotation" }
-
+ val annotation = getAnnotation(invocation)
// Compare the AnnotationSpec string ignoring whitespace
assertThat(annotation.toAnnotationSpec().toString().removeWhiteSpace())
.isEqualTo("""
@@ -374,34 +419,52 @@
"test.MyClass",
"""
package test;
+ import java.lang.annotation.ElementType;
+ import java.lang.annotation.Target;
+ @Target({ElementType.TYPE, ElementType.TYPE_USE})
@interface MyAnnotation {
long longParam();
long[] longArrayParam();
long[] longVarArgsParam(); // There's no varargs in java so use array
}
+ interface MyInterface {}
@MyAnnotation(
longParam = (byte) 1,
longArrayParam = {(short) 3, (int) 5, 7L},
longVarArgsParam = {(short) 9, (int) 11, 13L}
)
- class MyClass {}
+ class MyClass implements
+ @MyAnnotation(
+ longParam = (byte) 1,
+ longArrayParam = {(short) 3, (int) 5, 7L},
+ longVarArgsParam = {(short) 9, (int) 11, 13L}
+ )
+ MyInterface {}
""".trimIndent()
) as Source.JavaSource,
kotlinSource = Source.kotlin(
"test.MyClass.kt",
"""
package test
+ @Target(AnnotationTarget.CLASS, AnnotationTarget.TYPE)
annotation class MyAnnotation(
val longParam: Long,
val longArrayParam: LongArray,
vararg val longVarArgsParam: Long,
)
+ interface MyInterface
@MyAnnotation(
longParam = 1L,
longArrayParam = [3L, 5L, 7L],
longVarArgsParam = [9L, 11L, 13L],
)
- class MyClass
+ class MyClass :
+ @MyAnnotation(
+ longParam = 1L,
+ longArrayParam = [3L, 5L, 7L],
+ longVarArgsParam = [9L, 11L, 13L],
+ )
+ MyInterface
""".trimIndent()
) as Source.KotlinSource
) { invocation ->
@@ -432,10 +495,7 @@
}
}
- val annotation = invocation.processingEnv.requireTypeElement("test.MyClass")
- .getAllAnnotations()
- .single { it.qualifiedName == "test.MyAnnotation" }
-
+ val annotation = getAnnotation(invocation)
// Compare the AnnotationSpec string ignoring whitespace
assertThat(annotation.toAnnotationSpec().toString().removeWhiteSpace())
.isEqualTo("""
@@ -464,34 +524,52 @@
"test.MyClass",
"""
package test;
+ import java.lang.annotation.ElementType;
+ import java.lang.annotation.Target;
+ @Target({ElementType.TYPE, ElementType.TYPE_USE})
@interface MyAnnotation {
float floatParam();
float[] floatArrayParam();
float[] floatVarArgsParam(); // There's no varargs in java so use array
}
+ interface MyInterface {}
@MyAnnotation(
floatParam = (byte) 1,
floatArrayParam = {(short) 3, 5.1F, 7.1F},
floatVarArgsParam = {9, 11.1F, 13.1F}
)
- class MyClass {}
+ class MyClass implements
+ @MyAnnotation(
+ floatParam = (byte) 1,
+ floatArrayParam = {(short) 3, 5.1F, 7.1F},
+ floatVarArgsParam = {9, 11.1F, 13.1F}
+ )
+ MyInterface {}
""".trimIndent()
) as Source.JavaSource,
kotlinSource = Source.kotlin(
"test.MyClass.kt",
"""
package test
+ @Target(AnnotationTarget.CLASS, AnnotationTarget.TYPE)
annotation class MyAnnotation(
val floatParam: Float,
val floatArrayParam: FloatArray,
vararg val floatVarArgsParam: Float,
)
+ interface MyInterface
@MyAnnotation(
floatParam = 1F,
floatArrayParam = [3F, 5.1F, 7.1F],
floatVarArgsParam = [9F, 11.1F, 13.1F],
)
- class MyClass
+ class MyClass :
+ @MyAnnotation(
+ floatParam = 1F,
+ floatArrayParam = [3F, 5.1F, 7.1F],
+ floatVarArgsParam = [9F, 11.1F, 13.1F],
+ )
+ MyInterface
""".trimIndent()
) as Source.KotlinSource
) { invocation ->
@@ -522,10 +600,7 @@
}
}
- val annotation = invocation.processingEnv.requireTypeElement("test.MyClass")
- .getAllAnnotations()
- .single { it.qualifiedName == "test.MyAnnotation" }
-
+ val annotation = getAnnotation(invocation)
// Compare the AnnotationSpec string ignoring whitespace
assertThat(annotation.toAnnotationSpec().toString().removeWhiteSpace())
.isEqualTo("""
@@ -554,34 +629,52 @@
"test.MyClass",
"""
package test;
+ import java.lang.annotation.ElementType;
+ import java.lang.annotation.Target;
+ @Target({ElementType.TYPE, ElementType.TYPE_USE})
@interface MyAnnotation {
double doubleParam();
double[] doubleArrayParam();
double[] doubleVarArgsParam(); // There's no varargs in java so use array
}
+ interface MyInterface {}
@MyAnnotation(
doubleParam = (byte) 1,
doubleArrayParam = {(short) 3, 5.1F, 7.1},
doubleVarArgsParam = {9, 11.1F, 13.1}
)
- class MyClass {}
+ class MyClass implements
+ @MyAnnotation(
+ doubleParam = (byte) 1,
+ doubleArrayParam = {(short) 3, 5.1F, 7.1},
+ doubleVarArgsParam = {9, 11.1F, 13.1}
+ )
+ MyInterface {}
""".trimIndent()
) as Source.JavaSource,
kotlinSource = Source.kotlin(
"test.MyClass.kt",
"""
package test
+ @Target(AnnotationTarget.CLASS, AnnotationTarget.TYPE)
annotation class MyAnnotation(
val doubleParam: Double,
val doubleArrayParam: DoubleArray,
vararg val doubleVarArgsParam: Double,
)
+ interface MyInterface
@MyAnnotation(
doubleParam = 1.0,
doubleArrayParam = [3.0, 5.1, 7.1],
doubleVarArgsParam = [9.0, 11.1, 13.1],
)
- class MyClass
+ class MyClass :
+ @MyAnnotation(
+ doubleParam = 1.0,
+ doubleArrayParam = [3.0, 5.1, 7.1],
+ doubleVarArgsParam = [9.0, 11.1, 13.1],
+ )
+ MyInterface
""".trimIndent()
) as Source.KotlinSource
) { invocation ->
@@ -612,9 +705,7 @@
}
}
- val annotation = invocation.processingEnv.requireTypeElement("test.MyClass")
- .getAllAnnotations()
- .single { it.qualifiedName == "test.MyAnnotation" }
+ val annotation = getAnnotation(invocation)
annotation.getAnnotationValue("doubleParam").value
annotation.getAnnotationValue("doubleArrayParam").value
annotation.getAnnotationValue("doubleVarArgsParam").value
@@ -676,34 +767,52 @@
"test.MyClass",
"""
package test;
+ import java.lang.annotation.ElementType;
+ import java.lang.annotation.Target;
+ @Target({ElementType.TYPE, ElementType.TYPE_USE})
@interface MyAnnotation {
byte byteParam();
byte[] byteArrayParam();
byte[] byteVarArgsParam(); // There's no varargs in java so use array
}
+ interface MyInterface {}
@MyAnnotation(
byteParam = (byte) 1,
byteArrayParam = {(byte) 3, (byte) 5, (byte) 7},
byteVarArgsParam = {(byte) 9, (byte) 11, (byte) 13}
)
- class MyClass {}
+ class MyClass implements
+ @MyAnnotation(
+ byteParam = (byte) 1,
+ byteArrayParam = {(byte) 3, (byte) 5, (byte) 7},
+ byteVarArgsParam = {(byte) 9, (byte) 11, (byte) 13}
+ )
+ MyInterface {}
""".trimIndent()
) as Source.JavaSource,
kotlinSource = Source.kotlin(
"test.MyClass.kt",
"""
package test
+ @Target(AnnotationTarget.CLASS, AnnotationTarget.TYPE)
annotation class MyAnnotation(
val byteParam: Byte,
val byteArrayParam: ByteArray,
vararg val byteVarArgsParam: Byte,
)
+ interface MyInterface
@MyAnnotation(
byteParam = 1,
byteArrayParam = [3, 5, 7],
byteVarArgsParam = [9, 11, 13],
)
- class MyClass
+ class MyClass :
+ @MyAnnotation(
+ byteParam = 1,
+ byteArrayParam = [3, 5, 7],
+ byteVarArgsParam = [9, 11, 13],
+ )
+ MyInterface
""".trimIndent()
) as Source.KotlinSource
) { invocation ->
@@ -734,10 +843,7 @@
}
}
- val annotation = invocation.processingEnv.requireTypeElement("test.MyClass")
- .getAllAnnotations()
- .single { it.qualifiedName == "test.MyAnnotation" }
-
+ val annotation = getAnnotation(invocation)
// Compare the AnnotationSpec string ignoring whitespace
assertThat(annotation.toAnnotationSpec().toString().removeWhiteSpace())
.isEqualTo("""
@@ -766,34 +872,52 @@
"test.MyClass",
"""
package test;
+ import java.lang.annotation.ElementType;
+ import java.lang.annotation.Target;
+ @Target({ElementType.TYPE, ElementType.TYPE_USE})
@interface MyAnnotation {
char charParam();
char[] charArrayParam();
char[] charVarArgsParam(); // There's no varargs in java so use array
}
+ interface MyInterface {}
@MyAnnotation(
charParam = '1',
charArrayParam = {'2', '3', '4'},
charVarArgsParam = {'5', '6', '7'}
)
- class MyClass {}
+ class MyClass implements
+ @MyAnnotation(
+ charParam = '1',
+ charArrayParam = {'2', '3', '4'},
+ charVarArgsParam = {'5', '6', '7'}
+ )
+ MyInterface {}
""".trimIndent()
) as Source.JavaSource,
kotlinSource = Source.kotlin(
"test.MyClass.kt",
"""
package test
+ @Target(AnnotationTarget.CLASS, AnnotationTarget.TYPE)
annotation class MyAnnotation(
val charParam: Char,
val charArrayParam: CharArray,
vararg val charVarArgsParam: Char,
)
+ interface MyInterface
@MyAnnotation(
charParam = '1',
charArrayParam = ['2', '3', '4'],
charVarArgsParam = ['5', '6', '7'],
)
- class MyClass
+ class MyClass :
+ @MyAnnotation(
+ charParam = '1',
+ charArrayParam = ['2', '3', '4'],
+ charVarArgsParam = ['5', '6', '7'],
+ )
+ MyInterface
""".trimIndent()
) as Source.KotlinSource
) { invocation ->
@@ -824,10 +948,7 @@
}
}
- val annotation = invocation.processingEnv.requireTypeElement("test.MyClass")
- .getAllAnnotations()
- .single { it.qualifiedName == "test.MyAnnotation" }
-
+ val annotation = getAnnotation(invocation)
// Compare the AnnotationSpec string ignoring whitespace
assertThat(annotation.toAnnotationSpec().toString().removeWhiteSpace())
.isEqualTo("""
@@ -856,34 +977,52 @@
"test.MyClass",
"""
package test;
+ import java.lang.annotation.ElementType;
+ import java.lang.annotation.Target;
+ @Target({ElementType.TYPE, ElementType.TYPE_USE})
@interface MyAnnotation {
String stringParam();
String[] stringArrayParam();
String[] stringVarArgsParam(); // There's no varargs in java so use array
}
+ interface MyInterface {}
@MyAnnotation(
stringParam = "1",
stringArrayParam = {"3", "5", "7"},
stringVarArgsParam = {"9", "11", "13"}
)
- class MyClass {}
+ class MyClass implements
+ @MyAnnotation(
+ stringParam = "1",
+ stringArrayParam = {"3", "5", "7"},
+ stringVarArgsParam = {"9", "11", "13"}
+ )
+ MyInterface {}
""".trimIndent()
) as Source.JavaSource,
kotlinSource = Source.kotlin(
"test.MyClass.kt",
"""
package test
+ @Target(AnnotationTarget.CLASS, AnnotationTarget.TYPE)
annotation class MyAnnotation(
val stringParam: String,
val stringArrayParam: Array<String>,
vararg val stringVarArgsParam: String,
)
+ interface MyInterface
@MyAnnotation(
stringParam = "1",
stringArrayParam = ["3", "5", "7"],
stringVarArgsParam = ["9", "11", "13"],
)
- class MyClass
+ class MyClass :
+ @MyAnnotation(
+ stringParam = "1",
+ stringArrayParam = ["3", "5", "7"],
+ stringVarArgsParam = ["9", "11", "13"],
+ )
+ MyInterface
""".trimIndent()
) as Source.KotlinSource
) { invocation ->
@@ -925,10 +1064,7 @@
}
}
- val annotation = invocation.processingEnv.requireTypeElement("test.MyClass")
- .getAllAnnotations()
- .single { it.qualifiedName == "test.MyAnnotation" }
-
+ val annotation = getAnnotation(invocation)
// Compare the AnnotationSpec string ignoring whitespace
assertThat(annotation.toAnnotationSpec().toString().removeWhiteSpace())
.isEqualTo("""
@@ -957,18 +1093,28 @@
"test.MyClass",
"""
package test;
+ import java.lang.annotation.ElementType;
+ import java.lang.annotation.Target;
enum MyEnum {V1, V2, V3}
+ @Target({ElementType.TYPE, ElementType.TYPE_USE})
@interface MyAnnotation {
MyEnum enumParam();
MyEnum[] enumArrayParam();
MyEnum[] enumVarArgsParam(); // There's no varargs in java so use array
}
+ interface MyInterface {}
@MyAnnotation(
enumParam = MyEnum.V1,
enumArrayParam = {MyEnum.V1, MyEnum.V2, MyEnum.V3},
enumVarArgsParam = {MyEnum.V3, MyEnum.V2, MyEnum.V1}
)
- class MyClass {}
+ class MyClass implements
+ @MyAnnotation(
+ enumParam = MyEnum.V1,
+ enumArrayParam = {MyEnum.V1, MyEnum.V2, MyEnum.V3},
+ enumVarArgsParam = {MyEnum.V3, MyEnum.V2, MyEnum.V1}
+ )
+ MyInterface {}
""".trimIndent()
) as Source.JavaSource,
kotlinSource = Source.kotlin(
@@ -976,17 +1122,25 @@
"""
package test
enum class MyEnum {V1, V2, V3}
+ @Target(AnnotationTarget.CLASS, AnnotationTarget.TYPE)
annotation class MyAnnotation(
val enumParam: MyEnum,
val enumArrayParam: Array<MyEnum>,
vararg val enumVarArgsParam: MyEnum,
)
+ interface MyInterface
@MyAnnotation(
enumParam = MyEnum.V1,
enumArrayParam = [MyEnum.V1, MyEnum.V2, MyEnum.V3],
enumVarArgsParam = [MyEnum.V3, MyEnum.V2, MyEnum.V1],
)
- class MyClass
+ class MyClass :
+ @MyAnnotation(
+ enumParam = MyEnum.V1,
+ enumArrayParam = [MyEnum.V1, MyEnum.V2, MyEnum.V3],
+ enumVarArgsParam = [MyEnum.V3, MyEnum.V2, MyEnum.V1],
+ )
+ MyInterface
""".trimIndent()
) as Source.KotlinSource
) { invocation ->
@@ -1031,10 +1185,7 @@
}
}
- val annotation = invocation.processingEnv.requireTypeElement("test.MyClass")
- .getAllAnnotations()
- .single { it.qualifiedName == "test.MyAnnotation" }
-
+ val annotation = getAnnotation(invocation)
assertThat(annotation.toAnnotationSpec().toString().removeWhiteSpace())
.isEqualTo("""
@test.MyAnnotation(
@@ -1062,20 +1213,30 @@
"test.MyClass",
"""
package test;
+ import java.lang.annotation.ElementType;
+ import java.lang.annotation.Target;
class C1 {}
class C2 {}
class C3 {}
+ @Target({ElementType.TYPE, ElementType.TYPE_USE})
@interface MyAnnotation {
Class<?> typeParam();
Class<?>[] typeArrayParam();
Class<?>[] typeVarArgsParam(); // There's no varargs in java so use array
}
+ interface MyInterface {}
@MyAnnotation(
typeParam = C1.class,
typeArrayParam = {C1.class, C2.class, C3.class},
typeVarArgsParam = {C3.class, C2.class, C1.class}
)
- class MyClass {}
+ class MyClass implements
+ @MyAnnotation(
+ typeParam = C1.class,
+ typeArrayParam = {C1.class, C2.class, C3.class},
+ typeVarArgsParam = {C3.class, C2.class, C1.class}
+ )
+ MyInterface {}
""".trimIndent()
) as Source.JavaSource,
kotlinSource = Source.kotlin(
@@ -1085,17 +1246,25 @@
class C1
class C2
class C3
+ @Target(AnnotationTarget.CLASS, AnnotationTarget.TYPE)
annotation class MyAnnotation(
val typeParam: kotlin.reflect.KClass<*>,
val typeArrayParam: Array<kotlin.reflect.KClass<*>>,
vararg val typeVarArgsParam: kotlin.reflect.KClass<*>,
)
+ interface MyInterface
@MyAnnotation(
typeParam = C1::class,
typeArrayParam = [C1::class, C2::class, C3::class],
typeVarArgsParam = [C3::class, C2::class, C1::class],
)
- class MyClass
+ class MyClass :
+ @MyAnnotation(
+ typeParam = C1::class,
+ typeArrayParam = [C1::class, C2::class, C3::class],
+ typeVarArgsParam = [C3::class, C2::class, C1::class],
+ )
+ MyInterface
""".trimIndent()
) as Source.KotlinSource
) { invocation ->
@@ -1154,10 +1323,7 @@
}
}
- val annotation = invocation.processingEnv.requireTypeElement("test.MyClass")
- .getAllAnnotations()
- .single { it.qualifiedName == "test.MyAnnotation" }
-
+ val annotation = getAnnotation(invocation)
// Compare the AnnotationSpec string ignoring whitespace
assertThat(annotation.toAnnotationSpec().toString().removeWhiteSpace())
.isEqualTo("""
@@ -1186,20 +1352,30 @@
"test.MyClass",
"""
package test;
+ import java.lang.annotation.ElementType;
+ import java.lang.annotation.Target;
@interface A {
String value();
}
+ @Target({ElementType.TYPE, ElementType.TYPE_USE})
@interface MyAnnotation {
A annotationParam();
A[] annotationArrayParam();
A[] annotationVarArgsParam(); // There's no varargs in java so use array
}
+ interface MyInterface {}
@MyAnnotation(
annotationParam = @A("1"),
annotationArrayParam = {@A("3"), @A("5"), @A("7")},
annotationVarArgsParam = {@A("9"), @A("11"), @A("13")}
)
- class MyClass {}
+ class MyClass implements
+ @MyAnnotation(
+ annotationParam = @A("1"),
+ annotationArrayParam = {@A("3"), @A("5"), @A("7")},
+ annotationVarArgsParam = {@A("9"), @A("11"), @A("13")}
+ )
+ MyInterface {}
""".trimIndent()
) as Source.JavaSource,
kotlinSource = Source.kotlin(
@@ -1207,17 +1383,25 @@
"""
package test
annotation class A(val value: String)
+ @Target(AnnotationTarget.CLASS, AnnotationTarget.TYPE)
annotation class MyAnnotation(
val annotationParam: A,
val annotationArrayParam: Array<A>,
vararg val annotationVarArgsParam: A,
)
+ interface MyInterface
@MyAnnotation(
annotationParam = A("1"),
annotationArrayParam = [A("3"), A("5"), A("7")],
annotationVarArgsParam = [A("9"), A("11"), A("13")],
)
- class MyClass
+ class MyClass :
+ @MyAnnotation(
+ annotationParam = A("1"),
+ annotationArrayParam = [A("3"), A("5"), A("7")],
+ annotationVarArgsParam = [A("9"), A("11"), A("13")],
+ )
+ MyInterface
""".trimIndent()
) as Source.KotlinSource
) { invocation ->
@@ -1263,10 +1447,7 @@
}
}
- val annotation = invocation.processingEnv.requireTypeElement("test.MyClass")
- .getAllAnnotations()
- .single { it.qualifiedName == "test.MyAnnotation" }
-
+ val annotation = getAnnotation(invocation)
// Compare the AnnotationSpec string ignoring whitespace
assertThat(annotation.toAnnotationSpec().toString().removeWhiteSpace())
.isEqualTo("""
@@ -1292,17 +1473,36 @@
return this.replace("\\s+".toRegex(), "")
}
+ private fun getAnnotation(invocation: XTestInvocation): XAnnotation {
+ val typeElement = invocation.processingEnv.requireTypeElement("test.MyClass")
+ return if (isTypeAnnotation) {
+ typeElement.superInterfaces.first().getAllAnnotations()
+ .single { it.qualifiedName == "test.MyAnnotation" }
+ } else {
+ typeElement.getAllAnnotations().single { it.qualifiedName == "test.MyAnnotation" }
+ }
+ }
+
enum class SourceKind { JAVA, KOTLIN }
companion object {
@JvmStatic
- @Parameterized.Parameters(name = "isPreCompiled_{0}_sourceKind_{1}")
+ @Parameterized.Parameters(name = "isPreCompiled_{0}_sourceKind_{1}_isTypeAnnotation_{2}")
fun params(): List<Array<Any>> {
val isPreCompiledValues = arrayOf(false, true)
val sourceKindValues = arrayOf(SourceKind.JAVA, SourceKind.KOTLIN)
+ val isTypeAnnotation = arrayOf(false, true)
return isPreCompiledValues.flatMap { isPreCompiled ->
- sourceKindValues.map { sourceKind ->
- arrayOf(isPreCompiled, sourceKind)
+ sourceKindValues.flatMap { sourceKind ->
+ isTypeAnnotation.mapNotNull { isTypeAnnotation ->
+ // We can't see type annotations from precompiled Java classes. Skipping it
+ // for now: https://github.com/google/ksp/issues/1296
+ if (isPreCompiled && sourceKind == SourceKind.JAVA && isTypeAnnotation) {
+ null
+ } else {
+ arrayOf(isPreCompiled, sourceKind, isTypeAnnotation)
+ }
+ }
}
}
}
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeElementTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeElementTest.kt
index e44db5b..9036fa2 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeElementTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeElementTest.kt
@@ -19,6 +19,7 @@
import androidx.room.compiler.codegen.XClassName
import androidx.room.compiler.codegen.XTypeName
import androidx.room.compiler.codegen.asClassName
+import androidx.room.compiler.processing.javac.JavacType
import androidx.room.compiler.processing.ksp.jvmDescriptor
import androidx.room.compiler.processing.util.Source
import androidx.room.compiler.processing.util.XTestInvocation
@@ -255,6 +256,9 @@
assertThat(superInterface.typeArguments[0].asTypeName().kotlin)
.isEqualTo(KClassName("kotlin", "String"))
}
+ if (! invocation.isKsp) {
+ assertThat((superInterface as JavacType).kotlinType).isNotNull()
+ }
}
}
}
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeTest.kt
index 835cdfd..724c037 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeTest.kt
@@ -24,6 +24,7 @@
import androidx.room.compiler.processing.util.XTestInvocation
import androidx.room.compiler.processing.util.asJClassName
import androidx.room.compiler.processing.util.asKClassName
+import androidx.room.compiler.processing.util.compileFiles
import androidx.room.compiler.processing.util.dumpToString
import androidx.room.compiler.processing.util.getDeclaredField
import androidx.room.compiler.processing.util.getDeclaredMethodByJvmName
@@ -1525,4 +1526,115 @@
it.assertCompilationResult { hasErrorContaining("Unresolved reference: MissingType") }
}
}
+
+ @Test
+ fun hasAnnotationWithPackage() {
+ val kotlinSrc = Source.kotlin(
+ "KotlinClass.kt",
+ """
+ package foo.bar
+ interface KotlinInterface
+ open class KotlinBase
+ @Target(AnnotationTarget.TYPE)
+ annotation class KotlinAnnotation {
+ @Target(AnnotationTarget.TYPE)
+ annotation class KotlinNestedAnnotation
+ }
+ class KotlinClass : @KotlinAnnotation.KotlinNestedAnnotation KotlinBase(),
+ @KotlinAnnotation KotlinInterface {
+ inner class KotlinInner : @KotlinAnnotation KotlinInterface
+ class KotlinNested : @KotlinAnnotation KotlinInterface
+ }
+ """.trimIndent()
+ )
+ // KSP can't read nested annotations in Java sources if the filename does not match
+ // the outer class.
+ val javaAnnotationSource = Source.java(
+ "foo.bar.JavaAnnotation",
+ """
+ package foo.bar;
+ import java.lang.annotation.ElementType;
+ import java.lang.annotation.Target;
+ @Target(ElementType.TYPE_USE)
+ @interface JavaAnnotation {
+ @Target(ElementType.TYPE_USE)
+ @interface JavaNestedAnnotation {}
+ }
+ """.trimIndent()
+ )
+ val javaSrc = Source.java(
+ "foo.bar.JavaClass",
+ """
+ package foo.bar;
+ interface JavaInterface {}
+ class JavaBase {}
+ class JavaClass extends @JavaAnnotation.JavaNestedAnnotation JavaBase
+ implements @JavaAnnotation JavaInterface {}
+ """.trimIndent()
+ )
+ fun checkKotlin(invocation: XTestInvocation) {
+ val kotlinTypeElement = invocation.processingEnv.requireTypeElement(
+ "foo.bar.KotlinClass")
+ kotlinTypeElement.superInterfaces.single().let {
+ assertThat(it.getAllAnnotations().single().typeElement.packageName)
+ .isEqualTo("foo.bar")
+ assertThat(it.getAllAnnotations().single().typeElement.qualifiedName)
+ .isEqualTo("foo.bar.KotlinAnnotation")
+
+ assertThat(it.hasAnnotationWithPackage("foo.bar.KotlinAnnotation")).isFalse()
+ assertThat(it.hasAnnotationWithPackage("foo.bar")).isTrue()
+ assertThat(it.hasAnnotationWithPackage("foo")).isFalse()
+ }
+ kotlinTypeElement.superClass!!.let {
+ assertThat(it.getAllAnnotations().single().typeElement.packageName)
+ .isEqualTo("foo.bar")
+ assertThat(it.getAllAnnotations().single().typeElement.qualifiedName)
+ .isEqualTo("foo.bar.KotlinAnnotation.KotlinNestedAnnotation")
+
+ assertThat(it.getAllAnnotations().single().typeElement.packageName)
+ .isEqualTo("foo.bar")
+ assertThat(it.getAllAnnotations().single().typeElement.qualifiedName)
+ .isEqualTo("foo.bar.KotlinAnnotation.KotlinNestedAnnotation")
+ }
+ }
+ fun checkJava(invocation: XTestInvocation) {
+ val javaTypeElement = invocation.processingEnv.requireTypeElement(
+ "foo.bar.JavaClass")
+ javaTypeElement.superInterfaces.first().let {
+ assertThat(it.getAllAnnotations().single().typeElement.packageName)
+ .isEqualTo("foo.bar")
+ assertThat(it.getAllAnnotations().single().typeElement.qualifiedName)
+ .isEqualTo("foo.bar.JavaAnnotation")
+
+ assertThat(it.hasAnnotationWithPackage("foo.bar.JavaClass")).isFalse()
+ assertThat(it.hasAnnotationWithPackage("foo.bar")).isTrue()
+ assertThat(it.hasAnnotationWithPackage("foo")).isFalse()
+ }
+ javaTypeElement.superClass!!.let {
+ assertThat(it.getAllAnnotations().single().typeElement.packageName)
+ .isEqualTo("foo.bar")
+ assertThat(it.getAllAnnotations().single().typeElement.qualifiedName)
+ .isEqualTo("foo.bar.JavaAnnotation.JavaNestedAnnotation")
+
+ assertThat(it.hasAnnotationWithPackage("foo.bar.JavaClass")).isFalse()
+ assertThat(it.hasAnnotationWithPackage("foo.bar")).isTrue()
+ assertThat(it.hasAnnotationWithPackage("foo")).isFalse()
+ }
+ }
+ runProcessorTest(
+ sources = listOf(kotlinSrc, javaAnnotationSource, javaSrc),
+ handler = {
+ checkKotlin(it)
+ checkJava(it)
+ }
+ )
+ runProcessorTest(
+ classpath = compileFiles(listOf(kotlinSrc)),
+ handler = {
+ // We can't see type annotations from precompiled Java classes. Skipping it
+ // for now: https://github.com/google/ksp/issues/1296
+ checkKotlin(it)
+ }
+ )
+ }
}