aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTing-Yuan Huang <laszio@google.com>2023-01-19 03:28:08 -0800
committerKSP Auto Pick <kotlin-symbol-processing@google.com>2023-01-19 21:55:44 +0000
commit03b0374ee3ccfde8891a42c1fafc919020c376b3 (patch)
tree69166aea612ecede4f85d273184ecf3df2a3556b
parenta3fc613b7928a5908e5bdefbdc01e5155899d666 (diff)
downloadksp-03b0374ee3ccfde8891a42c1fafc919020c376b3.tar.gz
Introduce ksp.map.annotation.arguments.in.java
which defaults to false. When enabled, annotation arguments are mapped to Kotlin types in Java source files. (cherry picked from commit a9f2244b3327d128ed06046c1b7b968a54ec1ca9)
-rw-r--r--common-util/src/main/kotlin/com/google/devtools/ksp/KspOptions.kt12
-rw-r--r--compiler-plugin/src/main/kotlin/com/google/devtools/ksp/processing/impl/ResolverImpl.kt32
-rw-r--r--compiler-plugin/src/main/kotlin/com/google/devtools/ksp/symbol/impl/java/KSAnnotationJavaImpl.kt34
-rw-r--r--gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/KspSubplugin.kt4
-rw-r--r--integration-tests/src/test/kotlin/com/google/devtools/ksp/test/MapAnnotationArgumentsIT.kt40
-rw-r--r--integration-tests/src/test/resources/map-annotation-arguments/test-processor/src/main/kotlin/TestProcessor.kt36
-rw-r--r--integration-tests/src/test/resources/map-annotation-arguments/workload/src/main/java/com/example/AnnotationTest.java8
-rw-r--r--integration-tests/src/test/resources/map-annotation-arguments/workload/src/main/java/com/example/JavaAnnotation.java6
8 files changed, 139 insertions, 33 deletions
diff --git a/common-util/src/main/kotlin/com/google/devtools/ksp/KspOptions.kt b/common-util/src/main/kotlin/com/google/devtools/ksp/KspOptions.kt
index 6a6aa2af..c6538aab 100644
--- a/common-util/src/main/kotlin/com/google/devtools/ksp/KspOptions.kt
+++ b/common-util/src/main/kotlin/com/google/devtools/ksp/KspOptions.kt
@@ -57,6 +57,7 @@ class KspOptions(
val commonSources: List<File>,
val excludedProcessors: Set<String>,
+ val mapAnnotationArgumentsInJava: Boolean,
) {
class Builder {
var projectBaseDir: File? = null
@@ -93,6 +94,7 @@ class KspOptions(
var commonSources: MutableList<File> = mutableListOf()
var excludedProcessors: MutableSet<String> = mutableSetOf()
+ var mapAnnotationArgumentsInJava: Boolean = false
fun build(): KspOptions {
return KspOptions(
@@ -121,6 +123,7 @@ class KspOptions(
compilerVersion,
commonSources,
excludedProcessors,
+ mapAnnotationArgumentsInJava,
)
}
}
@@ -299,6 +302,14 @@ enum class KspCliOption(
false,
true
),
+
+ MAP_ANNOTATION_ARGUMENTS_IN_JAVA_OPTION(
+ "mapAnnotationArgumentsInJava",
+ "<mapAnnotationArgumentsInJava>",
+ "Map types in annotation arguments in Java sources",
+ false,
+ false
+ ),
}
@Suppress("IMPLICIT_CAST_TO_ANY")
@@ -328,4 +339,5 @@ fun KspOptions.Builder.processOption(option: KspCliOption, value: String) = when
KspCliOption.RETURN_OK_ON_ERROR_OPTION -> returnOkOnError = value.toBoolean()
KspCliOption.COMMON_SOURCES_OPTION -> commonSources.addAll(value.split(File.pathSeparator).map { File(it) })
KspCliOption.EXCLUDED_PROCESSORS_OPTION -> excludedProcessors.addAll(value.split(":"))
+ KspCliOption.MAP_ANNOTATION_ARGUMENTS_IN_JAVA_OPTION -> mapAnnotationArgumentsInJava = value.toBoolean()
}
diff --git a/compiler-plugin/src/main/kotlin/com/google/devtools/ksp/processing/impl/ResolverImpl.kt b/compiler-plugin/src/main/kotlin/com/google/devtools/ksp/processing/impl/ResolverImpl.kt
index e9b8bdf4..7c1fade2 100644
--- a/compiler-plugin/src/main/kotlin/com/google/devtools/ksp/processing/impl/ResolverImpl.kt
+++ b/compiler-plugin/src/main/kotlin/com/google/devtools/ksp/processing/impl/ResolverImpl.kt
@@ -657,6 +657,38 @@ class ResolverImpl(
resolverContext.typeResolver.transformJavaType(javaType, TypeUsage.COMMON.toAttributes())
}
+ /*
+ * Don't map Java types in annotation parameters
+ *
+ * Users may specify Java types explicitly by instances of `Class<T>`.
+ * The situation is similar to `getClassDeclarationByName` where we have
+ * decided to keep those Java types not mapped.
+ *
+ * It would be troublesome if users try to use reflection on types that
+ * were mapped to Kotlin builtins, becuase some of those builtins don't
+ * even exist in classpath.
+ *
+ * Therefore, ResolverImpl.resolveJavaType cannot be used.
+ */
+ fun resolveJavaTypeInAnnotations(psiType: PsiType): KSType = if (options.mapAnnotationArgumentsInJava) {
+ getKSTypeCached(resolveJavaType(psiType))
+ } else {
+ when (psiType) {
+ is PsiPrimitiveType -> {
+ getClassDeclarationByName(psiType.boxedTypeName!!)!!.asStarProjectedType()
+ }
+ is PsiArrayType -> {
+ val componentType = resolveJavaTypeInAnnotations(psiType.componentType)
+ val componentTypeRef = createKSTypeReferenceFromKSType(componentType)
+ val typeArgs = listOf(getTypeArgument(componentTypeRef, Variance.INVARIANT))
+ builtIns.arrayType.replace(typeArgs)
+ }
+ else -> {
+ getClassDeclarationByName(psiType.canonicalText)?.asStarProjectedType() ?: KSErrorType
+ }
+ }
+ }
+
fun KotlinType.expandNonRecursively(): KotlinType =
(constructor.declarationDescriptor as? TypeAliasDescriptor)?.expandedType?.withAbbreviation(this as SimpleType)
?: this
diff --git a/compiler-plugin/src/main/kotlin/com/google/devtools/ksp/symbol/impl/java/KSAnnotationJavaImpl.kt b/compiler-plugin/src/main/kotlin/com/google/devtools/ksp/symbol/impl/java/KSAnnotationJavaImpl.kt
index 592748e4..f6ae397b 100644
--- a/compiler-plugin/src/main/kotlin/com/google/devtools/ksp/symbol/impl/java/KSAnnotationJavaImpl.kt
+++ b/compiler-plugin/src/main/kotlin/com/google/devtools/ksp/symbol/impl/java/KSAnnotationJavaImpl.kt
@@ -24,7 +24,6 @@ import com.google.devtools.ksp.processing.impl.ResolverImpl
import com.google.devtools.ksp.symbol.*
import com.google.devtools.ksp.symbol.impl.binary.getAbsentDefaultArguments
import com.google.devtools.ksp.symbol.impl.binary.getDefaultConstructorArguments
-import com.google.devtools.ksp.symbol.impl.kotlin.KSErrorType
import com.google.devtools.ksp.symbol.impl.kotlin.KSTypeImpl
import com.google.devtools.ksp.symbol.impl.toLocation
import com.intellij.lang.jvm.JvmClassKind
@@ -103,37 +102,6 @@ class KSAnnotationJavaImpl private constructor(val psi: PsiAnnotation) : KSAnnot
kotlinType?.getDefaultConstructorArguments(emptyList(), this) ?: emptyList()
}
- /*
- * Don't map Java types in annotation parameters
- *
- * Users may specify Java types explicitly by instances of `Class<T>`.
- * The situation is similar to `getClassDeclarationByName` where we have
- * decided to keep those Java types not mapped.
- *
- * It would be troublesome if users try to use reflection on types that
- * were mapped to Kotlin builtins, becuase some of those builtins don't
- * even exist in classpath.
- *
- * Therefore, ResolverImpl.resolveJavaType cannot be used.
- */
- private fun resolveJavaTypeSimple(psiType: PsiType): KSType {
- return when (psiType) {
- is PsiPrimitiveType -> {
- ResolverImpl.instance!!.getClassDeclarationByName(psiType.boxedTypeName!!)!!.asStarProjectedType()
- }
- is PsiArrayType -> {
- val componentType = resolveJavaTypeSimple(psiType.componentType)
- val componentTypeRef = ResolverImpl.instance!!.createKSTypeReferenceFromKSType(componentType)
- val typeArgs = listOf(ResolverImpl.instance!!.getTypeArgument(componentTypeRef, Variance.INVARIANT))
- ResolverImpl.instance!!.builtIns.arrayType.replace(typeArgs)
- }
- else -> {
- ResolverImpl.instance!!.getClassDeclarationByName(psiType.canonicalText)?.asStarProjectedType()
- ?: KSErrorType
- }
- }
- }
-
private fun calcValue(value: PsiAnnotationMemberValue?): Any? {
if (value is PsiAnnotation) {
return getCached(value)
@@ -149,7 +117,7 @@ class KSAnnotationJavaImpl private constructor(val psi: PsiAnnotation) : KSAnnot
}
return when (result) {
is PsiType -> {
- resolveJavaTypeSimple(result)
+ ResolverImpl.instance!!.resolveJavaTypeInAnnotations(result)
}
is PsiLiteralValue -> {
result.value
diff --git a/gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/KspSubplugin.kt b/gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/KspSubplugin.kt
index 3b923674..e242039a 100644
--- a/gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/KspSubplugin.kt
+++ b/gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/KspSubplugin.kt
@@ -157,6 +157,10 @@ class KspGradleSubplugin @Inject internal constructor(private val registry: Tool
"excludedProcessors",
kspExtension.excludedProcessors.joinToString(":")
)
+ options += SubpluginOption(
+ "mapAnnotationArgumentsInJava",
+ project.findProperty("ksp.map.annotation.arguments.in.java")?.toString() ?: "false"
+ )
commandLineArgumentProviders.get().forEach {
it.asArguments().forEach { argument ->
if (!argument.matches(Regex("\\S+=\\S+"))) {
diff --git a/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/MapAnnotationArgumentsIT.kt b/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/MapAnnotationArgumentsIT.kt
new file mode 100644
index 00000000..870b4e60
--- /dev/null
+++ b/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/MapAnnotationArgumentsIT.kt
@@ -0,0 +1,40 @@
+package com.google.devtools.ksp.test
+
+import org.gradle.testkit.runner.GradleRunner
+import org.gradle.testkit.runner.TaskOutcome
+import org.junit.Assert
+import org.junit.Rule
+import org.junit.Test
+
+class MapAnnotationArgumentsIT {
+ @Rule
+ @JvmField
+ val project: TemporaryTestProject = TemporaryTestProject("map-annotation-arguments", "test-processor")
+
+ val expectedErrors = listOf(
+ "e: [ksp] unboxedChar: Char != Character\n",
+ "e: [ksp] boxedChar: (Char..Char?) != Character\n",
+ "e: Error occurred in KSP, check log for detail\n",
+ )
+
+ @Test
+ fun testMapAnnotationArguments() {
+ val gradleRunner = GradleRunner.create().withProjectDir(project.root)
+
+ gradleRunner.withArguments("assemble", "-Pksp.map.annotation.arguments.in.java=true").build().let { result ->
+ Assert.assertEquals(TaskOutcome.SUCCESS, result.task(":workload:kspKotlin")?.outcome)
+ Assert.assertEquals(TaskOutcome.SUCCESS, result.task(":workload:assemble")?.outcome)
+ }
+
+ gradleRunner.withArguments("clean", "assemble", "--rerun-tasks").buildAndFail().let { result ->
+ Assert.assertEquals(TaskOutcome.FAILED, result.task(":workload:kspKotlin")?.outcome)
+ Assert.assertTrue(expectedErrors.all { it in result.output })
+ }
+
+ gradleRunner.withArguments("clean", "assemble", "-Pksp.map.annotation.arguments.in.java=false", "--rerun-tasks")
+ .buildAndFail().let { result ->
+ Assert.assertEquals(TaskOutcome.FAILED, result.task(":workload:kspKotlin")?.outcome)
+ Assert.assertTrue(expectedErrors.all { it in result.output })
+ }
+ }
+}
diff --git a/integration-tests/src/test/resources/map-annotation-arguments/test-processor/src/main/kotlin/TestProcessor.kt b/integration-tests/src/test/resources/map-annotation-arguments/test-processor/src/main/kotlin/TestProcessor.kt
new file mode 100644
index 00000000..7892f012
--- /dev/null
+++ b/integration-tests/src/test/resources/map-annotation-arguments/test-processor/src/main/kotlin/TestProcessor.kt
@@ -0,0 +1,36 @@
+import com.google.devtools.ksp.getClassDeclarationByName
+import com.google.devtools.ksp.processing.*
+import com.google.devtools.ksp.symbol.*
+
+class TestProcessor(
+ val codeGenerator: CodeGenerator,
+ val logger: KSPLogger
+) : SymbolProcessor {
+ val expected = mapOf(
+ "unboxedChar" to "Char",
+ "boxedChar" to "(Char..Char?)",
+ )
+
+ override fun process(resolver: Resolver): List<KSAnnotated> {
+ val j = resolver.getClassDeclarationByName("com.example.AnnotationTest")!!
+ j.annotations.forEach { annotation ->
+ annotation.arguments.forEach {
+ val key = it.name?.asString()
+ val value = it.value.toString()
+ if (expected[key] != value) {
+ logger.error("$key: ${expected[key]} != $value")
+ }
+ }
+ }
+
+ return emptyList()
+ }
+}
+
+class TestProcessorProvider : SymbolProcessorProvider {
+ override fun create(
+ environment: SymbolProcessorEnvironment
+ ): SymbolProcessor {
+ return TestProcessor(environment.codeGenerator, environment.logger)
+ }
+}
diff --git a/integration-tests/src/test/resources/map-annotation-arguments/workload/src/main/java/com/example/AnnotationTest.java b/integration-tests/src/test/resources/map-annotation-arguments/workload/src/main/java/com/example/AnnotationTest.java
new file mode 100644
index 00000000..18696806
--- /dev/null
+++ b/integration-tests/src/test/resources/map-annotation-arguments/workload/src/main/java/com/example/AnnotationTest.java
@@ -0,0 +1,8 @@
+package com.example;
+
+@JavaAnnotation(
+ unboxedChar = char.class,
+ boxedChar = Character.class
+)
+public class AnnotationTest {
+}
diff --git a/integration-tests/src/test/resources/map-annotation-arguments/workload/src/main/java/com/example/JavaAnnotation.java b/integration-tests/src/test/resources/map-annotation-arguments/workload/src/main/java/com/example/JavaAnnotation.java
new file mode 100644
index 00000000..c7c8368f
--- /dev/null
+++ b/integration-tests/src/test/resources/map-annotation-arguments/workload/src/main/java/com/example/JavaAnnotation.java
@@ -0,0 +1,6 @@
+package com.example;
+
+public @interface JavaAnnotation {
+ Class unboxedChar();
+ Class boxedChar();
+} \ No newline at end of file