FIR2IR: fix the way annotations are moved to fields
1. When an annotation has multiple targets, the priority goes like this:
constructor parameter (if applicable) -> property -> backing field.
2. The argument to `kotlin.annotation.Target` is a vararg, so that
should be handled as well.
3. `AnnotationTarget.VALUE_PARAMETER` allows receivers, constructor
parameters, and setter parameters, while `AnnotationTarget.FIELD` allows
both backing fields and delegates.
Known issue: java.lang.annotation.Target is not remapped to the Kotlin
equivalent, so things are still broken for pure Java annotations.
This commit is contained in:
+56
-63
@@ -7,14 +7,14 @@ package org.jetbrains.kotlin.fir.backend.generators
|
|||||||
|
|
||||||
import org.jetbrains.kotlin.descriptors.annotations.AnnotationUseSiteTarget
|
import org.jetbrains.kotlin.descriptors.annotations.AnnotationUseSiteTarget
|
||||||
import org.jetbrains.kotlin.fir.FirAnnotationContainer
|
import org.jetbrains.kotlin.fir.FirAnnotationContainer
|
||||||
|
import org.jetbrains.kotlin.fir.FirFakeSourceElementKind
|
||||||
import org.jetbrains.kotlin.fir.backend.Fir2IrComponents
|
import org.jetbrains.kotlin.fir.backend.Fir2IrComponents
|
||||||
import org.jetbrains.kotlin.fir.declarations.FirProperty
|
import org.jetbrains.kotlin.fir.declarations.FirProperty
|
||||||
import org.jetbrains.kotlin.fir.declarations.FirValueParameter
|
import org.jetbrains.kotlin.fir.declarations.FirValueParameter
|
||||||
import org.jetbrains.kotlin.fir.declarations.hasMetaAnnotationUseSiteTargets
|
import org.jetbrains.kotlin.fir.declarations.useSiteTargetsFromMetaAnnotation
|
||||||
import org.jetbrains.kotlin.fir.expressions.FirAnnotationCall
|
import org.jetbrains.kotlin.fir.expressions.FirAnnotationCall
|
||||||
import org.jetbrains.kotlin.ir.declarations.*
|
import org.jetbrains.kotlin.ir.declarations.*
|
||||||
import org.jetbrains.kotlin.ir.expressions.IrConstructorCall
|
import org.jetbrains.kotlin.ir.expressions.IrConstructorCall
|
||||||
import org.jetbrains.kotlin.ir.util.isPropertyField
|
|
||||||
import org.jetbrains.kotlin.ir.util.isSetter
|
import org.jetbrains.kotlin.ir.util.isSetter
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -36,80 +36,73 @@ class AnnotationGenerator(private val components: Fir2IrComponents) : Fir2IrComp
|
|||||||
irContainer.annotations = firContainer.annotations.toIrAnnotations()
|
irContainer.annotations = firContainer.annotations.toIrAnnotations()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun generate(irValueParameter: IrValueParameter, firValueParameter: FirValueParameter, isInConstructor: Boolean) {
|
private fun FirAnnotationCall.target(applicable: List<AnnotationUseSiteTarget>): AnnotationUseSiteTarget? =
|
||||||
irValueParameter.annotations +=
|
useSiteTarget ?: applicable.firstOrNull(useSiteTargetsFromMetaAnnotation(session)::contains)
|
||||||
firValueParameter.annotations
|
|
||||||
.filter {
|
companion object {
|
||||||
// TODO: for `null` use-site, refer to targets on the meta annotation
|
// Priority order: constructor parameter (if applicable) -> property -> field. So, for example, if `A`
|
||||||
it.useSiteTarget == null || !isInConstructor || it.useSiteTarget == AnnotationUseSiteTarget.CONSTRUCTOR_PARAMETER
|
// can be attached to all three, then in a declaration like
|
||||||
}
|
// class C(@A val x: Int) { @A val y = 1 }
|
||||||
.toIrAnnotations()
|
// the parameter `x` and the property `y` will have the annotation, while the property `x` and both backing fields will not.
|
||||||
|
private val propertyTargets = listOf(AnnotationUseSiteTarget.PROPERTY, AnnotationUseSiteTarget.FIELD)
|
||||||
|
private val constructorPropertyTargets = listOf(AnnotationUseSiteTarget.CONSTRUCTOR_PARAMETER) + propertyTargets
|
||||||
|
private val delegatedPropertyTargets = propertyTargets + listOf(AnnotationUseSiteTarget.PROPERTY_DELEGATE_FIELD)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun FirAnnotationCall.targetsField(): Boolean =
|
// TODO: third argument should be whether this parameter is a property declaration (though this probably makes no difference)
|
||||||
// Check if the annotation has a field-targeting meta annotation, e.g., @Target(FIELD)
|
fun generate(irValueParameter: IrValueParameter, firValueParameter: FirValueParameter, isInConstructor: Boolean) {
|
||||||
hasMetaAnnotationUseSiteTargets(session, AnnotationUseSiteTarget.FIELD, AnnotationUseSiteTarget.PROPERTY_DELEGATE_FIELD)
|
if (isInConstructor) {
|
||||||
|
irValueParameter.annotations += firValueParameter.annotations
|
||||||
|
.filter { it.target(constructorPropertyTargets) == AnnotationUseSiteTarget.CONSTRUCTOR_PARAMETER }
|
||||||
|
.toIrAnnotations()
|
||||||
|
} else {
|
||||||
|
irValueParameter.annotations += firValueParameter.annotations.toIrAnnotations()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun generate(irProperty: IrProperty, property: FirProperty) {
|
fun generate(irProperty: IrProperty, property: FirProperty) {
|
||||||
irProperty.annotations +=
|
val applicableTargets = when {
|
||||||
property.annotations
|
property.source?.kind == FirFakeSourceElementKind.PropertyFromParameter -> constructorPropertyTargets
|
||||||
.filter {
|
irProperty.isDelegated -> delegatedPropertyTargets
|
||||||
it.useSiteTarget == AnnotationUseSiteTarget.PROPERTY ||
|
else -> propertyTargets
|
||||||
// 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)
|
irProperty.annotations += property.annotations
|
||||||
(it.useSiteTarget == null && !it.targetsField())
|
.filter { it.target(applicableTargets) == AnnotationUseSiteTarget.PROPERTY }
|
||||||
}
|
.toIrAnnotations()
|
||||||
.toIrAnnotations()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun generate(irField: IrField, property: FirProperty) {
|
fun generate(irField: IrField, property: FirProperty) {
|
||||||
assert(irField.isPropertyField) {
|
val irProperty = irField.correspondingPropertySymbol?.owner ?: throw AssertionError("$irField is not a property field")
|
||||||
"$irField is not a property field."
|
val applicableTargets = when {
|
||||||
|
property.source?.kind == FirFakeSourceElementKind.PropertyFromParameter -> constructorPropertyTargets
|
||||||
|
irProperty.isDelegated -> delegatedPropertyTargets
|
||||||
|
else -> propertyTargets
|
||||||
}
|
}
|
||||||
irField.annotations +=
|
irField.annotations += property.annotations.filter {
|
||||||
property.annotations
|
val target = it.target(applicableTargets)
|
||||||
.filter {
|
target == AnnotationUseSiteTarget.FIELD || target == AnnotationUseSiteTarget.PROPERTY_DELEGATE_FIELD
|
||||||
it.useSiteTarget == AnnotationUseSiteTarget.FIELD ||
|
}.toIrAnnotations()
|
||||||
it.useSiteTarget == AnnotationUseSiteTarget.PROPERTY_DELEGATE_FIELD ||
|
|
||||||
(it.useSiteTarget == null && it.targetsField())
|
|
||||||
}
|
|
||||||
.toIrAnnotations()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun generate(propertyAccessor: IrFunction, property: FirProperty) {
|
fun generate(propertyAccessor: IrFunction, property: FirProperty) {
|
||||||
assert(propertyAccessor.isPropertyAccessor) {
|
assert(propertyAccessor.isPropertyAccessor) { "$propertyAccessor is not a property accessor." }
|
||||||
"$propertyAccessor is not a property accessor."
|
|
||||||
}
|
|
||||||
if (propertyAccessor.isSetter) {
|
if (propertyAccessor.isSetter) {
|
||||||
propertyAccessor.annotations +=
|
propertyAccessor.annotations += property.annotations
|
||||||
property.annotations
|
.filter { it.useSiteTarget == AnnotationUseSiteTarget.PROPERTY_SETTER }
|
||||||
.filter {
|
.toIrAnnotations()
|
||||||
it.useSiteTarget == AnnotationUseSiteTarget.PROPERTY_SETTER
|
val parameter = propertyAccessor.valueParameters.single()
|
||||||
}
|
parameter.annotations += property.annotations
|
||||||
.toIrAnnotations()
|
.filter { it.useSiteTarget == AnnotationUseSiteTarget.SETTER_PARAMETER }
|
||||||
propertyAccessor.valueParameters.singleOrNull()?.annotations =
|
.toIrAnnotations()
|
||||||
propertyAccessor.valueParameters.singleOrNull()?.annotations?.plus(
|
|
||||||
property.annotations
|
|
||||||
.filter {
|
|
||||||
it.useSiteTarget == AnnotationUseSiteTarget.SETTER_PARAMETER
|
|
||||||
}
|
|
||||||
.toIrAnnotations()
|
|
||||||
)!!
|
|
||||||
} else {
|
} else {
|
||||||
propertyAccessor.annotations +=
|
propertyAccessor.annotations += property.annotations
|
||||||
property.annotations
|
.filter { it.useSiteTarget == AnnotationUseSiteTarget.PROPERTY_GETTER }
|
||||||
.filter {
|
.toIrAnnotations()
|
||||||
it.useSiteTarget == AnnotationUseSiteTarget.PROPERTY_GETTER
|
}
|
||||||
}
|
propertyAccessor.extensionReceiverParameter?.let { receiver ->
|
||||||
.toIrAnnotations()
|
receiver.annotations += property.annotations
|
||||||
|
.filter { it.useSiteTarget == AnnotationUseSiteTarget.RECEIVER }
|
||||||
|
.toIrAnnotations()
|
||||||
}
|
}
|
||||||
propertyAccessor.extensionReceiverParameter?.annotations =
|
|
||||||
propertyAccessor.extensionReceiverParameter?.annotations?.plus(
|
|
||||||
property.annotations
|
|
||||||
.filter {
|
|
||||||
it.useSiteTarget == AnnotationUseSiteTarget.RECEIVER
|
|
||||||
}
|
|
||||||
.toIrAnnotations()
|
|
||||||
)!!
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+25
-25
@@ -54,36 +54,36 @@ inline val FirProperty.hasJvmFieldAnnotation: Boolean
|
|||||||
val FirAnnotationCall.isJvmFieldAnnotation: Boolean
|
val FirAnnotationCall.isJvmFieldAnnotation: Boolean
|
||||||
get() = toAnnotationClassId() == JVM_FIELD_CLASS_ID
|
get() = toAnnotationClassId() == JVM_FIELD_CLASS_ID
|
||||||
|
|
||||||
private fun FirAnnotationCall.useSiteTargetsFromMetaAnnotation(session: FirSession): List<AnnotationUseSiteTarget> {
|
fun FirAnnotationCall.useSiteTargetsFromMetaAnnotation(session: FirSession): Set<AnnotationUseSiteTarget> =
|
||||||
val metaAnnotationAboutTarget =
|
toAnnotationClass(session)?.annotations?.find { it.toAnnotationClassId() == TARGET_CLASS_ID }?.argumentList?.arguments
|
||||||
toAnnotationClass(session)?.annotations?.find { it.toAnnotationClassId() == TARGET_CLASS_ID }
|
?.toAnnotationUseSiteTargets() ?: DEFAULT_USE_SITE_TARGETS
|
||||||
?: return emptyList()
|
|
||||||
return metaAnnotationAboutTarget.argumentList.arguments.toAnnotationUseSiteTargets()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun List<FirExpression>.toAnnotationUseSiteTargets(): List<AnnotationUseSiteTarget> =
|
private fun List<FirExpression>.toAnnotationUseSiteTargets(): Set<AnnotationUseSiteTarget> =
|
||||||
flatMap { arg ->
|
flatMapTo(mutableSetOf()) { arg ->
|
||||||
val unwrappedArg = if (arg is FirNamedArgumentExpression) arg.expression else arg
|
when (val unwrappedArg = if (arg is FirNamedArgumentExpression) arg.expression else arg) {
|
||||||
if (unwrappedArg is FirArrayOfCall) {
|
is FirArrayOfCall -> unwrappedArg.argumentList.arguments.toAnnotationUseSiteTargets()
|
||||||
unwrappedArg.argumentList.arguments.toAnnotationUseSiteTargets()
|
is FirVarargArgumentsExpression -> unwrappedArg.arguments.toAnnotationUseSiteTargets()
|
||||||
} else {
|
else -> USE_SITE_TARGET_NAME_MAP[unwrappedArg.callableNameOfMetaAnnotationArgument?.identifier] ?: setOf()
|
||||||
unwrappedArg.toAnnotationUseSiteTarget()?.let { listOf(it) } ?: emptyList()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val USE_SITE_TARGET_NAME_MAP =
|
// See [org.jetbrains.kotlin.descriptors.annotations.KotlinTarget.USE_SITE_MAPPING] (it's in reverse)
|
||||||
AnnotationUseSiteTarget.values().map { it.name to it }.toMap()
|
private val USE_SITE_TARGET_NAME_MAP = mapOf(
|
||||||
|
"FIELD" to setOf(AnnotationUseSiteTarget.FIELD, AnnotationUseSiteTarget.PROPERTY_DELEGATE_FIELD),
|
||||||
|
"FILE" to setOf(AnnotationUseSiteTarget.FILE),
|
||||||
|
"PROPERTY" to setOf(AnnotationUseSiteTarget.PROPERTY),
|
||||||
|
"PROPERTY_GETTER" to setOf(AnnotationUseSiteTarget.PROPERTY_GETTER),
|
||||||
|
"PROPERTY_SETTER" to setOf(AnnotationUseSiteTarget.PROPERTY_SETTER),
|
||||||
|
"VALUE_PARAMETER" to setOf(
|
||||||
|
AnnotationUseSiteTarget.CONSTRUCTOR_PARAMETER,
|
||||||
|
AnnotationUseSiteTarget.RECEIVER,
|
||||||
|
AnnotationUseSiteTarget.SETTER_PARAMETER,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
private fun FirExpression.toAnnotationUseSiteTarget(): AnnotationUseSiteTarget? =
|
// See [org.jetbrains.kotlin.descriptors.annotations.KotlinTarget] (the second argument of each entry)
|
||||||
// TODO: depending on the context, "PARAMETER" can be mapped to either CONSTRUCTOR_PARAMETER or SETTER_PARAMETER ?
|
private val DEFAULT_USE_SITE_TARGETS: Set<AnnotationUseSiteTarget> =
|
||||||
callableNameOfMetaAnnotationArgument?.identifier?.let {
|
USE_SITE_TARGET_NAME_MAP.values.fold(setOf<AnnotationUseSiteTarget>()) { a, b -> a + b } - setOf(AnnotationUseSiteTarget.FILE)
|
||||||
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 {
|
fun FirAnnotatedDeclaration.hasAnnotation(classId: ClassId): Boolean {
|
||||||
return annotations.any { it.toAnnotationClassId() == classId }
|
return annotations.any { it.toAnnotationClassId() == classId }
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
// IGNORE_BACKEND_FIR: JVM_IR
|
|
||||||
// WITH_REFLECT
|
// WITH_REFLECT
|
||||||
// TARGET_BACKEND: JVM
|
// TARGET_BACKEND: JVM
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user