FIR2IR: use @Target to put annotations on either property or backing field

This commit is contained in:
Jinseong Jeon
2020-09-24 15:30:37 -07:00
committed by Mikhail Glukhikh
parent b59832c5fd
commit f3db113a82
3 changed files with 61 additions and 20 deletions
@@ -10,7 +10,7 @@ import org.jetbrains.kotlin.fir.FirAnnotationContainer
import org.jetbrains.kotlin.fir.backend.Fir2IrComponents
import org.jetbrains.kotlin.fir.declarations.FirProperty
import org.jetbrains.kotlin.fir.declarations.FirValueParameter
import org.jetbrains.kotlin.fir.declarations.isJvmFieldAnnotation
import org.jetbrains.kotlin.fir.declarations.hasMetaAnnotationUseSiteTargets
import org.jetbrains.kotlin.fir.expressions.FirAnnotationCall
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.expressions.IrConstructorCall
@@ -40,17 +40,24 @@ internal class AnnotationGenerator(private val components: Fir2IrComponents) : F
irValueParameter.annotations +=
firValueParameter.annotations
.filter {
// TODO: for `null` use-site, refer to targets on the meta annotation
it.useSiteTarget == null || !isInConstructor || it.useSiteTarget == AnnotationUseSiteTarget.CONSTRUCTOR_PARAMETER
}
.toIrAnnotations()
}
private fun FirAnnotationCall.targetsField(): Boolean =
// Check if the annotation has a field-targeting meta annotation, e.g., @Target(FIELD)
hasMetaAnnotationUseSiteTargets(session, AnnotationUseSiteTarget.FIELD, AnnotationUseSiteTarget.PROPERTY_DELEGATE_FIELD)
fun generate(irProperty: IrProperty, property: FirProperty) {
irProperty.annotations +=
property.annotations
.filter {
!it.isJvmFieldAnnotation &&
(it.useSiteTarget == null || it.useSiteTarget == AnnotationUseSiteTarget.PROPERTY)
it.useSiteTarget == AnnotationUseSiteTarget.PROPERTY ||
// NB: annotation with null use-site should be landed on the property (ahead of field),
// unless it has FIELD target on the meta annotation, like @Target(FIELD)
(it.useSiteTarget == null && !it.targetsField())
}
.toIrAnnotations()
}
@@ -62,9 +69,9 @@ internal class AnnotationGenerator(private val components: Fir2IrComponents) : F
irField.annotations +=
property.annotations
.filter {
it.isJvmFieldAnnotation ||
it.useSiteTarget == AnnotationUseSiteTarget.FIELD ||
it.useSiteTarget == AnnotationUseSiteTarget.PROPERTY_DELEGATE_FIELD
it.useSiteTarget == AnnotationUseSiteTarget.FIELD ||
it.useSiteTarget == AnnotationUseSiteTarget.PROPERTY_DELEGATE_FIELD ||
(it.useSiteTarget == null && it.targetsField())
}
.toIrAnnotations()
}
@@ -5,10 +5,10 @@
package org.jetbrains.kotlin.fir.declarations
import org.jetbrains.kotlin.descriptors.annotations.AnnotationUseSiteTarget
import org.jetbrains.kotlin.fir.FirAnnotationContainer
import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.expressions.FirAnnotationCall
import org.jetbrains.kotlin.fir.expressions.FirQualifiedAccessExpression
import org.jetbrains.kotlin.fir.expressions.*
import org.jetbrains.kotlin.fir.references.FirResolvedNamedReference
import org.jetbrains.kotlin.fir.resolve.toSymbol
import org.jetbrains.kotlin.fir.symbols.ConeClassLikeLookupTag
@@ -18,6 +18,10 @@ import org.jetbrains.kotlin.fir.types.coneType
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.Name
private val RETENTION_CLASS_ID = ClassId.fromString("kotlin/annotation/Retention")
private val TARGET_CLASS_ID = ClassId.fromString("kotlin/annotation/Target")
private val JVM_FIELD_CLASS_ID = ClassId.fromString("kotlin/jvm/JvmField")
private fun FirAnnotationCall.toAnnotationLookupTag(): ConeClassLikeLookupTag =
(annotationTypeRef.coneType as ConeClassLikeType).lookupTag
@@ -27,29 +31,60 @@ fun FirAnnotationCall.toAnnotationClassId(): ClassId =
private fun FirAnnotationCall.toAnnotationClass(session: FirSession): FirRegularClass? =
toAnnotationLookupTag().toSymbol(session)?.fir as? FirRegularClass
// TODO: this is temporary solution, we need something better
private val FirExpression.callableNameOfMetaAnnotationArgument: Name?
get() =
(this as? FirQualifiedAccessExpression)?.let {
val callableSymbol = (it.calleeReference as? FirResolvedNamedReference)?.resolvedSymbol as? FirCallableSymbol<*>
callableSymbol?.callableId?.callableName
}
fun FirAnnotationContainer.nonSourceAnnotations(session: FirSession): List<FirAnnotationCall> =
annotations.filter { annotation ->
val firAnnotationClass = annotation.toAnnotationClass(session)
firAnnotationClass != null && firAnnotationClass.annotations.none { meta ->
val firMetaAnnotationClassId = meta.toAnnotationClassId()
firMetaAnnotationClassId.asString() == "kotlin/annotation/Retention" &&
// TODO: this is temporary solution, we need something better
(meta.argumentList.arguments.singleOrNull() as? FirQualifiedAccessExpression)?.let {
val callableSymbol = (it.calleeReference as? FirResolvedNamedReference)?.resolvedSymbol as? FirCallableSymbol<*>
callableSymbol?.callableId?.callableName
} == Name.identifier("SOURCE")
meta.toAnnotationClassId() == RETENTION_CLASS_ID &&
meta.argumentList.arguments.singleOrNull()?.callableNameOfMetaAnnotationArgument == Name.identifier("SOURCE")
}
}
inline val FirProperty.hasJvmFieldAnnotation: Boolean
get() = annotations.any { it.isJvmFieldAnnotation }
inline val FirAnnotationCall.isJvmFieldAnnotation: Boolean
get() {
val classId = toAnnotationClassId()
return classId.packageFqName.asString() == "kotlin.jvm" && classId.relativeClassName.asString() == "JvmField"
val FirAnnotationCall.isJvmFieldAnnotation: Boolean
get() = toAnnotationClassId() == JVM_FIELD_CLASS_ID
private fun FirAnnotationCall.useSiteTargetsFromMetaAnnotation(session: FirSession): List<AnnotationUseSiteTarget> {
val metaAnnotationAboutTarget =
toAnnotationClass(session)?.annotations?.find { it.toAnnotationClassId() == TARGET_CLASS_ID }
?: return emptyList()
return metaAnnotationAboutTarget.argumentList.arguments.toAnnotationUseSiteTargets()
}
private fun List<FirExpression>.toAnnotationUseSiteTargets(): List<AnnotationUseSiteTarget> =
flatMap { arg ->
val unwrappedArg = if (arg is FirNamedArgumentExpression) arg.expression else arg
if (unwrappedArg is FirArrayOfCall) {
unwrappedArg.argumentList.arguments.toAnnotationUseSiteTargets()
} else {
unwrappedArg.toAnnotationUseSiteTarget()?.let { listOf(it) } ?: emptyList()
}
}
private val USE_SITE_TARGET_NAME_MAP =
AnnotationUseSiteTarget.values().map { it.name to it }.toMap()
private fun FirExpression.toAnnotationUseSiteTarget(): AnnotationUseSiteTarget? =
// TODO: depending on the context, "PARAMETER" can be mapped to either CONSTRUCTOR_PARAMETER or SETTER_PARAMETER ?
callableNameOfMetaAnnotationArgument?.identifier?.let {
USE_SITE_TARGET_NAME_MAP[it]
}
fun FirAnnotationCall.hasMetaAnnotationUseSiteTargets(session: FirSession, vararg useSiteTargets: AnnotationUseSiteTarget): Boolean {
val meta = useSiteTargetsFromMetaAnnotation(session)
return useSiteTargets.any { meta.contains(it) }
}
fun FirAnnotatedDeclaration.hasAnnotation(classId: ClassId): Boolean {
return annotations.any { it.toAnnotationClassId() == classId }
}
@@ -1,4 +1,3 @@
// IGNORE_BACKEND_FIR: JVM_IR
// TARGET_BACKEND: JVM
// WITH_RUNTIME