[FIR] Remove duplicate annotations from primary ctor params/properties

If an annotation doesn't specify an explicit use-site target,
previously it was added to both, the primary constructor value parameter
and the property in the FIR. Then, in FIR2IR, only the "correct" one was
added to the IR. Move up the deduplication logic into the frontend.

^KT-56177 Fixed
This commit is contained in:
Kirill Rakhman
2023-02-23 10:27:15 +01:00
committed by Space Team
parent 9268fd0e87
commit eee66ab43f
34 changed files with 1537 additions and 124 deletions
@@ -10,7 +10,7 @@ FILE: kt55286.kt
super<R|kotlin/Any|>()
}
@R|Deprecated<Base.Nested>|() public final val a: R|kotlin/String| = R|<local>/a|
public final val a: R|kotlin/String| = R|<local>/a|
public get(): R|kotlin/String|
public final class Nested : R|kotlin/Any| {
@@ -26,7 +26,7 @@ FILE: kt55286.kt
super<R|Base|>(String())
}
@R|Deprecated<Base.Nested>|() public final val b: R|kotlin/String| = R|<local>/b|
public final val b: R|kotlin/String| = R|<local>/b|
public get(): R|kotlin/String|
}
@@ -10,7 +10,7 @@ FILE: test.kt
super<R|kotlin/Any|>()
}
@R|KotlinMessage|() public final val message: R|kotlin/String| = R|<local>/message|
public final val message: R|kotlin/String| = R|<local>/message|
@PROPERTY_GETTER:R|KotlinMessage|() public get(): R|kotlin/String|
public final operator fun component1(): R|kotlin/String|
@@ -46,8 +46,6 @@ object FirNativeSharedImmutableChecker : FirBasicDeclarationChecker() {
return
}
if (declaration.source?.kind is KtFakeSourceElementKind) return
if (!context.isTopLevel) {
reporter.reportIfHasAnnotation(
declaration,
@@ -57,4 +55,4 @@ object FirNativeSharedImmutableChecker : FirBasicDeclarationChecker() {
)
}
}
}
}
@@ -72,7 +72,7 @@ fun FirRegularClass.getAllowedAnnotationTargets(session: FirSession): Set<Kotlin
fun FirClassLikeSymbol<*>.getAllowedAnnotationTargets(session: FirSession): Set<KotlinTarget> {
lazyResolveToPhase(FirResolvePhase.ANNOTATIONS_ARGUMENTS_MAPPING)
val targetAnnotation = getTargetAnnotation(session) ?: return defaultAnnotationTargets
val arguments = targetAnnotation.findArgumentByName(ParameterNames.targetAllowedTargets)?.unfoldArrayOrVararg().orEmpty()
val arguments = targetAnnotation.findArgumentByName(ParameterNames.targetAllowedTargets)?.unwrapAndFlattenArgument().orEmpty()
return arguments.mapNotNullTo(mutableSetOf()) { argument ->
val targetExpression = argument as? FirQualifiedAccessExpression
@@ -94,7 +94,7 @@ fun FirClassLikeSymbol<*>.getTargetAnnotation(session: FirSession): FirAnnotatio
}
fun FirExpression.extractClassesFromArgument(session: FirSession): List<FirRegularClassSymbol> {
return unfoldArrayOrVararg().mapNotNull {
return unwrapAndFlattenArgument().mapNotNull {
it.extractClassFromArgument(session)
}
}
@@ -178,14 +178,6 @@ fun FirAnnotationContainer.getImplicitUseSiteTargetList(context: CheckerContext)
}
}
private fun FirExpression.unfoldArrayOrVararg(): List<FirExpression> {
return when (this) {
is FirVarargArgumentsExpression -> arguments
is FirArrayOfCall -> arguments
else -> return emptyList()
}
}
fun checkRepeatedAnnotation(
annotationContainer: FirAnnotationContainer?,
annotations: List<FirAnnotation>,
@@ -162,7 +162,6 @@ object FirAnnotationChecker : FirBasicDeclarationChecker() {
context
)
} else {
if (declaration is FirProperty && declaration.source?.kind == KtFakeSourceElementKind.PropertyFromParameter) return
reporter.reportOn(
annotation.source,
FirErrors.WRONG_ANNOTATION_TARGET,
@@ -213,8 +212,6 @@ object FirAnnotationChecker : FirBasicDeclarationChecker() {
reporter.reportOn(annotation.source, FirErrors.INAPPLICABLE_PARAM_TARGET, context)
}
}
annotated is FirProperty && annotated.source?.kind == KtFakeSourceElementKind.PropertyFromParameter -> {
}
else -> reporter.reportOn(annotation.source, FirErrors.INAPPLICABLE_PARAM_TARGET, context)
}
FILE -> {
@@ -1657,7 +1657,7 @@ class Fir2IrDeclarationStorage(
annotationGenerator.generate(this, firAnnotationContainer)
if (this is IrFunction && firAnnotationContainer is FirSimpleFunction) {
valueParameters.zip(firAnnotationContainer.valueParameters).forEach { (irParameter, firParameter) ->
annotationGenerator.generate(irParameter, firParameter, isInConstructor = false)
annotationGenerator.generate(irParameter, firParameter)
}
}
}
@@ -24,6 +24,9 @@ import org.jetbrains.kotlin.ir.util.isSetter
* need special handling: [AnnotationUseSiteTarget]. In particular, [FirProperty] contains all annotations associated with that property,
* whose targets may vary. After all the necessary pieces of IR elements, e.g., backing field, are ready, this generator splits those
* annotations to the specified targets.
*
* Note: Annotations on primary constructor properties are already split between value parameters and properties in FIR. Before this change,
* it used to be done here.
*/
class AnnotationGenerator(private val components: Fir2IrComponents) : Fir2IrComponents by components {
@@ -40,32 +43,16 @@ class AnnotationGenerator(private val components: Fir2IrComponents) : Fir2IrComp
useSiteTarget ?: applicable.firstOrNull(useSiteTargetsFromMetaAnnotation(session)::contains)
companion object {
// Priority order: constructor parameter (if applicable) -> property -> field. So, for example, if `A`
// can be attached to all three, then in a declaration like
// class C(@A val x: Int) { @A val y = 1 }
// 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)
}
// TODO: third argument should be whether this parameter is a property declaration (though this probably makes no difference)
fun generate(irValueParameter: IrValueParameter, firValueParameter: FirValueParameter, isInConstructor: Boolean) {
if (isInConstructor) {
irValueParameter.annotations += firValueParameter.annotations
.filter { it.target(constructorPropertyTargets) == AnnotationUseSiteTarget.CONSTRUCTOR_PARAMETER }
.toIrAnnotations()
} else {
irValueParameter.annotations += firValueParameter.annotations.toIrAnnotations()
}
fun generate(irValueParameter: IrValueParameter, firValueParameter: FirValueParameter) {
irValueParameter.annotations += firValueParameter.annotations.toIrAnnotations()
}
fun generate(irProperty: IrProperty, property: FirProperty) {
val applicableTargets = when {
property.source?.kind == KtFakeSourceElementKind.PropertyFromParameter -> constructorPropertyTargets
irProperty.isDelegated -> delegatedPropertyTargets
else -> propertyTargets
}
val applicableTargets = if (irProperty.isDelegated) delegatedPropertyTargets else propertyTargets
irProperty.annotations += property.annotations
.filter { it.target(applicableTargets) == AnnotationUseSiteTarget.PROPERTY }
.toIrAnnotations()
@@ -73,11 +60,8 @@ class AnnotationGenerator(private val components: Fir2IrComponents) : Fir2IrComp
fun generate(irField: IrField, property: FirProperty) {
val irProperty = irField.correspondingPropertySymbol?.owner ?: throw AssertionError("$irField is not a property field")
val applicableTargets = when {
property.source?.kind == KtFakeSourceElementKind.PropertyFromParameter -> constructorPropertyTargets
irProperty.isDelegated -> delegatedPropertyTargets
else -> propertyTargets
}
val applicableTargets = if (irProperty.isDelegated) delegatedPropertyTargets else propertyTargets
irField.annotations += property.annotations.filter {
val target = it.target(applicableTargets)
target == AnnotationUseSiteTarget.FIELD || target == AnnotationUseSiteTarget.PROPERTY_DELEGATE_FIELD
@@ -110,7 +110,7 @@ internal class ClassMemberGenerator(
val annotationMode = containingClass?.classKind == ClassKind.ANNOTATION_CLASS && irFunction is IrConstructor
for ((valueParameter, firValueParameter) in irParameters.zip(firFunction.valueParameters)) {
valueParameter.setDefaultValue(firValueParameter, annotationMode)
annotationGenerator.generate(valueParameter, firValueParameter, irFunction is IrConstructor)
annotationGenerator.generate(valueParameter, firValueParameter)
}
annotationGenerator.generate(irFunction, firFunction)
}
@@ -233,6 +233,12 @@ public class FirLightTreeBlackBoxCodegenTestGenerated extends AbstractFirLightTr
runTest("compiler/testData/codegen/box/annotations/javaPropertyWithIntInitializer.kt");
}
@Test
@TestMetadata("javaTargetOnPrimaryCtorParameter.kt")
public void testJavaTargetOnPrimaryCtorParameter() throws Exception {
runTest("compiler/testData/codegen/box/annotations/javaTargetOnPrimaryCtorParameter.kt");
}
@Test
@TestMetadata("jvmAnnotationFlags.kt")
public void testJvmAnnotationFlags() throws Exception {
@@ -371,6 +377,18 @@ public class FirLightTreeBlackBoxCodegenTestGenerated extends AbstractFirLightTr
runTest("compiler/testData/codegen/box/annotations/syntheticMethodForProperty.kt");
}
@Test
@TestMetadata("targetOnPrimaryCtorParameter.kt")
public void testTargetOnPrimaryCtorParameter() throws Exception {
runTest("compiler/testData/codegen/box/annotations/targetOnPrimaryCtorParameter.kt");
}
@Test
@TestMetadata("targetOnPrimaryCtorParameterMultiModule.kt")
public void testTargetOnPrimaryCtorParameterMultiModule() throws Exception {
runTest("compiler/testData/codegen/box/annotations/targetOnPrimaryCtorParameterMultiModule.kt");
}
@Test
@TestMetadata("typeAnnotationOnJdk6.kt")
public void testTypeAnnotationOnJdk6() throws Exception {
@@ -233,6 +233,12 @@ public class FirPsiBlackBoxCodegenTestGenerated extends AbstractFirPsiBlackBoxCo
runTest("compiler/testData/codegen/box/annotations/javaPropertyWithIntInitializer.kt");
}
@Test
@TestMetadata("javaTargetOnPrimaryCtorParameter.kt")
public void testJavaTargetOnPrimaryCtorParameter() throws Exception {
runTest("compiler/testData/codegen/box/annotations/javaTargetOnPrimaryCtorParameter.kt");
}
@Test
@TestMetadata("jvmAnnotationFlags.kt")
public void testJvmAnnotationFlags() throws Exception {
@@ -371,6 +377,18 @@ public class FirPsiBlackBoxCodegenTestGenerated extends AbstractFirPsiBlackBoxCo
runTest("compiler/testData/codegen/box/annotations/syntheticMethodForProperty.kt");
}
@Test
@TestMetadata("targetOnPrimaryCtorParameter.kt")
public void testTargetOnPrimaryCtorParameter() throws Exception {
runTest("compiler/testData/codegen/box/annotations/targetOnPrimaryCtorParameter.kt");
}
@Test
@TestMetadata("targetOnPrimaryCtorParameterMultiModule.kt")
public void testTargetOnPrimaryCtorParameterMultiModule() throws Exception {
runTest("compiler/testData/codegen/box/annotations/targetOnPrimaryCtorParameterMultiModule.kt");
}
@Test
@TestMetadata("typeAnnotationOnJdk6.kt")
public void testTypeAnnotationOnJdk6() throws Exception {
@@ -8,6 +8,7 @@ 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.containingClassLookupTag
import org.jetbrains.kotlin.fir.expressions.*
import org.jetbrains.kotlin.fir.references.FirErrorNamedReference
import org.jetbrains.kotlin.fir.references.FirResolvedNamedReference
@@ -64,6 +65,7 @@ fun List<FirAnnotation>.nonSourceAnnotations(session: FirSession): List<FirAnnot
?.callableNameOfMetaAnnotationArgument == sourceName
}
}
fun FirAnnotationContainer.nonSourceAnnotations(session: FirSession): List<FirAnnotation> =
annotations.nonSourceAnnotations(session)
@@ -77,21 +79,31 @@ fun FirAnnotation.useSiteTargetsFromMetaAnnotation(session: FirSession): Set<Ann
return toAnnotationClass(session)
?.annotations
?.find { it.toAnnotationClassId(session) == StandardClassIds.Annotations.Target }
?.findArgumentByName(StandardClassIds.Annotations.ParameterNames.targetAllowedTargets)
?.unwrapVarargValue()
?.toAnnotationUseSiteTargets()
?.findUseSiteTargets()
?: DEFAULT_USE_SITE_TARGETS
}
private fun List<FirExpression>.toAnnotationUseSiteTargets(): Set<AnnotationUseSiteTarget> =
flatMapTo(mutableSetOf()) { arg ->
when (val unwrappedArg = if (arg is FirNamedArgumentExpression) arg.expression else arg) {
is FirArrayOfCall -> unwrappedArg.argumentList.arguments.toAnnotationUseSiteTargets()
is FirVarargArgumentsExpression -> unwrappedArg.arguments.toAnnotationUseSiteTargets()
else -> USE_SITE_TARGET_NAME_MAP[unwrappedArg.callableNameOfMetaAnnotationArgument?.identifier] ?: setOf()
private fun FirAnnotation.findUseSiteTargets(): Set<AnnotationUseSiteTarget> = buildSet {
fun addIfMatching(arg: FirExpression) {
if (arg !is FirQualifiedAccessExpression) return
val callableSymbol = arg.calleeReference.toResolvedCallableSymbol() ?: return
if (callableSymbol.containingClassLookupTag()?.classId == StandardClassIds.AnnotationTarget) {
USE_SITE_TARGET_NAME_MAP[callableSymbol.callableId.callableName.identifier]?.let { addAll(it) }
}
}
if (this@findUseSiteTargets is FirAnnotationCall) {
for (arg in argumentList.arguments) {
arg.unwrapAndFlattenArgument().forEach(::addIfMatching)
}
} else {
argumentMapping.mapping[StandardClassIds.Annotations.ParameterNames.targetAllowedTargets]
?.unwrapAndFlattenArgument()
?.forEach(::addIfMatching)
}
}
// See [org.jetbrains.kotlin.descriptors.annotations.KotlinTarget.USE_SITE_MAPPING] (it's in reverse)
private val USE_SITE_TARGET_NAME_MAP = mapOf(
"FIELD" to setOf(AnnotationUseSiteTarget.FIELD, AnnotationUseSiteTarget.PROPERTY_DELEGATE_FIELD),
@@ -19,6 +19,7 @@ abstract class FirTypeResolver : FirSessionComponent {
scopeClassDeclaration: ScopeClassDeclaration,
areBareTypesAllowed: Boolean,
isOperandOfIsOperator: Boolean,
resolveDeprecations: Boolean,
// Note: sometimes we don't have useSiteFile in IDE context
useSiteFile: FirFile?,
supertypeSupplier: SupertypeSupplier
@@ -82,7 +82,8 @@ class FirTypeResolverImpl(private val session: FirSession) : FirTypeResolver() {
typeRef: FirUserTypeRef,
scopeClassDeclaration: ScopeClassDeclaration,
useSiteFile: FirFile?,
supertypeSupplier: SupertypeSupplier
supertypeSupplier: SupertypeSupplier,
resolveDeprecations: Boolean
): TypeResolutionResult {
val qualifierResolver = session.qualifierResolver
var applicability: CandidateApplicability? = null
@@ -101,10 +102,12 @@ class FirTypeResolverImpl(private val session: FirSession) : FirTypeResolver() {
diagnostic = ConeVisibilityError(symbol)
}
val deprecation = symbol.getDeprecation(session, useSiteFile)
if (deprecation != null && deprecation.deprecationLevel == DeprecationLevelValue.HIDDEN) {
symbolApplicability = minOf(CandidateApplicability.HIDDEN, symbolApplicability)
diagnostic = null
if (resolveDeprecations) {
val deprecation = symbol.getDeprecation(session, useSiteFile)
if (deprecation != null && deprecation.deprecationLevel == DeprecationLevelValue.HIDDEN) {
symbolApplicability = minOf(CandidateApplicability.HIDDEN, symbolApplicability)
diagnostic = null
}
}
if (applicability == null || symbolApplicability > applicability!!) {
@@ -491,13 +494,14 @@ class FirTypeResolverImpl(private val session: FirSession) : FirTypeResolver() {
scopeClassDeclaration: ScopeClassDeclaration,
areBareTypesAllowed: Boolean,
isOperandOfIsOperator: Boolean,
resolveDeprecations: Boolean,
useSiteFile: FirFile?,
supertypeSupplier: SupertypeSupplier
): Pair<ConeKotlinType, ConeDiagnostic?> {
return when (typeRef) {
is FirResolvedTypeRef -> error("Do not resolve, resolved type-refs")
is FirUserTypeRef -> {
val result = resolveUserTypeToSymbol(typeRef, scopeClassDeclaration, useSiteFile, supertypeSupplier)
val result = resolveUserTypeToSymbol(typeRef, scopeClassDeclaration, useSiteFile, supertypeSupplier, resolveDeprecations)
resolveUserType(
typeRef,
result,
@@ -26,6 +26,7 @@ import org.jetbrains.kotlin.fir.types.builder.buildResolvedTypeRef
class FirSpecificTypeResolverTransformer(
override val session: FirSession,
private val errorTypeAsResolved: Boolean = true,
private val resolveDeprecations: Boolean = true,
private val supertypeSupplier: SupertypeSupplier = SupertypeSupplier.Default
) : FirAbstractTreeTransformer<ScopeClassDeclaration>(phase = FirResolvePhase.SUPER_TYPES) {
private val typeResolver = session.typeResolver
@@ -85,6 +86,7 @@ class FirSpecificTypeResolverTransformer(
data,
areBareTypesAllowed,
isOperandOfIsOperator,
resolveDeprecations,
currentFile,
supertypeSupplier
)
@@ -104,6 +106,7 @@ class FirSpecificTypeResolverTransformer(
data,
areBareTypesAllowed,
isOperandOfIsOperator,
resolveDeprecations,
currentFile,
supertypeSupplier
)
@@ -7,7 +7,9 @@ package org.jetbrains.kotlin.fir.resolve.transformers
import kotlinx.collections.immutable.toImmutableList
import org.jetbrains.kotlin.KtFakeSourceElementKind
import org.jetbrains.kotlin.descriptors.annotations.AnnotationUseSiteTarget
import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.correspondingProperty
import org.jetbrains.kotlin.fir.copyWithNewSourceKind
import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.fir.declarations.utils.isFromVararg
@@ -115,8 +117,18 @@ open class FirTypeResolveTransformer(
override fun transformConstructor(constructor: FirConstructor, data: Any?): FirConstructor = whileAnalysing(session, constructor) {
return withScopeCleanup {
constructor.addTypeParametersScope()
transformDeclaration(constructor, data)
} as FirConstructor
val result = transformDeclaration(constructor, data) as FirConstructor
if (result.isPrimary) {
for (valueParameter in result.valueParameters) {
if (valueParameter.correspondingProperty != null) {
valueParameter.removeDuplicateAnnotationsOfPrimaryConstructorElement()
}
}
}
result
}
}
override fun transformTypeAlias(typeAlias: FirTypeAlias, data: Any?): FirTypeAlias = whileAnalysing(session, typeAlias) {
@@ -170,6 +182,10 @@ open class FirTypeResolveTransformer(
unboundCyclesInTypeParametersSupertypes(property)
if (property.source?.kind == KtFakeSourceElementKind.PropertyFromParameter) {
property.removeDuplicateAnnotationsOfPrimaryConstructorElement()
}
property
}
}
@@ -380,4 +396,30 @@ open class FirTypeResolveTransformer(
scopes.add(FirMemberTypeParameterScope(this))
}
}
/**
* In a scenario like
*
* ```
* annotation class Ann
* class Foo(@Ann val x: String)
* ```
*
* both, the primary ctor value parameter and the property `x` will be annotated with `@Ann`. This is due to the fact, that the
* annotation needs to be resolved in order to determine its annotation targets. We remove annotations from the wrong target if they
* don't explicitly specify the use-site target (in which case they shouldn't have been added to the element in the raw FIR).
*
* For value parameters, we remove the annotation if the targets don't include [AnnotationUseSiteTarget.CONSTRUCTOR_PARAMETER].
* For properties, we remove the annotation, if the targets include [AnnotationUseSiteTarget.CONSTRUCTOR_PARAMETER].
*/
private fun FirVariable.removeDuplicateAnnotationsOfPrimaryConstructorElement() {
val isParameter = this is FirValueParameter
replaceAnnotations(annotations.filter {
it.useSiteTarget != null ||
// equivalent to
// CONSTRUCTOR_PARAMETER in targets && isParameter ||
// CONSTRUCTOR_PARAMETER !in targets && !isParameter
AnnotationUseSiteTarget.CONSTRUCTOR_PARAMETER in it.useSiteTargetsFromMetaAnnotation(session) == isParameter
})
}
}
@@ -7,41 +7,48 @@ package org.jetbrains.kotlin.fir.resolve.transformers.plugin
import kotlinx.collections.immutable.PersistentList
import kotlinx.collections.immutable.persistentListOf
import org.jetbrains.kotlin.fir.FirAnnotationContainer
import org.jetbrains.kotlin.fir.FirElement
import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.PrivateForInline
import org.jetbrains.kotlin.fir.*
import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.fir.expressions.FirAnnotation
import org.jetbrains.kotlin.fir.expressions.FirAnnotationCall
import org.jetbrains.kotlin.fir.expressions.FirAnnotationResolvePhase
import org.jetbrains.kotlin.fir.expressions.FirStatement
import org.jetbrains.kotlin.fir.expressions.*
import org.jetbrains.kotlin.fir.expressions.builder.buildResolvedQualifier
import org.jetbrains.kotlin.fir.extensions.*
import org.jetbrains.kotlin.fir.references.builder.buildResolvedNamedReference
import org.jetbrains.kotlin.fir.references.impl.FirSimpleNamedReference
import org.jetbrains.kotlin.fir.resolve.ResolutionMode
import org.jetbrains.kotlin.fir.resolve.ScopeSession
import org.jetbrains.kotlin.fir.resolve.transformers.DesignationState
import org.jetbrains.kotlin.fir.resolve.transformers.FirSpecificTypeResolverTransformer
import org.jetbrains.kotlin.fir.resolve.transformers.ScopeClassDeclaration
import org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.FirAbstractBodyResolveTransformerDispatcher
import org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.FirDeclarationsResolveTransformer
import org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.FirExpressionsResolveTransformer
import org.jetbrains.kotlin.fir.resolve.transformers.plugin.CompilerRequiredAnnotationsHelper.REQUIRED_ANNOTATIONS
import org.jetbrains.kotlin.fir.resolve.transformers.plugin.CompilerRequiredAnnotationsHelper.REQUIRED_ANNOTATIONS_WITH_ARGUMENTS
import org.jetbrains.kotlin.fir.resolve.transformers.withClassDeclarationCleanup
import org.jetbrains.kotlin.fir.scopes.FirScope
import org.jetbrains.kotlin.fir.scopes.createImportingScopes
import org.jetbrains.kotlin.fir.scopes.getProperties
import org.jetbrains.kotlin.fir.scopes.getSingleClassifier
import org.jetbrains.kotlin.fir.scopes.impl.FirAbstractImportingScope
import org.jetbrains.kotlin.fir.symbols.impl.FirCallableSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirClassSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirEnumEntrySymbol
import org.jetbrains.kotlin.fir.types.*
import org.jetbrains.kotlin.fir.types.builder.buildPlaceholderProjection
import org.jetbrains.kotlin.fir.types.builder.buildStarProjection
import org.jetbrains.kotlin.fir.types.builder.buildTypeProjectionWithVariance
import org.jetbrains.kotlin.fir.types.builder.buildUserTypeRef
import org.jetbrains.kotlin.fir.types.builder.*
import org.jetbrains.kotlin.fir.types.impl.ConeClassLikeTypeImpl
import org.jetbrains.kotlin.fir.types.impl.FirImplicitUnitTypeRef
import org.jetbrains.kotlin.fir.types.impl.FirQualifierPartImpl
import org.jetbrains.kotlin.fir.types.impl.FirTypeArgumentListImpl
import org.jetbrains.kotlin.fir.visitors.FirDefaultTransformer
import org.jetbrains.kotlin.fir.visitors.transformSingle
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.name.StandardClassIds.Annotations.Deprecated
import org.jetbrains.kotlin.name.StandardClassIds.Annotations.DeprecatedSinceKotlin
import org.jetbrains.kotlin.name.StandardClassIds.Annotations.JvmRecord
import org.jetbrains.kotlin.name.StandardClassIds.Annotations.WasExperimental
import org.jetbrains.kotlin.name.StandardClassIds.Annotations.Target
import org.jetbrains.kotlin.utils.addToStdlib.shouldNotBeCalled
/**
@@ -50,6 +57,7 @@ import org.jetbrains.kotlin.utils.addToStdlib.shouldNotBeCalled
object CompilerRequiredAnnotationsHelper {
internal val REQUIRED_ANNOTATIONS_WITH_ARGUMENTS: Set<ClassId> = setOf(
Deprecated,
Target,
)
val REQUIRED_ANNOTATIONS: Set<ClassId> = REQUIRED_ANNOTATIONS_WITH_ARGUMENTS + setOf(
@@ -69,6 +77,112 @@ internal abstract class AbstractFirSpecificAnnotationResolveTransformer(
private val REQUIRED_ANNOTATION_NAMES: Set<Name> = REQUIRED_ANNOTATIONS.mapTo(mutableSetOf()) { it.shortClassName }
}
inner class FirEnumAnnotationArgumentsTransformerDispatcher : FirAbstractBodyResolveTransformerDispatcher(
session,
FirResolvePhase.COMPILER_REQUIRED_ANNOTATIONS,
scopeSession = scopeSession,
implicitTypeOnly = false,
) {
override val expressionsTransformer: FirExpressionsResolveTransformer = FirEnumAnnotationArgumentsTransformer(this)
override val declarationsTransformer: FirDeclarationsResolveTransformer get() = throw NotImplementedError()
}
/**
* Special transformer that resolves qualified expressions exclusively to enums from import scope. This doesn't
* trigger body resolve.
*/
private inner class FirEnumAnnotationArgumentsTransformer(transformer: FirAbstractBodyResolveTransformerDispatcher) :
AbstractFirExpressionsResolveTransformerForAnnotations(transformer) {
override fun transformFunctionCall(functionCall: FirFunctionCall, data: ResolutionMode): FirStatement {
// transform arrayOf arguments to handle `@Foo(bar = arrayOf(X))`
functionCall.transformChildren(transformer, data)
return super.transformFunctionCall(functionCall, data)
}
override fun resolveQualifiedAccessAndSelectCandidate(
qualifiedAccessExpression: FirQualifiedAccessExpression,
isUsedAsReceiver: Boolean,
callSite: FirElement
): FirStatement {
qualifiedAccessExpression.resolveFromImportScope()
return qualifiedAccessExpression
}
private fun FirQualifiedAccessExpression.resolveFromImportScope() {
val calleeReference = calleeReference as? FirSimpleNamedReference ?: return
val receiver = explicitReceiver as? FirQualifiedAccessExpression
if (receiver != null) {
// Simple case X.Y or fully qualified case a.b.X.Y
// Resolve receiver from import scope.
val receiverCalleeReference = receiver.calleeReference as? FirSimpleNamedReference ?: return
val receiverName = receiverCalleeReference.name.takeIf { !it.isSpecial } ?: return
val symbol = scopes.filterIsInstance<FirAbstractImportingScope>().firstNotNullOfOrNull {
it.getSingleClassifier(receiverName) as? FirClassSymbol<*>
} ?: return
// If fully qualified, check that given package name matches the resolved one.
val segments = generateSequence(receiver.explicitReceiver) { (it as? FirQualifiedAccessExpression)?.explicitReceiver }
.mapNotNull { (it.calleeReference as? FirSimpleNamedReference)?.name?.identifier }
.toList()
if (segments.isNotEmpty() && FqName.fromSegments(segments.asReversed()) != symbol.classId.packageFqName) {
return
}
val resolvedReceiver = buildResolvedQualifier {
source = receiver.source
packageFqName = symbol.classId.packageFqName
relativeClassFqName = symbol.classId.relativeClassName
typeRef = FirImplicitUnitTypeRef(receiver.typeRef.source)
this.symbol = symbol
}
// Resolve enum entry by name from the declarations of the receiver.
val calleeSymbol = symbol.fir.declarations.firstOrNull {
it is FirEnumEntry && it.name == calleeReference.name
}?.symbol as? FirEnumEntrySymbol ?: return
updateCallee(calleeReference, calleeSymbol)
replaceExplicitReceiver(resolvedReceiver)
replaceDispatchReceiver(resolvedReceiver)
} else {
// Case where enum entry is explicitly imported.
val calleeSymbol = scopes.firstNotNullOfOrNull {
it.getProperties(calleeReference.name).firstOrNull()
} as? FirEnumEntrySymbol ?: return
updateCallee(calleeReference, calleeSymbol)
}
}
private fun FirQualifiedAccessExpression.updateCallee(
calleeReference: FirSimpleNamedReference,
calleeSymbol: FirEnumEntrySymbol
) {
session.lookupTracker?.recordLookup(
calleeReference.name,
calleeSymbol.dispatchReceiverType?.classId?.asFqNameString() ?: calleeSymbol.callableId.packageName.asString(),
this.source,
context.file.source,
)
replaceCalleeReference(buildResolvedNamedReference {
source = calleeReference.source
name = calleeReference.name
resolvedSymbol = calleeSymbol
})
calleeSymbol.containingClassLookupTag()
?.let { ConeClassLikeTypeImpl(it, emptyArray(), false) }
?.let { replaceTypeRef(typeRef.resolvedTypeFromPrototype(it)) }
}
}
private val predicateBasedProvider = session.predicateBasedProvider
private val annotationsFromPlugins: Set<AnnotationFqn> = session.registeredPluginAnnotations.annotations
@@ -80,15 +194,12 @@ internal abstract class AbstractFirSpecificAnnotationResolveTransformer(
@PrivateForInline
val typeResolverTransformer: FirSpecificTypeResolverTransformer = FirSpecificTypeResolverTransformer(
session,
errorTypeAsResolved = false
errorTypeAsResolved = false,
resolveDeprecations = false,
)
@PrivateForInline
val argumentsTransformer = FirAnnotationArgumentsResolveTransformer(
session,
scopeSession,
FirResolvePhase.COMPILER_REQUIRED_ANNOTATIONS
)
val argumentsTransformer = FirEnumAnnotationArgumentsTransformerDispatcher()
private var owners: PersistentList<FirDeclaration> = persistentListOf()
private val classDeclarationsStack = ArrayDeque<FirClass>().apply {
@@ -139,9 +139,8 @@ private class FirDeclarationsResolveTransformerForArgumentAnnotations(
}
}
private class FirExpressionsResolveTransformerForSpecificAnnotations(
transformer: FirAbstractBodyResolveTransformerDispatcher
) : FirExpressionsResolveTransformer(transformer) {
abstract class AbstractFirExpressionsResolveTransformerForAnnotations(transformer: FirAbstractBodyResolveTransformerDispatcher) :
FirExpressionsResolveTransformer(transformer) {
override fun transformAnnotation(annotation: FirAnnotation, data: ResolutionMode): FirStatement {
dataFlowAnalyzer.enterAnnotation()
@@ -166,13 +165,11 @@ private class FirExpressionsResolveTransformerForSpecificAnnotations(
return calleeReference !is FirErrorNamedReference
}
override fun resolveQualifiedAccessAndSelectCandidate(
abstract override fun resolveQualifiedAccessAndSelectCandidate(
qualifiedAccessExpression: FirQualifiedAccessExpression,
isUsedAsReceiver: Boolean,
callSite: FirElement,
): FirStatement {
return callResolver.resolveOnlyEnumOrQualifierAccessAndSelectCandidate(qualifiedAccessExpression, isUsedAsReceiver)
}
): FirStatement
override fun transformFunctionCall(functionCall: FirFunctionCall, data: ResolutionMode): FirStatement {
return functionCall
@@ -254,3 +251,16 @@ private class FirExpressionsResolveTransformerForSpecificAnnotations(
return false
}
}
private class FirExpressionsResolveTransformerForSpecificAnnotations(transformer: FirAbstractBodyResolveTransformerDispatcher) :
AbstractFirExpressionsResolveTransformerForAnnotations(transformer) {
override fun resolveQualifiedAccessAndSelectCandidate(
qualifiedAccessExpression: FirQualifiedAccessExpression,
isUsedAsReceiver: Boolean,
callSite: FirElement,
): FirStatement {
return callResolver.resolveOnlyEnumOrQualifierAccessAndSelectCandidate(qualifiedAccessExpression, isUsedAsReceiver)
}
}
@@ -128,6 +128,16 @@ fun <T : FirStatement> FirBlock.replaceFirstStatement(factory: (T) -> FirStateme
fun FirExpression.unwrapArgument(): FirExpression = (this as? FirWrappedArgumentExpression)?.expression ?: this
fun FirExpression.unwrapAndFlattenArgument(): List<FirExpression> = buildList { unwrapAndFlattenArgumentTo(this) }
private fun FirExpression.unwrapAndFlattenArgumentTo(list: MutableList<FirExpression>) {
when (val unwrapped = unwrapArgument()) {
is FirArrayOfCall, is FirFunctionCall -> (unwrapped as FirCall).arguments.forEach { it.unwrapAndFlattenArgumentTo(list) }
is FirVarargArgumentsExpression -> unwrapped.arguments.forEach { it.unwrapAndFlattenArgumentTo(list) }
else -> list.add(unwrapped)
}
}
val FirVariableAssignment.explicitReceiver: FirExpression? get() = unwrapLValue()?.explicitReceiver
val FirVariableAssignment.dispatchReceiver: FirExpression get() = unwrapLValue()?.dispatchReceiver ?: FirNoReceiverExpression