diff --git a/compiler/fir/fir2ir/src/org/jetbrains/kotlin/fir/backend/generators/AnnotationGenerator.kt b/compiler/fir/fir2ir/src/org/jetbrains/kotlin/fir/backend/generators/AnnotationGenerator.kt index bc7e5ba92b1..372c9fd4b39 100644 --- a/compiler/fir/fir2ir/src/org/jetbrains/kotlin/fir/backend/generators/AnnotationGenerator.kt +++ b/compiler/fir/fir2ir/src/org/jetbrains/kotlin/fir/backend/generators/AnnotationGenerator.kt @@ -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() } diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/declarations/FirAnnotationUtils.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/declarations/FirAnnotationUtils.kt index ef665cb993a..4a9303f3d65 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/declarations/FirAnnotationUtils.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/declarations/FirAnnotationUtils.kt @@ -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 = 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 { + val metaAnnotationAboutTarget = + toAnnotationClass(session)?.annotations?.find { it.toAnnotationClassId() == TARGET_CLASS_ID } + ?: return emptyList() + return metaAnnotationAboutTarget.argumentList.arguments.toAnnotationUseSiteTargets() +} + +private fun List.toAnnotationUseSiteTargets(): List = + 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 } } diff --git a/compiler/testData/codegen/box/annotations/jvmAnnotationFlags.kt b/compiler/testData/codegen/box/annotations/jvmAnnotationFlags.kt index c06101fc266..6a491742843 100644 --- a/compiler/testData/codegen/box/annotations/jvmAnnotationFlags.kt +++ b/compiler/testData/codegen/box/annotations/jvmAnnotationFlags.kt @@ -1,4 +1,3 @@ -// IGNORE_BACKEND_FIR: JVM_IR // TARGET_BACKEND: JVM // WITH_RUNTIME