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:
pyos
2020-11-27 14:13:04 +01:00
committed by TeamCityServer
parent 4817d5e01d
commit 50ae360ff9
3 changed files with 81 additions and 89 deletions
@@ -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()
)!!
} }
} }
@@ -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