Skip to content

Commit 9728df7

Browse files
committed
Fix various java annotation parsing issues
This commit fixes multiple issues in annotation parsing for java code 1) When getting annotation names, it was always using the index instead of the name. I changed it to default to name and only use index if name is not provided. 2) Array values in annotations were repoted as a list of pairs instead of 1 key and multiple values, fixed. 3) When the annotation value is a type, it was not returning a KSType but instead returning a PsiType, fixed. 4) When the annotation value is another annotation, it was not returning a KSAnnotation, fixed. 5) For expressions resolving to literals, it was returning the PsiLiteral instead of the value of it. 6) TypeReferences (relevant in enum values) were not being resolved. Updated/added tests for all cases above. The enum fix for java source is not great right now because seems like evaluator does not resolve it, hence I implemented a manual resolution that is very similar to the implementation in KSAnnotationImpl.
1 parent 879677f commit 9728df7

File tree

8 files changed

+227
-31
lines changed

8 files changed

+227
-31
lines changed

compiler-plugin/src/main/kotlin/com/google/devtools/ksp/processor/AnnotationArgumentProcessor.kt

+5-3
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,11 @@ class AnnotationArgumentProcessor : AbstractTestProcessor() {
2929
val visitor = ArgumentVisitor()
3030

3131
override fun process(resolver: Resolver) {
32-
val symbol = resolver.getSymbolsWithAnnotation("Bar", true).single()
33-
val annotation = (symbol as KSClassDeclaration).annotations.single()
34-
annotation.arguments.map { it.accept(visitor, Unit) }
32+
resolver.getSymbolsWithAnnotation("Bar", true).forEach {
33+
val annotation = it.annotations.single()
34+
annotation.arguments.map { it.accept(visitor, Unit) }
35+
}
36+
3537
val C = resolver.getClassDeclarationByName("C")!!
3638
C.annotations.first().arguments.map { results.add(it.value.toString()) }
3739
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
* Copyright 2020 Google LLC
3+
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
19+
package com.google.devtools.ksp.processor
20+
21+
import com.google.devtools.ksp.getClassDeclarationByName
22+
import com.google.devtools.ksp.processing.Resolver
23+
import com.google.devtools.ksp.symbol.KSClassDeclaration
24+
25+
class AnnotationArrayValueProcessor : AbstractTestProcessor() {
26+
val result = mutableListOf<String>()
27+
28+
override fun toResult(): List<String> {
29+
return result
30+
}
31+
32+
override fun process(resolver: Resolver) {
33+
val ktClass = resolver.getClassDeclarationByName("KotlinAnnotated")!!
34+
logAnnotations(ktClass)
35+
val javaClass = resolver.getClassDeclarationByName("JavaAnnotated")!!
36+
logAnnotations(javaClass)
37+
}
38+
39+
private fun logAnnotations(classDeclaration: KSClassDeclaration) {
40+
result.add(classDeclaration.qualifiedName!!.asString())
41+
classDeclaration.annotations.forEach { annotation ->
42+
result.add("${annotation.shortName.asString()} ->")
43+
annotation.arguments.forEach {
44+
val value = it.value
45+
val key = it.name?.asString()
46+
if (value is Array<*>) {
47+
result.add("$key = [${value.joinToString(", ")}]")
48+
} else {
49+
result.add("$key = ${it.value}")
50+
}
51+
}
52+
}
53+
}
54+
}

compiler-plugin/src/main/kotlin/com/google/devtools/ksp/processor/AnnotationDefaultValueProcessor.kt

+10-9
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
package com.google.devtools.ksp.processor
2020

2121
import com.google.devtools.ksp.processing.Resolver
22+
import com.google.devtools.ksp.symbol.KSClassDeclaration
2223

2324
class AnnotationDefaultValueProcessor : AbstractTestProcessor() {
2425
val result = mutableListOf<String>()
@@ -29,14 +30,14 @@ class AnnotationDefaultValueProcessor : AbstractTestProcessor() {
2930

3031
override fun process(resolver: Resolver) {
3132
val ktClass = resolver.getClassDeclarationByName(resolver.getKSNameFromString("A"))!!
32-
var ktAnno = ktClass.annotations[0]
33-
var javaAnno = ktClass.annotations[1]
34-
result.add("${ktAnno.shortName.asString()} -> ${ktAnno.arguments.map { "${it.name?.asString()}:${it.value}" }.joinToString(",")}")
35-
result.add("${javaAnno.shortName.asString()} -> ${javaAnno.arguments.map { "${it.name?.asString()}:${it.value}" }.joinToString(",")}")
33+
logAnnotations(ktClass)
3634
val javaClass = resolver.getClassDeclarationByName(resolver.getKSNameFromString("JavaAnnotated"))!!
37-
ktAnno = javaClass.annotations[0]
38-
javaAnno = javaClass.annotations[1]
39-
result.add("${ktAnno.shortName.asString()} -> ${ktAnno.arguments.map { "${it.name?.asString()}:${it.value}" }.joinToString(",")}")
40-
result.add("${javaAnno.shortName.asString()} -> ${javaAnno.arguments.map { "${it.name?.asString()}:${it.value}" }.joinToString(",")}")
35+
logAnnotations(javaClass)
4136
}
42-
}
37+
38+
private fun logAnnotations(classDeclaration: KSClassDeclaration) {
39+
classDeclaration.annotations.forEach { annotation ->
40+
result.add("${annotation.shortName.asString()} -> ${annotation.arguments.map { "${it.name?.asString()}:${it.value}" }.joinToString(",")}")
41+
}
42+
}
43+
}

compiler-plugin/src/main/kotlin/com/google/devtools/ksp/symbol/impl/java/KSAnnotationJavaImpl.kt

+51-15
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,18 @@
1818

1919
package com.google.devtools.ksp.symbol.impl.java
2020

21+
import com.google.devtools.ksp.getClassDeclarationByName
22+
import com.google.devtools.ksp.processing.impl.ResolverImpl
2123
import org.jetbrains.kotlin.descriptors.ClassDescriptor
2224
import com.google.devtools.ksp.symbol.*
2325
import com.google.devtools.ksp.symbol.impl.KSObjectCache
2426
import com.google.devtools.ksp.symbol.impl.binary.getAbsentDefaultArguments
27+
import com.google.devtools.ksp.symbol.impl.kotlin.KSErrorType
2528
import com.google.devtools.ksp.symbol.impl.kotlin.KSNameImpl
2629
import com.google.devtools.ksp.symbol.impl.kotlin.KSTypeImpl
2730
import com.google.devtools.ksp.symbol.impl.toLocation
31+
import com.intellij.lang.jvm.JvmClassKind
2832
import com.intellij.psi.*
29-
import org.jetbrains.kotlin.descriptors.ClassConstructorDescriptor
30-
import java.lang.IllegalStateException
3133

3234
class KSAnnotationJavaImpl private constructor(val psi: PsiAnnotation) : KSAnnotation {
3335
companion object : KSObjectCache<PsiAnnotation, KSAnnotationJavaImpl>() {
@@ -51,14 +53,21 @@ class KSAnnotationJavaImpl private constructor(val psi: PsiAnnotation) : KSAnnot
5153
((annotationType.resolve() as KSTypeImpl).kotlinType.constructor.declarationDescriptor as? ClassDescriptor)
5254
?.constructors?.single()
5355
val presentValueArguments = psi.parameterList.attributes
54-
.flatMapIndexed { index, it ->
55-
if (it.value is PsiArrayInitializerMemberValue) {
56-
(it.value as PsiArrayInitializerMemberValue).initializers.map {
57-
nameValuePairToKSAnnotation(annotationConstructor, index, it)
56+
.mapIndexed { index, it ->
57+
// use the name in the attribute if it is explicitly specified, otherwise, fall back to index.
58+
val name = it.name ?: annotationConstructor?.valueParameters?.getOrNull(index)?.name?.asString()
59+
val value = it.value
60+
val calculatedValue: Any? = if (value is PsiArrayInitializerMemberValue) {
61+
value.initializers.map {
62+
calcValue(it)
5863
}
5964
} else {
60-
listOf(nameValuePairToKSAnnotation(annotationConstructor, index, it.value))
65+
calcValue(it.value)
6166
}
67+
KSValueArgumentJavaImpl.getCached(
68+
name = name?.let(KSNameImpl::getCached),
69+
value = calculatedValue
70+
)
6271
}
6372
val presentValueArgumentNames = presentValueArguments.map { it.name?.asString() ?: "" }
6473
val argumentsFromDefault = annotationConstructor?.let {
@@ -67,15 +76,42 @@ class KSAnnotationJavaImpl private constructor(val psi: PsiAnnotation) : KSAnnot
6776
presentValueArguments.plus(argumentsFromDefault)
6877
}
6978

70-
private fun nameValuePairToKSAnnotation(annotationConstructor: ClassConstructorDescriptor?, nameIndex: Int, value: PsiAnnotationMemberValue?): KSValueArgument {
71-
return KSValueArgumentJavaImpl.getCached(
72-
annotationConstructor?.valueParameters?.get(nameIndex)?.name?.let { KSNameImpl.getCached(it.asString()) },
73-
calcValue(value)
74-
)
75-
}
76-
7779
private fun calcValue(value: PsiAnnotationMemberValue?): Any? {
78-
return value?.let { JavaPsiFacade.getInstance(value.project).constantEvaluationHelper.computeConstantExpression(value) }
80+
if (value is PsiAnnotation) {
81+
return getCached(value)
82+
}
83+
val result = when(value) {
84+
is PsiReference -> value.resolve()?.let { resolved ->
85+
JavaPsiFacade.getInstance(value.project).constantEvaluationHelper.computeConstantExpression(value) ?: resolved
86+
}
87+
else -> value?.let { JavaPsiFacade.getInstance(value.project).constantEvaluationHelper.computeConstantExpression(value) }
88+
}
89+
return when(result) {
90+
is PsiType -> {
91+
ResolverImpl.instance.getClassDeclarationByName(result.canonicalText)?.asStarProjectedType() ?: KSErrorType
92+
}
93+
is PsiLiteralValue -> {
94+
result.value
95+
}
96+
is PsiField -> {
97+
// manually handle enums as constant expression evaluator does not seem to be resolving them.
98+
val containingClass = result.containingClass
99+
if (containingClass?.classKind == JvmClassKind.ENUM) {
100+
// this is an enum entry
101+
containingClass.qualifiedName?.let {
102+
ResolverImpl.instance.getClassDeclarationByName(it)
103+
}?.declarations?.find {
104+
it is KSClassDeclaration && it.classKind == ClassKind.ENUM_ENTRY && it.simpleName.asString() == result.name
105+
}?.let { (it as KSClassDeclaration).asStarProjectedType() }
106+
?.let {
107+
return it
108+
}
109+
} else {
110+
null
111+
}
112+
}
113+
else -> result
114+
}
79115
}
80116

81117
override val shortName: KSName by lazy {

compiler-plugin/src/test/java/com/google/devtools/ksp/test/KotlinKSPTestGenerated.java

+5
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,11 @@ public void testAnnotationValue() throws Exception {
4747
runTest("testData/api/annotationValue.kt");
4848
}
4949

50+
@TestMetadata("annotationWithArrayValue.kt")
51+
public void testAnnotationWithArrayValue() throws Exception {
52+
runTest("testData/api/annotationWithArrayValue.kt");
53+
}
54+
5055
@TestMetadata("annotationWithDefault.kt")
5156
public void testAnnotationWithDefault() throws Exception {
5257
runTest("testData/api/annotationWithDefault.kt");

compiler-plugin/testData/api/annotationValue.kt

+21-4
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,16 @@
2727
// @Suppress
2828
// G
2929
// 31
30-
// warning1
31-
// warning 2
30+
// Str
31+
// 42
32+
// Foo
33+
// File
34+
// <ERROR TYPE>
35+
// @Foo
36+
// @Suppress
37+
// G
38+
// 31
39+
// [warning1, warning 2]
3240
// END
3341
// FILE: a.kt
3442

@@ -51,7 +59,7 @@ annotation class Bar(
5159
)
5260

5361
fun Fun() {
54-
@Bar("Str", 42, Foo::class, java.io.File::class, Local::class, Foo(17), Suppress("name1", "name2"), RGB.G)
62+
@Bar("Str", 40 + 2, Foo::class, java.io.File::class, Local::class, Foo(17), Suppress("name1", "name2"), RGB.G)
5563
class Local
5664
}
5765

@@ -61,4 +69,13 @@ fun Fun() {
6169
class C {
6270

6371
}
64-
72+
// FILE: JavaAnnotated.java
73+
@Bar(argStr = "Str",
74+
argInt = 40 + 2,
75+
argClsUser = Foo.class,
76+
argClsLib = java.io.File.class,
77+
argClsLocal = Local.class, // intentional error type
78+
argAnnoUser = @Foo(s = 17),
79+
argAnnoLib = @Suppress(names = {"name1", "name2"}),
80+
argEnum = RGB.G)
81+
public class JavaAnnotated {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/*
2+
* Copyright 2020 Google LLC
3+
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
// TEST PROCESSOR: AnnotationArrayValueProcessor
18+
// EXPECTED:
19+
// KotlinAnnotated
20+
// KotlinAnnotation ->
21+
// stringArray = [a, b, null, c]
22+
// classArray = [Any, List<*>]
23+
// JavaAnnotation ->
24+
// stringArray = [x, y, null, z]
25+
// classArray = [String, Long]
26+
// JavaAnnotated
27+
// KotlinAnnotation ->
28+
// stringArray = [j-a, j-b, null, j-c]
29+
// classArray = [Object, List<*>]
30+
// JavaAnnotation ->
31+
// stringArray = [j-x, j-y, null, j-z]
32+
// classArray = [Integer, Character]
33+
// END
34+
// FILE: a.kt
35+
36+
annotation class KotlinAnnotation(val stringArray: Array<String?>, val classArray: Array<KClass<*>?>)
37+
38+
@KotlinAnnotation(
39+
stringArray = ["a", "b", null, "c"],
40+
classArray = [Any::class, List::class]
41+
)
42+
@JavaAnnotation(
43+
stringArray = ["x", "y", null, "z"],
44+
classArray = [String::class, Long::class]
45+
)
46+
class KotlinAnnotated
47+
48+
// FILE: JavaAnnotation.java
49+
public @interface JavaAnnotation {
50+
String[] stringArray();
51+
Class[] classArray();
52+
}
53+
54+
// FILE: JavaAnnotated.java
55+
import java.util.*;
56+
@KotlinAnnotation(
57+
stringArray = {"j-a", "j-b", null, "j-c"},
58+
classArray = {Object.class, List.class}
59+
)
60+
@JavaAnnotation(
61+
stringArray = {"j-x", "j-y", null, "j-z"},
62+
classArray = {Integer.class, Character.class}
63+
)
64+
public class JavaAnnotated {
65+
}

compiler-plugin/testData/api/annotationWithDefault.kt

+16
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,22 @@
1919
// EXPECTED:
2020
// KotlinAnnotation -> a:debugKt,b:default
2121
// JavaAnnotation -> debug:debug,withDefaultValue:OK
22+
// JavaAnnotation2 -> y:y-kotlin,x:x-kotlin,z:z-default
23+
// KotlinAnnotation2 -> y:y-kotlin,x:x-kotlin,z:z-default
2224
// KotlinAnnotation -> a:debugJava,b:default
2325
// JavaAnnotation -> debug:debugJava2,withDefaultValue:OK
26+
// JavaAnnotation2 -> y:y-java,x:x-java,z:z-default
27+
// KotlinAnnotation2 -> y:y-java,x:x-java,z:z-default
2428
// END
2529
// FILE: a.kt
2630

2731
annotation class KotlinAnnotation(val a: String, val b:String = "default")
32+
annotation class KotlinAnnotation2(val x: String, val y:String = "y-default", val z:String = "z-default")
2833

2934
@KotlinAnnotation("debugKt")
3035
@JavaAnnotation("debug")
36+
@JavaAnnotation2(y="y-kotlin", x="x-kotlin")
37+
@KotlinAnnotation2(y="y-kotlin", x="x-kotlin")
3138
class A
3239

3340
// FILE: JavaAnnotation.java
@@ -36,10 +43,19 @@ public @interface JavaAnnotation {
3643
String withDefaultValue() default "OK";
3744
}
3845

46+
// FILE: JavaAnnotation2.java
47+
public @interface JavaAnnotation2 {
48+
String x() default "x-default";
49+
String y() default "y-default";
50+
String z() default "z-default";
51+
}
52+
3953
// FILE: JavaAnnotated.java
4054

4155
@KotlinAnnotation("debugJava")
4256
@JavaAnnotation("debugJava2")
57+
@JavaAnnotation2(y="y-java", x="x-java")
58+
@KotlinAnnotation2(y="y-java", x="x-java")
4359
public class JavaAnnotated {
4460

4561
}

0 commit comments

Comments
 (0)