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)
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 6a6aa2a..c6538aa 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 @@
     val commonSources: List<File>,
 
     val excludedProcessors: Set<String>,
+    val mapAnnotationArgumentsInJava: Boolean,
 ) {
     class Builder {
         var projectBaseDir: File? = null
@@ -93,6 +94,7 @@
         var commonSources: MutableList<File> = mutableListOf()
 
         var excludedProcessors: MutableSet<String> = mutableSetOf()
+        var mapAnnotationArgumentsInJava: Boolean = false
 
         fun build(): KspOptions {
             return KspOptions(
@@ -121,6 +123,7 @@
                 compilerVersion,
                 commonSources,
                 excludedProcessors,
+                mapAnnotationArgumentsInJava,
             )
         }
     }
@@ -299,6 +302,14 @@
         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 @@
     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 e9b8bdf..7c1fade 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 @@
             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 592748e..f6ae397 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.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 @@
         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 @@
         }
         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 3b92367..e242039 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 @@
                 "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 0000000..870b4e6
--- /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 0000000..7892f01
--- /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 0000000..1869680
--- /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 0000000..c7c8368
--- /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