@@ -39,6 +39,7 @@ import com.google.devtools.ksp.impl.symbol.kotlin.toKSClassDeclaration
3939import com.google.devtools.ksp.impl.symbol.kotlin.toKSFile
4040import com.google.devtools.ksp.impl.symbol.kotlin.toKSFunctionDeclaration
4141import com.google.devtools.ksp.impl.symbol.kotlin.toKSPropertyDeclaration
42+ import com.google.devtools.ksp.impl.symbol.kotlin.toKtClassSymbol
4243import com.google.devtools.ksp.impl.symbol.kotlin.toLocation
4344import com.google.devtools.ksp.processing.SymbolProcessor
4445import com.google.devtools.ksp.symbol.AnnotationUseSiteTarget
@@ -60,13 +61,17 @@ import com.intellij.psi.PsiParameter
6061import com.intellij.psi.PsiTypeParameter
6162import com.intellij.psi.PsiTypeParameterList
6263import com.intellij.util.containers.addIfNotNull
64+ import org.jetbrains.kotlin.analysis.api.annotations.KaAnnotation
65+ import org.jetbrains.kotlin.analysis.api.annotations.KaAnnotationValue
6366import org.jetbrains.kotlin.analysis.api.resolution.KaAnnotationCall
6467import org.jetbrains.kotlin.analysis.api.resolution.successfulCallOrNull
6568import org.jetbrains.kotlin.analysis.api.resolution.symbol
6669import org.jetbrains.kotlin.analysis.api.symbols.KaCallableSymbol
70+ import org.jetbrains.kotlin.analysis.api.symbols.KaClassKind
6771import org.jetbrains.kotlin.analysis.api.symbols.KaClassSymbol
6872import org.jetbrains.kotlin.analysis.api.symbols.KaTypeAliasSymbol
6973import org.jetbrains.kotlin.analysis.api.types.symbol
74+ import org.jetbrains.kotlin.name.CallableId
7075import org.jetbrains.kotlin.name.ClassId
7176import org.jetbrains.kotlin.name.FqName
7277import org.jetbrains.kotlin.name.Name
@@ -77,7 +82,7 @@ import org.jetbrains.kotlin.psi.KtFile
7782import org.jetbrains.kotlin.psi.KtTypeReference
7883import org.jetbrains.kotlin.psi.psiUtil.parameterIndex
7984import org.jetbrains.kotlin.utils.addToStdlib.flatGroupBy
80- import kotlin.collections.mapValues
85+ import org.jetbrains. kotlin.utils.addToStdlib.getOrPut
8186
8287/* *
8388 * An [AnnotationResolutionStrategy] that uses a combination of Psi and Kotlin's Analysis API to resolve
@@ -90,6 +95,8 @@ class PsiResolutionStrategy(
9095 override val deferredSymbols : Map <SymbolProcessor , List <Restorable >>
9196) : AnnotationResolutionStrategy {
9297
98+ private val targetFieldFix = AnnotationDeclarationTargetFieldFix ()
99+
93100 /* *
94101 * Returns all symbols annotated with [annotationName].
95102 */
@@ -280,10 +287,11 @@ class PsiResolutionStrategy(
280287 // so we should only cache for known unique declarations in the file.
281288 // See test shadowingAnnotations.kt
282289 val fileCache = fileCaches.getOrPut(file) { FileCache (file) }
283- val classId = resolveAnnotationEntryClassId(annotationEntry, fileCache)
284290 // N.B.: Skip nullable qualified names without error. This is what the AA-based implementation does.
285291 // For now, this is the intended behavior. If it is changed in the future, then it is an API change.
286- val fqn = classId?.asFqNameString() ? : return @forEach
292+ val classId = resolveAnnotationEntryClassId(annotationEntry, fileCache) ? : return @forEach
293+ targetFieldFix.recordClassId(annotationEntry, classId)
294+ val fqn = classId.asFqNameString()
287295 getOrPut(fqn, ::mutableListOf)
288296 .add(annotated to annotationEntry)
289297 }
@@ -427,7 +435,14 @@ class PsiResolutionStrategy(
427435 private fun KtDeclaration.resolve (annotationEntry : KtAnnotationEntry ): Collection <KSAnnotated > {
428436 // TODO: This should perform case distinction instead of getTargetedSymbol
429437 val ksSym = analyze { symbol.toKSAnnotated() }
430- return ksSym.findTargetedSymbol(annotationEntry.ksUseSiteTarget)
438+ return when (ksSym) {
439+ is KSPropertyDeclaration -> targetFieldFix.resolveKSPropertyDeclaration(
440+ ksSym,
441+ annotationEntry
442+ )
443+
444+ else -> ksSym.findTargetedSymbol(annotationEntry.ksUseSiteTarget)
445+ }
431446 }
432447
433448 /* *
@@ -839,4 +854,152 @@ class PsiResolutionStrategy(
839854 */
840855 private val KtAnnotationEntry .ksUseSiteTarget: AnnotationUseSiteTarget ?
841856 get() = useSiteTarget?.getAnnotationUseSiteTarget()?.toKSAnnotationUseSiteTarget()
857+
858+
859+ /* *
860+ * This object represents a temporary fix for https://github.com/google/ksp/issues/2987.
861+ * It decides if an annotation is annotated with the FIELD annotation target,
862+ * and if so, applies to the field instead of the property.
863+ */
864+ private inner class AnnotationDeclarationTargetFieldFix {
865+
866+ /* *
867+ * Stores the [ClassId]s for the [KtAnnotationEntry].
868+ */
869+ private val annotationClassIds: MutableMap <KtAnnotationEntry , ClassId > = mutableMapOf ()
870+
871+ /* *
872+ * Stores the key-value pair [annotation] -> [classId]. This mapping is used by [resolveKSPropertyDeclaration].
873+ */
874+ fun recordClassId (annotation : KtAnnotationEntry , classId : ClassId ) {
875+ annotationClassIds.putIfAbsent(annotation, classId)
876+ }
877+
878+ /* *
879+ * Resolves [ksPropDecl] with respect to the declaration-site target [annotation].
880+ * The use-site target of [annotation] takes precedence over the declaration-site target.
881+ */
882+ fun resolveKSPropertyDeclaration (
883+ ksPropDecl : KSPropertyDeclaration ,
884+ annotation : KtAnnotationEntry
885+ ): Collection <KSAnnotated > =
886+ when {
887+ annotation.ksUseSiteTarget != null -> ksPropDecl.findTargetedSymbol(annotation.ksUseSiteTarget)
888+ canBeAppliedTo(
889+ annotation,
890+ ANNOTATION_TARGET_PROPERTY_ENTRY
891+ ) && canBeAppliedTo(
892+ annotation,
893+ ANNOTATION_TARGET_FIELD_ENTRY
894+ ) -> {
895+ listOfNotNull(ksPropDecl, ksPropDecl.backingField)
896+ }
897+
898+ canBeAppliedTo(annotation, ANNOTATION_TARGET_FIELD_ENTRY ) -> {
899+ listOfNotNull(ksPropDecl, ksPropDecl.backingField)
900+ }
901+
902+ canBeAppliedTo(annotation, ANNOTATION_TARGET_PROPERTY_ENTRY ) -> {
903+ listOfNotNull(ksPropDecl)
904+ }
905+
906+ else -> emptyList()
907+ }
908+
909+ /* *
910+ * Returns `true` if [annotation] can be applied to [entry], which is a [CallableId] for
911+ * the FIELD or PROPERTY [AnnotationTarget].
912+ * @see [ANNOTATION_TARGET_FIELD_ENTRY]
913+ * @see [ANNOTATION_TARGET_PROPERTY_ENTRY]
914+ */
915+ private fun canBeAppliedTo (annotation : KtAnnotationEntry , entry : CallableId ): Boolean =
916+ applicationCache.getOrPut(annotation to entry) {
917+ val targetAnnotation = findTargetAnnotation(getCachedClassId(annotation)) ? : return @getOrPut true
918+ val args = targetAnnotationArguments(targetAnnotation)
919+ args.contains(entry)
920+ }
921+
922+ /* *
923+ * Private cache for [canBeAppliedTo]
924+ */
925+ private val applicationCache: MutableMap <Pair <KtAnnotationEntry , CallableId >, Boolean > = mutableMapOf ()
926+
927+ private fun getCachedClassId (annotation : KtAnnotationEntry ): ClassId =
928+ annotationClassIds[annotation] ? : throw InternalKSPException (
929+ " Unexpected null class id for annotation entry: $annotation " ,
930+ annotation.psiOrParent.toLocation(),
931+ annotation.javaClass
932+ )
933+
934+ private val TARGET_ANNOTATION = ClassId (
935+ FqName (" kotlin.annotation" ),
936+ Name .identifier(" Target" )
937+ )
938+ private val ANNOTATION_TARGET_FIELD_ENTRY : CallableId = annotationTargetEntry(" FIELD" )
939+ private val ANNOTATION_TARGET_PROPERTY_ENTRY : CallableId = annotationTargetEntry(" PROPERTY" )
940+
941+ private fun annotationTargetEntry (entry : String ) = ClassId (
942+ FqName (" kotlin.annotation" ),
943+ Name .identifier(" AnnotationTarget" )
944+ ).let {
945+ CallableId (it, Name .identifier(entry))
946+ }
947+
948+ private fun isAnnotationClass (kaClassSym : KaClassSymbol ? ): Boolean =
949+ kaClassSym?.classKind == KaClassKind .ANNOTATION_CLASS
950+
951+ /* *
952+ * Returns the `@Target` annotation if it exists.
953+ */
954+ private fun findTargetAnnotation (kaClassSym : KaClassSymbol ? ): KaAnnotation ? {
955+ if (! isAnnotationClass(kaClassSym)) {
956+ return null
957+ }
958+ return kaClassSym?.annotations?.find { it.classId == TARGET_ANNOTATION }
959+ }
960+
961+ private fun findTargetAnnotation (classId : ClassId ): KaAnnotation ? =
962+ findTargetAnnotation(classId.toKtClassSymbol())
963+
964+ /* *
965+ * Returns the arguments of the target annotation. Throws an error if the annotation
966+ * has more than one non-null argument.
967+ */
968+ private fun targetAnnotationArguments (targetAnnotation : KaAnnotation ): List <CallableId > {
969+ val args = targetAnnotation.arguments
970+ if (args.size != 1 ) {
971+ throw InternalKSPException (
972+ " Unexpected number of arguments passed to @Target annotation" ,
973+ targetAnnotation.psi.toLocation(),
974+ targetAnnotation.javaClass
975+ )
976+ }
977+ return args.firstOrNull()?.let { flattenAnnotationArgs(it.expression) } ? : emptyList()
978+ }
979+
980+ /* *
981+ * Returns a list of [CallableId] where each member is the concrete type of the argument passed to
982+ * the [Target] annotation.
983+ * The [Target] annotation is defined as:
984+ * ```kt
985+ * annotation class Target(val allowedTarget: AnnotationTarget)
986+ * ```
987+ * However, you can pass an array of [AnnotationTarget] to [Target], so
988+ * both of the following constructors are allowed, hence the flattening:
989+ * ```kt
990+ * @Target(AnnotationTarget.FIELD)
991+ * @Target({AnnotationTarget.FIELD, AnnotationTarget.CLASS})
992+ * ```
993+ */
994+ private fun flattenAnnotationArgs (annotationValue : KaAnnotationValue ): List <CallableId > =
995+ when (annotationValue) {
996+ is KaAnnotationValue .EnumEntryValue -> listOfNotNull(annotationValue.callableId)
997+ is KaAnnotationValue .ArrayValue -> annotationValue.values.flatMap(::flattenAnnotationArgs)
998+ else -> throw InternalKSPException (
999+ " Unexpected argument type to @Target annotation" ,
1000+ annotationValue.sourcePsi.toLocation(),
1001+ annotationValue.javaClass,
1002+ )
1003+ }
1004+ }
8421005}
0 commit comments