diff --git a/compiler/fir/analysis-tests/testData/resolve/arguments/argumentsOfJavaAnnotation.kt b/compiler/fir/analysis-tests/testData/resolve/arguments/argumentsOfJavaAnnotation.kt index 6cc468f8ad9..4e128d99842 100644 --- a/compiler/fir/analysis-tests/testData/resolve/arguments/argumentsOfJavaAnnotation.kt +++ b/compiler/fir/analysis-tests/testData/resolve/arguments/argumentsOfJavaAnnotation.kt @@ -9,7 +9,7 @@ public @interface Ann { // FILE: main.kt @Ann(x = 10, s = "") -@Ann(10, "") -@Ann(x = 10, s = "", y = 10) +@Ann(10, "") +@Ann(x = 10, s = "", y = 10) class A diff --git a/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirDiagnosticsList.kt b/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirDiagnosticsList.kt index d9e1e0b9727..2defbd77a5e 100644 --- a/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirDiagnosticsList.kt +++ b/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirDiagnosticsList.kt @@ -267,6 +267,8 @@ object DIAGNOSTICS_LIST : DiagnosticList("FirErrors") { parameter("useSiteDescription") } val INAPPLICABLE_FILE_TARGET by error(PositioningStrategy.ANNOTATION_USE_SITE) + val REPEATED_ANNOTATION by error() + val REPEATED_ANNOTATION_WARNING by warning() } val EXPERIMENTAL by object : DiagnosticGroup("OptIn-related") { diff --git a/compiler/fir/checkers/checkers.jvm/src/org/jetbrains/kotlin/fir/analysis/jvm/checkers/declaration/FirRepeatableAnnotationChecker.kt b/compiler/fir/checkers/checkers.jvm/src/org/jetbrains/kotlin/fir/analysis/jvm/checkers/declaration/FirRepeatableAnnotationChecker.kt index 274ac74c4f3..b932c15ed4c 100644 --- a/compiler/fir/checkers/checkers.jvm/src/org/jetbrains/kotlin/fir/analysis/jvm/checkers/declaration/FirRepeatableAnnotationChecker.kt +++ b/compiler/fir/checkers/checkers.jvm/src/org/jetbrains/kotlin/fir/analysis/jvm/checkers/declaration/FirRepeatableAnnotationChecker.kt @@ -7,14 +7,12 @@ package org.jetbrains.kotlin.fir.analysis.jvm.checkers.declaration import org.jetbrains.kotlin.builtins.StandardNames import org.jetbrains.kotlin.config.LanguageFeature +import org.jetbrains.kotlin.descriptors.annotations.AnnotationUseSiteTarget import org.jetbrains.kotlin.descriptors.annotations.KotlinTarget -import org.jetbrains.kotlin.fir.FirSession import org.jetbrains.kotlin.fir.FirSourceElement +import org.jetbrains.kotlin.fir.analysis.checkers.* import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext import org.jetbrains.kotlin.fir.analysis.checkers.declaration.FirAnnotatedDeclarationChecker -import org.jetbrains.kotlin.fir.analysis.checkers.getAllowedAnnotationTargets -import org.jetbrains.kotlin.fir.analysis.checkers.getRetention -import org.jetbrains.kotlin.fir.analysis.checkers.unsubstitutedScope import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter import org.jetbrains.kotlin.fir.analysis.diagnostics.jvm.FirJvmErrors import org.jetbrains.kotlin.fir.analysis.diagnostics.reportOn @@ -44,7 +42,7 @@ object FirRepeatableAnnotationChecker : FirAnnotatedDeclarationChecker() { private val REPEATABLE_ANNOTATION_CONTAINER_NAME = Name.identifier(JvmAbi.REPEATABLE_ANNOTATION_CONTAINER_NAME) override fun check(declaration: FirAnnotatedDeclaration, context: CheckerContext, reporter: DiagnosticReporter) { - val annotationsSet = hashSetOf() + val annotationsMap = hashMapOf>() val session = context.session val annotations = declaration.annotations @@ -54,9 +52,13 @@ object FirRepeatableAnnotationChecker : FirAnnotatedDeclarationChecker() { if (annotationClassId.isLocal) continue val annotationClass = session.symbolProvider.getClassLikeSymbolByClassId(annotationClassId) ?: continue - // TODO: consider REPEATED_ANNOTATION ? - if (fqName in annotationsSet && - annotationClass.isRepeatableAnnotation(session) && + val useSiteTarget = annotation.useSiteTarget + val existingTargetsForAnnotation = annotationsMap.getOrPut(annotation.annotationTypeRef.coneType) { arrayListOf() } + val duplicateAnnotation = useSiteTarget in existingTargetsForAnnotation || + existingTargetsForAnnotation.any { (it == null) != (useSiteTarget == null) } + + if (duplicateAnnotation && + annotationClass.containsRepeatableAnnotation(session) && annotationClass.getAnnotationRetention() != AnnotationRetention.SOURCE ) { if (context.isJvm6()) { @@ -80,22 +82,44 @@ object FirRepeatableAnnotationChecker : FirAnnotatedDeclarationChecker() { } } - annotationsSet.add(fqName) + existingTargetsForAnnotation.add(useSiteTarget) } if (declaration is FirRegularClass) { val javaRepeatable = annotations.find { it.fqName(session) == JAVA_REPEATABLE_ANNOTATION } if (javaRepeatable != null) { - checkJavaRepeatableAnnotationDeclaration(javaRepeatable, declaration, context, reporter) + withSuppressedDiagnostics(javaRepeatable, context) { + checkJavaRepeatableAnnotationDeclaration(javaRepeatable, declaration, context, reporter) + } } else { val kotlinRepeatable = annotations.find { it.fqName(session) == StandardNames.FqNames.repeatable } if (kotlinRepeatable != null) { - checkKotlinRepeatableAnnotationDeclaration(kotlinRepeatable, declaration, context, reporter) + withSuppressedDiagnostics(kotlinRepeatable, context) { + checkKotlinRepeatableAnnotationDeclaration(kotlinRepeatable, declaration, context, reporter) + } } } } } + private fun FirClassLikeSymbol<*>.resolveContainerAnnotation(): FqName? { + val repeatableAnnotation = + getAnnotationByFqName(StandardNames.FqNames.repeatable) ?: getAnnotationByFqName(JAVA_REPEATABLE_ANNOTATION) ?: return null + return repeatableAnnotation.resolveContainerAnnotation()?.asSingleFqName() + } + + private fun FirAnnotationCall.resolveContainerAnnotation(): ClassId? { + val value = findArgumentByName(REPEATABLE_PARAMETER_NAME) ?: return null + val classCallArgument = (value as? FirGetClassCall)?.argument ?: return null + if (classCallArgument is FirResolvedQualifier) { + return classCallArgument.classId + } else if (classCallArgument is FirClassReferenceExpression) { + val type = classCallArgument.classTypeRef.coneType.lowerBoundIfFlexible() as? ConeClassLikeType ?: return null + return type.lookupTag.classId + } + return null + } + private fun checkJavaRepeatableAnnotationDeclaration( javaRepeatable: FirAnnotationCall, annotationClass: FirRegularClass, @@ -231,34 +255,4 @@ object FirRepeatableAnnotationChecker : FirAnnotatedDeclarationChecker() { } } } - - private fun FirClassLikeSymbol<*>.isRepeatableAnnotation(session: FirSession): Boolean { - if (getAnnotationByFqName(StandardNames.FqNames.repeatable) != null) return true - if (getAnnotationByFqName(JAVA_REPEATABLE_ANNOTATION) == null) return false - - return session.languageVersionSettings.supportsFeature(LanguageFeature.RepeatableAnnotations) || - getAnnotationRetention() == AnnotationRetention.SOURCE - } - - private fun FirClassLikeSymbol<*>.getAnnotationRetention(): AnnotationRetention { - return getAnnotationByFqName(StandardNames.FqNames.retention)?.getRetention() ?: AnnotationRetention.RUNTIME - } - - private fun FirClassLikeSymbol<*>.resolveContainerAnnotation(): FqName? { - val repeatableAnnotation = - getAnnotationByFqName(StandardNames.FqNames.repeatable) ?: getAnnotationByFqName(JAVA_REPEATABLE_ANNOTATION) ?: return null - return repeatableAnnotation.resolveContainerAnnotation()?.asSingleFqName() - } - - private fun FirAnnotationCall.resolveContainerAnnotation(): ClassId? { - val value = findArgumentByName(REPEATABLE_PARAMETER_NAME) ?: return null - val classCallArgument = (value as? FirGetClassCall)?.argument ?: return null - if (classCallArgument is FirResolvedQualifier) { - return classCallArgument.classId - } else if (classCallArgument is FirClassReferenceExpression) { - val type = classCallArgument.classTypeRef.coneType.lowerBoundIfFlexible() as? ConeClassLikeType ?: return null - return type.lookupTag.classId - } - return null - } } \ No newline at end of file diff --git a/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt b/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt index 9f68464cf17..66fe7052947 100644 --- a/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt +++ b/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt @@ -234,6 +234,8 @@ object FirErrors { val INAPPLICABLE_PARAM_TARGET by error0() val REDUNDANT_ANNOTATION_TARGET by warning1() val INAPPLICABLE_FILE_TARGET by error0(SourceElementPositioningStrategies.ANNOTATION_USE_SITE) + val REPEATED_ANNOTATION by error0() + val REPEATED_ANNOTATION_WARNING by warning0() // OptIn-related val EXPERIMENTAL_API_USAGE by warning2(SourceElementPositioningStrategies.REFERENCE_BY_QUALIFIED) diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/FirAnnotationHelpers.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/FirAnnotationHelpers.kt index 650c4853bba..6999392881d 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/FirAnnotationHelpers.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/FirAnnotationHelpers.kt @@ -6,13 +6,24 @@ package org.jetbrains.kotlin.fir.analysis.checkers import org.jetbrains.kotlin.builtins.StandardNames +import org.jetbrains.kotlin.config.LanguageFeature +import org.jetbrains.kotlin.descriptors.annotations.AnnotationUseSiteTarget import org.jetbrains.kotlin.descriptors.annotations.KotlinTarget import org.jetbrains.kotlin.fir.FirAnnotationContainer +import org.jetbrains.kotlin.fir.FirFakeSourceElementKind import org.jetbrains.kotlin.fir.FirSession +import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext +import org.jetbrains.kotlin.fir.analysis.checkers.context.findClosest +import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter +import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors +import org.jetbrains.kotlin.fir.analysis.diagnostics.reportOn import org.jetbrains.kotlin.fir.declarations.* +import org.jetbrains.kotlin.fir.declarations.impl.FirPrimaryConstructor import org.jetbrains.kotlin.fir.expressions.* +import org.jetbrains.kotlin.fir.languageVersionSettings import org.jetbrains.kotlin.fir.references.FirResolvedNamedReference import org.jetbrains.kotlin.fir.resolve.fullyExpandedType +import org.jetbrains.kotlin.fir.resolve.symbolProvider import org.jetbrains.kotlin.fir.resolve.toSymbol import org.jetbrains.kotlin.fir.symbols.ensureResolved import org.jetbrains.kotlin.fir.symbols.impl.FirClassLikeSymbol @@ -21,10 +32,15 @@ import org.jetbrains.kotlin.fir.types.ConeClassLikeType import org.jetbrains.kotlin.fir.types.FirErrorTypeRef import org.jetbrains.kotlin.fir.types.coneType import org.jetbrains.kotlin.name.ClassId +import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.resolve.UseSiteTargetsList private val RETENTION_PARAMETER_NAME = Name.identifier("value") private val TARGET_PARAMETER_NAME = Name.identifier("allowedTargets") +private val JAVA_REPEATABLE_ANNOTATION = FqName("java.lang.annotation.Repeatable") +private val JAVA_REPEATABLE_ANNOTATION_CLASS_ID = ClassId.topLevel(JAVA_REPEATABLE_ANNOTATION) +private val JVM_REPEATABLE_ANNOTATION_CLASS_ID = ClassId.fromString("kotlin/jvm/JvmRepeatable") fun FirRegularClass.getRetention(): AnnotationRetention { return getRetentionAnnotation()?.getRetention() ?: AnnotationRetention.RUNTIME @@ -90,6 +106,69 @@ fun FirExpression.extractClassesFromArgument(): List { } } +fun checkRepeatedAnnotation( + useSiteTarget: AnnotationUseSiteTarget?, + existingTargetsForAnnotation: MutableList, + annotation: FirAnnotationCall, + context: CheckerContext, + reporter: DiagnosticReporter, +) { + val duplicated = useSiteTarget in existingTargetsForAnnotation + || existingTargetsForAnnotation.any { (it == null) != (useSiteTarget == null) } + if (duplicated && !annotation.isRepeatable(context.session) && annotation.source?.kind !is FirFakeSourceElementKind) { + reporter.reportOn(annotation.source, FirErrors.REPEATED_ANNOTATION, context) + } +} + +fun FirAnnotationCall.isRepeatable(session: FirSession): Boolean { + val annotationClassId = this.toAnnotationClassId() ?: return false + if (annotationClassId.isLocal) return false + val annotationClass = session.symbolProvider.getClassLikeSymbolByFqName(annotationClassId) ?: return false + + return annotationClass.containsRepeatableAnnotation(session) +} + +fun FirClassLikeSymbol<*>.containsRepeatableAnnotation(session: FirSession): Boolean { + if (getAnnotationByClassId(StandardNames.FqNames.repeatableClassId) != null) return true + if (getAnnotationByClassId(JAVA_REPEATABLE_ANNOTATION_CLASS_ID) != null || + getAnnotationByClassId(JVM_REPEATABLE_ANNOTATION_CLASS_ID) != null + ) { + return session.languageVersionSettings.supportsFeature(LanguageFeature.RepeatableAnnotations) || + getAnnotationRetention() == AnnotationRetention.SOURCE && origin == FirDeclarationOrigin.Java + } + return false +} + +fun FirClassLikeSymbol<*>.getAnnotationRetention(): AnnotationRetention { + return getAnnotationByFqName(StandardNames.FqNames.retention)?.getRetention() ?: AnnotationRetention.RUNTIME +} + +fun FirAnnotationContainer.getDefaultUseSiteTarget( + annotation: FirAnnotationCall, + context: CheckerContext +): AnnotationUseSiteTarget? { + return getImplicitUseSiteTargetList(context).firstOrNull { + KotlinTarget.USE_SITE_MAPPING[it] in annotation.getAllowedAnnotationTargets(context.session) + } +} + +fun FirAnnotationContainer.getImplicitUseSiteTargetList(context: CheckerContext): List { + return when (this) { + is FirValueParameter -> { + return if (context.findClosest() is FirPrimaryConstructor) + UseSiteTargetsList.T_CONSTRUCTOR_PARAMETER + else + emptyList() + } + is FirProperty -> + if (!isLocal) UseSiteTargetsList.T_PROPERTY else emptyList() + is FirPropertyAccessor -> + if (isGetter) listOf(AnnotationUseSiteTarget.PROPERTY_GETTER) else listOf(AnnotationUseSiteTarget.PROPERTY_SETTER) + else -> + emptyList() + } +} + private fun FirExpression.unfoldArrayOrVararg(): List { return when (this) { is FirVarargArgumentsExpression -> arguments diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/SourceNavigator.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/SourceNavigator.kt index 8b7f595e015..d9911008dae 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/SourceNavigator.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/SourceNavigator.kt @@ -5,6 +5,7 @@ package org.jetbrains.kotlin.fir.analysis.checkers +import com.intellij.lang.LighterASTNode import com.intellij.psi.PsiElement import com.intellij.psi.PsiNameIdentifierOwner import com.intellij.psi.impl.source.tree.LeafPsiElement @@ -74,13 +75,10 @@ open class LightTreeSourceNavigator : SourceNavigator { } override fun FirSourceElement.getRawIdentifier(): String? { - val tokenType = elementType - return if (tokenType is KtNameReferenceExpressionElementType || tokenType == KtTokens.IDENTIFIER) { - lighterASTNode.toString() - } else if (tokenType is KtTypeProjectionElementType) { - lighterASTNode.getChildren(treeStructure).last().toString() - } else { - null + return when (elementType) { + is KtNameReferenceExpressionElementType, KtTokens.IDENTIFIER -> lighterASTNode.toString() + is KtTypeProjectionElementType -> lighterASTNode.getChildren(treeStructure).last().toString() + else -> null } } @@ -89,10 +87,14 @@ open class LightTreeSourceNavigator : SourceNavigator { } override fun FirValueParameterSymbol.isCatchElementParameter(): Boolean { - val localSource = source ?: return false - var parent = localSource.treeStructure.getParent(localSource.lighterASTNode) - parent?.let { parent = localSource.treeStructure.getParent(it) } - return parent?.tokenType == KtNodeTypes.CATCH + return source?.getParentOfParent()?.tokenType == KtNodeTypes.CATCH + } + + private fun FirSourceElement?.getParentOfParent(): LighterASTNode? { + val source = this ?: return null + var parent = source.treeStructure.getParent(source.lighterASTNode) + parent?.let { parent = source.treeStructure.getParent(it) } + return parent } } diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirAnnotationChecker.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirAnnotationChecker.kt index 705e8711792..38c07316146 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirAnnotationChecker.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirAnnotationChecker.kt @@ -5,28 +5,26 @@ package org.jetbrains.kotlin.fir.analysis.checkers.declaration -import org.jetbrains.kotlin.descriptors.ClassKind +import org.jetbrains.kotlin.config.LanguageFeature import org.jetbrains.kotlin.descriptors.annotations.AnnotationUseSiteTarget import org.jetbrains.kotlin.descriptors.annotations.KotlinTarget +import org.jetbrains.kotlin.fir.FirAnnotationContainer import org.jetbrains.kotlin.fir.FirFakeSourceElementKind +import org.jetbrains.kotlin.fir.analysis.checkers.* import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext import org.jetbrains.kotlin.fir.analysis.checkers.context.findClosest -import org.jetbrains.kotlin.fir.analysis.checkers.getActualTargetList -import org.jetbrains.kotlin.fir.analysis.checkers.getAllowedAnnotationTargets import org.jetbrains.kotlin.fir.analysis.diagnostics.* import org.jetbrains.kotlin.fir.declarations.* import org.jetbrains.kotlin.fir.declarations.utils.hasBackingField -import org.jetbrains.kotlin.fir.declarations.utils.isCompanion -import org.jetbrains.kotlin.fir.declarations.utils.isInner -import org.jetbrains.kotlin.fir.declarations.utils.isLocal import org.jetbrains.kotlin.fir.expressions.FirAnnotationCall import org.jetbrains.kotlin.fir.expressions.argumentMapping +import org.jetbrains.kotlin.fir.languageVersionSettings import org.jetbrains.kotlin.fir.packageFqName import org.jetbrains.kotlin.fir.resolve.fqName +import org.jetbrains.kotlin.fir.types.ConeKotlinType +import org.jetbrains.kotlin.fir.types.coneType import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.StandardClassIds -import org.jetbrains.kotlin.resolve.AnnotationTargetList -import org.jetbrains.kotlin.resolve.AnnotationTargetLists object FirAnnotationChecker : FirAnnotatedDeclarationChecker() { private val deprecatedClassId = FqName("kotlin.Deprecated") @@ -41,12 +39,13 @@ object FirAnnotationChecker : FirAnnotatedDeclarationChecker() { var deprecatedSinceKotlinCall: FirAnnotationCall? = null for (annotation in declaration.annotations) { - val fqName = annotation.fqName(context.session) + val fqName = annotation.fqName(context.session) ?: continue if (fqName == deprecatedClassId) { deprecatedCall = annotation } else if (fqName == deprecatedSinceKotlinClassId) { deprecatedSinceKotlinCall = annotation } + withSuppressedDiagnostics(annotation, context) { checkAnnotationTarget(declaration, annotation, context, reporter) } @@ -56,6 +55,14 @@ object FirAnnotationChecker : FirAnnotatedDeclarationChecker() { checkDeprecatedCalls(deprecatedSinceKotlinCall, deprecatedCall, context, reporter) } } + + checkRepeatedAnnotations(declaration, context, reporter) + if (declaration is FirProperty) { + checkRepeatedAnnotationsInProperty(declaration, context, reporter) + } else if (declaration is FirTypeAlias) { + checkRepeatedAnnotations(declaration.expandedTypeRef, context, reporter) + // TODO: KT-48652, implement after KT-48651 + } } private fun checkAnnotationTarget( @@ -199,5 +206,54 @@ object FirAnnotationChecker : FirAnnotatedDeclarationChecker() { } } } + + private fun checkRepeatedAnnotations( + annotationContainer: FirAnnotationContainer, + context: CheckerContext, + reporter: DiagnosticReporter + ) { + val annotationsMap = hashMapOf>() + + for (annotation in annotationContainer.annotations) { + val useSiteTarget = annotation.useSiteTarget ?: annotationContainer.getDefaultUseSiteTarget(annotation, context) + val existingTargetsForAnnotation = annotationsMap.getOrPut(annotation.annotationTypeRef.coneType) { arrayListOf() } + + withSuppressedDiagnostics(annotation, context) { + checkRepeatedAnnotation(useSiteTarget, existingTargetsForAnnotation, annotation, context, reporter) + } + + existingTargetsForAnnotation.add(useSiteTarget) + } + } + + private fun checkRepeatedAnnotationsInProperty( + property: FirProperty, + context: CheckerContext, + reporter: DiagnosticReporter + ) { + fun FirAnnotationContainer?.getAnnotationTypes(): List { + return this?.annotations?.map { it.annotationTypeRef.coneType } ?: listOf() + } + + val propertyAnnotations = mapOf( + AnnotationUseSiteTarget.PROPERTY_GETTER to property.getter?.getAnnotationTypes(), + AnnotationUseSiteTarget.PROPERTY_SETTER to property.setter?.getAnnotationTypes(), + AnnotationUseSiteTarget.SETTER_PARAMETER to property.setter?.valueParameters?.single().getAnnotationTypes() + ) + + val isError = context.session.languageVersionSettings.supportsFeature(LanguageFeature.ProhibitRepeatedUseSiteTargetAnnotations) + + for (annotation in property.annotations) { + val useSiteTarget = annotation.useSiteTarget ?: property.getDefaultUseSiteTarget(annotation, context) + val existingAnnotations = propertyAnnotations[useSiteTarget] ?: continue + + if (annotation.annotationTypeRef.coneType in existingAnnotations && !annotation.isRepeatable(context.session)) { + val factory = if (isError) FirErrors.REPEATED_ANNOTATION else FirErrors.REPEATED_ANNOTATION_WARNING + if (annotation.source?.kind !is FirFakeSourceElementKind) { + reporter.reportOn(annotation.source, factory, context) + } + } + } + } } diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/FirExpressionAnnotationChecker.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/FirExpressionAnnotationChecker.kt index 215d93d83d1..56529b46758 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/FirExpressionAnnotationChecker.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/FirExpressionAnnotationChecker.kt @@ -5,10 +5,13 @@ package org.jetbrains.kotlin.fir.analysis.checkers.expression +import org.jetbrains.kotlin.descriptors.annotations.AnnotationUseSiteTarget import org.jetbrains.kotlin.descriptors.annotations.KotlinTarget import org.jetbrains.kotlin.fir.FirFakeSourceElementKind +import org.jetbrains.kotlin.fir.analysis.checkers.checkRepeatedAnnotation import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext import org.jetbrains.kotlin.fir.analysis.checkers.getAllowedAnnotationTargets +import org.jetbrains.kotlin.fir.analysis.checkers.getDefaultUseSiteTarget import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors import org.jetbrains.kotlin.fir.analysis.diagnostics.reportOn @@ -17,6 +20,8 @@ import org.jetbrains.kotlin.fir.declarations.FirDeclaration import org.jetbrains.kotlin.fir.expressions.FirBlock import org.jetbrains.kotlin.fir.expressions.FirExpression import org.jetbrains.kotlin.fir.expressions.FirStatement +import org.jetbrains.kotlin.fir.types.ConeKotlinType +import org.jetbrains.kotlin.fir.types.coneType object FirExpressionAnnotationChecker : FirBasicExpressionChecker() { override fun check(expression: FirStatement, context: CheckerContext, reporter: DiagnosticReporter) { @@ -26,12 +31,22 @@ object FirExpressionAnnotationChecker : FirBasicExpressionChecker() { expression !is FirExpression || expression is FirBlock && expression.source?.kind == FirFakeSourceElementKind.DesugaredForLoop ) return + + val annotationsMap = hashMapOf>() + for (annotation in expression.annotations) { + val useSiteTarget = annotation.useSiteTarget ?: expression.getDefaultUseSiteTarget(annotation, context) + val existingTargetsForAnnotation = annotationsMap.getOrPut(annotation.annotationTypeRef.coneType) { arrayListOf() } + withSuppressedDiagnostics(annotation, context) { if (KotlinTarget.EXPRESSION !in annotation.getAllowedAnnotationTargets(context.session)) { reporter.reportOn(annotation.source, FirErrors.WRONG_ANNOTATION_TARGET, "expression", context) } + + checkRepeatedAnnotation(useSiteTarget, existingTargetsForAnnotation, annotation, context, reporter) } + + existingTargetsForAnnotation.add(useSiteTarget) } } } diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirDefaultErrorMessages.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirDefaultErrorMessages.kt index 0cbe7945d13..5736a742b0f 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirDefaultErrorMessages.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirDefaultErrorMessages.kt @@ -363,6 +363,8 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.REIFIED_TYPE_FORB import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.REIFIED_TYPE_IN_CATCH_CLAUSE import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.REIFIED_TYPE_PARAMETER_IN_OVERRIDE import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.REIFIED_TYPE_PARAMETER_NO_INLINE +import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.REPEATED_ANNOTATION +import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.REPEATED_ANNOTATION_WARNING import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.REPEATED_BOUND import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.REPEATED_MODIFIER import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.RESERVED_MEMBER_INSIDE_INLINE_CLASS @@ -691,6 +693,8 @@ class FirDefaultErrorMessages { TO_STRING, TO_STRING ) + map.put(REPEATED_ANNOTATION, "This annotation is not repeatable") + map.put(REPEATED_ANNOTATION_WARNING, "This annotation is not repeatable") // OptIn map.put(EXPERIMENTAL_API_USAGE, "{1}", TO_STRING, STRING) diff --git a/compiler/fir/java/src/org/jetbrains/kotlin/fir/java/JavaUtils.kt b/compiler/fir/java/src/org/jetbrains/kotlin/fir/java/JavaUtils.kt index 5b51dc7ce4a..71084209fd5 100644 --- a/compiler/fir/java/src/org/jetbrains/kotlin/fir/java/JavaUtils.kt +++ b/compiler/fir/java/src/org/jetbrains/kotlin/fir/java/JavaUtils.kt @@ -88,7 +88,6 @@ private val JAVA_TARGET_CLASS_ID = ClassId.topLevel(FqName(Target::class.java.ca private val JAVA_RETENTION_CLASS_ID = ClassId.topLevel(FqName(Retention::class.java.canonicalName)) private val JAVA_DEPRECATED_CLASS_ID = ClassId.topLevel(FqName(Deprecated::class.java.canonicalName)) private val JAVA_DOCUMENTED_CLASS_ID = ClassId.topLevel(FqName(Documented::class.java.canonicalName)) -private val JAVA_REPEATABLE_CLASS_ID = ClassId.topLevel(FqName("java.lang.annotation.Repeatable")) // since Java 8 private val JAVA_TARGETS_TO_KOTLIN = mapOf( "TYPE" to EnumSet.of(AnnotationTarget.CLASS, AnnotationTarget.FILE), @@ -171,7 +170,6 @@ private fun JavaAnnotation.toFirAnnotationCall( val lookupTag = when (classId) { JAVA_TARGET_CLASS_ID -> ClassId.topLevel(StandardNames.FqNames.target) JAVA_RETENTION_CLASS_ID -> ClassId.topLevel(StandardNames.FqNames.retention) - JAVA_REPEATABLE_CLASS_ID -> ClassId.topLevel(StandardNames.FqNames.repeatable) JAVA_DOCUMENTED_CLASS_ID -> ClassId.topLevel(StandardNames.FqNames.mustBeDocumented) JAVA_DEPRECATED_CLASS_ID -> ClassId.topLevel(StandardNames.FqNames.deprecated) else -> classId diff --git a/compiler/testData/codegen/box/annotations/repeatable/javaAnnotation.kt b/compiler/testData/codegen/box/annotations/repeatable/javaAnnotation.kt index 358b895cbfa..cf5c29ac252 100644 --- a/compiler/testData/codegen/box/annotations/repeatable/javaAnnotation.kt +++ b/compiler/testData/codegen/box/annotations/repeatable/javaAnnotation.kt @@ -1,6 +1,5 @@ // !LANGUAGE: +RepeatableAnnotations // TARGET_BACKEND: JVM_IR -// IGNORE_BACKEND_FIR: JVM_IR // WITH_RUNTIME // FULL_JDK // JVM_TARGET: 1.8 diff --git a/compiler/testData/diagnostics/tests/annotations/AnnotationForClassTypeParameter_15.fir.kt b/compiler/testData/diagnostics/tests/annotations/AnnotationForClassTypeParameter_15.fir.kt index bab102be7e6..a8af099615e 100644 --- a/compiler/testData/diagnostics/tests/annotations/AnnotationForClassTypeParameter_15.fir.kt +++ b/compiler/testData/diagnostics/tests/annotations/AnnotationForClassTypeParameter_15.fir.kt @@ -5,10 +5,10 @@ annotation class A1 @Target(AnnotationTarget.TYPE_PARAMETER) annotation class A2(val some: Int = 12) -class TopLevelClass<@A1 @A2(3) @A2 @A1(12) @A2("Test") T> { - class InnerClass<@A1 @A2(3) @A2 @A1(12) @A2("Test") T> { +class TopLevelClass<@A1 @A2(3) @A2 @A1(12) @A2("Test") T> { + class InnerClass<@A1 @A2(3) @A2 @A1(12) @A2("Test") T> { fun test() { - class InFun<@A1 @A2(3) @A2 @A1(12) @A2("Test") T> + class InFun<@A1 @A2(3) @A2 @A1(12) @A2("Test") T> } } -} \ No newline at end of file +} diff --git a/compiler/testData/diagnostics/tests/annotations/AnnotationForClassTypeParameter_16.fir.kt b/compiler/testData/diagnostics/tests/annotations/AnnotationForClassTypeParameter_16.fir.kt index fd5a2fc1b9c..5a5c24f05f4 100644 --- a/compiler/testData/diagnostics/tests/annotations/AnnotationForClassTypeParameter_16.fir.kt +++ b/compiler/testData/diagnostics/tests/annotations/AnnotationForClassTypeParameter_16.fir.kt @@ -8,18 +8,18 @@ annotation class TA1 @Target(AnnotationTarget.TYPE_PARAMETER) annotation class TA2(val some: Int = 12) -class TopLevelClass<@A1 @A2(3) @A2 @A1(12) @A2("Test") T> { - class InnerClass<@A1 @A2(3) @A2 @A1(12) @A2("Test") T> { +class TopLevelClass<@A1 @A2(3) @A2 @A1(12) @A2("Test") T> { + class InnerClass<@A1 @A2(3) @A2 @A1(12) @A2("Test") T> { fun test() { - class InFun<@A1 @A2(3) @A2 @A1(12) @A2("Test") T> + class InFun<@A1 @A2(3) @A2 @A1(12) @A2("Test") T> } } } -class TTopLevelClass<@TA1 @TA2(3) @TA2 @TA1(12) @TA2("Test") T> { - class TInnerClass<@TA1 @TA2(3) @TA2 @TA1(12) @TA2("Test") T> { +class TTopLevelClass<@TA1 @TA2(3) @TA2 @TA1(12) @TA2("Test") T> { + class TInnerClass<@TA1 @TA2(3) @TA2 @TA1(12) @TA2("Test") T> { fun test() { - class TInFun<@TA1 @TA2(3) @TA2 @TA1(12) @TA2("Test") T> + class TInFun<@TA1 @TA2(3) @TA2 @TA1(12) @TA2("Test") T> } } } diff --git a/compiler/testData/diagnostics/tests/annotations/AnnotationForClassTypeParameter_typeUseFlag.fir.kt b/compiler/testData/diagnostics/tests/annotations/AnnotationForClassTypeParameter_typeUseFlag.fir.kt index a4c7e2a8668..712294271fb 100644 --- a/compiler/testData/diagnostics/tests/annotations/AnnotationForClassTypeParameter_typeUseFlag.fir.kt +++ b/compiler/testData/diagnostics/tests/annotations/AnnotationForClassTypeParameter_typeUseFlag.fir.kt @@ -9,18 +9,18 @@ annotation class TA1 @Target(AnnotationTarget.TYPE_PARAMETER) annotation class TA2(val some: Int = 12) -class TopLevelClass<@A1 @A2(3) @A2 @A1(12) @A2("Test") T> { - class InnerClass<@A1 @A2(3) @A2 @A1(12) @A2("Test") T> { +class TopLevelClass<@A1 @A2(3) @A2 @A1(12) @A2("Test") T> { + class InnerClass<@A1 @A2(3) @A2 @A1(12) @A2("Test") T> { fun test() { - class InFun<@A1 @A2(3) @A2 @A1(12) @A2("Test") T> + class InFun<@A1 @A2(3) @A2 @A1(12) @A2("Test") T> } } } -class TTopLevelClass<@TA1 @TA2(3) @TA2 @TA1(12) @TA2("Test") T> { - class TInnerClass<@TA1 @TA2(3) @TA2 @TA1(12) @TA2("Test") T> { +class TTopLevelClass<@TA1 @TA2(3) @TA2 @TA1(12) @TA2("Test") T> { + class TInnerClass<@TA1 @TA2(3) @TA2 @TA1(12) @TA2("Test") T> { fun test() { - class TInFun<@TA1 @TA2(3) @TA2 @TA1(12) @TA2("Test") T> + class TInFun<@TA1 @TA2(3) @TA2 @TA1(12) @TA2("Test") T> } } } diff --git a/compiler/testData/diagnostics/tests/annotations/AnnotationForFunctionTypeParameter.fir.kt b/compiler/testData/diagnostics/tests/annotations/AnnotationForFunctionTypeParameter.fir.kt index 43c2ac054ec..9a1bde2ac4d 100644 --- a/compiler/testData/diagnostics/tests/annotations/AnnotationForFunctionTypeParameter.fir.kt +++ b/compiler/testData/diagnostics/tests/annotations/AnnotationForFunctionTypeParameter.fir.kt @@ -1,13 +1,13 @@ annotation class A1 annotation class A2(val some: Int = 12) -fun <@A1 @A2(3) @A2 @A1(12) @A2("Test") T> topFun() = 12 +fun <@A1 @A2(3) @A2 @A1(12) @A2("Test") T> topFun() = 12 class SomeClass { - fun <@A1 @A2(3) @A2 @A1(12) @A2("Test") T> method() = 12 + fun <@A1 @A2(3) @A2 @A1(12) @A2("Test") T> method() = 12 fun foo() { - fun <@A1 @A2(3) @A2 @A1(12) @A2("Test") T> innerFun() = 12 + fun <@A1 @A2(3) @A2 @A1(12) @A2("Test") T> innerFun() = 12 } } @@ -16,12 +16,12 @@ annotation class TA @Target(AnnotationTarget.TYPE_PARAMETER) annotation class TPA(val some: Int = 12) -fun <@TA @TPA(3) @TPA @TA(12) @TPA("Test") T> topFunTPA() = 12 +fun <@TA @TPA(3) @TPA @TA(12) @TPA("Test") T> topFunTPA() = 12 class SomeClassTPA { - fun <@TA @TPA(3) @TPA @TA(12) @TPA("Test") T> method() = 12 + fun <@TA @TPA(3) @TPA @TA(12) @TPA("Test") T> method() = 12 fun foo() { - fun <@TA @TPA(3) @TPA @TA(12) @TPA("Test") T> innerFun() = 12 + fun <@TA @TPA(3) @TPA @TA(12) @TPA("Test") T> innerFun() = 12 } } diff --git a/compiler/testData/diagnostics/tests/annotations/AnnotationsForPropertyTypeParameter.fir.kt b/compiler/testData/diagnostics/tests/annotations/AnnotationsForPropertyTypeParameter.fir.kt index b1898026048..179fac80469 100644 --- a/compiler/testData/diagnostics/tests/annotations/AnnotationsForPropertyTypeParameter.fir.kt +++ b/compiler/testData/diagnostics/tests/annotations/AnnotationsForPropertyTypeParameter.fir.kt @@ -3,10 +3,10 @@ annotation class A1 annotation class A2(val some: Int = 12) -val <@A1 @A2(3) @A2 @A1(12) @A2("Test") T> T.topProp: Int get() = 12 +val <@A1 @A2(3) @A2 @A1(12) @A2("Test") T> T.topProp: Int get() = 12 class SomeClass { - val <@A1 @A2(3) @A2 @A1(12) @A2("Test") T> T.field: Int get() = 12 + val <@A1 @A2(3) @A2 @A1(12) @A2("Test") T> T.field: Int get() = 12 fun foo() { val <@A1 @A2(3) @A2 @A1(12) @A2("Test") T> localVal = 12 diff --git a/compiler/testData/diagnostics/tests/annotations/options/unrepeatable.fir.kt b/compiler/testData/diagnostics/tests/annotations/options/unrepeatable.fir.kt deleted file mode 100644 index ea3e981f159..00000000000 --- a/compiler/testData/diagnostics/tests/annotations/options/unrepeatable.fir.kt +++ /dev/null @@ -1,23 +0,0 @@ -annotation class unrepann(val x: Int) - -annotation class ann(val y: Int) - -@unrepann(1) @unrepann(2) class DoubleAnnotated - -@ann(3) @ann(7) @ann(42) class TripleAnnotated - -@Target(AnnotationTarget.EXPRESSION) -@Retention(AnnotationRetention.SOURCE) -annotation class annexpr - -@ann(0) @ann(1) fun foo(@ann(7) @ann(2) x: Int): Int { - @annexpr @annexpr return x -} - -@unrepann(0) -@get:unrepann(1) -var annotated = 1 // No errors should be here - -@unrepann(0) -@property:unrepann(1) -var annotated2 = 2 diff --git a/compiler/testData/diagnostics/tests/annotations/options/unrepeatable.kt b/compiler/testData/diagnostics/tests/annotations/options/unrepeatable.kt index b43d1cb91f4..a43b870710a 100644 --- a/compiler/testData/diagnostics/tests/annotations/options/unrepeatable.kt +++ b/compiler/testData/diagnostics/tests/annotations/options/unrepeatable.kt @@ -1,3 +1,4 @@ +// FIR_IDENTICAL annotation class unrepann(val x: Int) annotation class ann(val y: Int) diff --git a/compiler/testData/diagnostics/tests/annotations/repeatable/containerAndAnnotationAreBothApplied_1_5.fir.kt b/compiler/testData/diagnostics/tests/annotations/repeatable/containerAndAnnotationAreBothApplied_1_5.fir.kt deleted file mode 100644 index 5af4685e48d..00000000000 --- a/compiler/testData/diagnostics/tests/annotations/repeatable/containerAndAnnotationAreBothApplied_1_5.fir.kt +++ /dev/null @@ -1,142 +0,0 @@ -// !LANGUAGE: -RepeatableAnnotations -// FULL_JDK -// FILE: JR.java - -import java.lang.annotation.*; - -@Repeatable(JR.Container.class) -@Retention(RetentionPolicy.RUNTIME) -public @interface JR { - public @interface Container { - JR[] value(); - } -} - -// FILE: JS.java - -import java.lang.annotation.*; - -@Repeatable(JS.Container.class) -@Retention(RetentionPolicy.SOURCE) -public @interface JS { - public @interface Container { - JS[] value(); - } -} - -// FILE: KR.kt - -@java.lang.annotation.Repeatable(KR.Container::class) -@Retention(AnnotationRetention.RUNTIME) -annotation class KR { - annotation class Container(val value: Array) -} - -// FILE: KS.kt - -@java.lang.annotation.Repeatable(KS.Container::class) -@Retention(AnnotationRetention.SOURCE) -annotation class KS { - annotation class Container(val value: Array) -} - -// FILE: test.kt - -// Java runtime-retained annotation - -@JR -@JR.Container() -fun jr1() {} - -@JR -@JR.Container() -@JR -fun jr2() {} - -@JR -@JR.Container(JR()) -@JR -fun jr3() {} - -@JR -@JR.Container(JR()) -fun jr4() {} - -@JR -@JR.Container(JR(), JR()) -fun jr5() {} - - -// Java source-retained annotation - -@JS -@JS.Container() -fun js1() {} - -@JS -@JS.Container() -@JS -fun js2() {} - -@JS -@JS.Container(JS()) -@JS -fun js3() {} - -@JS -@JS.Container(JS()) -fun js4() {} - -@JS -@JS.Container(JS(), JS()) -fun js5() {} - - -// Kotlin runtime-retained annotation - -@KR.Container([]) -@KR -fun kr1() {} - -@KR.Container([]) -@KR -@KR -fun kr2() {} - -@KR -@KR -@KR.Container([KR()]) -fun kr3() {} - -@KR.Container([KR()]) -@KR -fun kr4() {} - -@KR -@KR.Container([KR(), KR()]) -fun kr5() {} - - -// Kotlin source-retained annotation - -@KS.Container([]) -@KS -fun ks1() {} - -@KS.Container([]) -@KS -@KS -fun ks2() {} - -@KS -@KS -@KS.Container([KS()]) -fun ks3() {} - -@KS.Container([KS()]) -@KS -fun ks4() {} - -@KS -@KS.Container([KS(), KS()]) -fun ks5() {} diff --git a/compiler/testData/diagnostics/tests/annotations/repeatable/containerAndAnnotationAreBothApplied_1_5.kt b/compiler/testData/diagnostics/tests/annotations/repeatable/containerAndAnnotationAreBothApplied_1_5.kt index 5f014e769c0..2b9d14bcf85 100644 --- a/compiler/testData/diagnostics/tests/annotations/repeatable/containerAndAnnotationAreBothApplied_1_5.kt +++ b/compiler/testData/diagnostics/tests/annotations/repeatable/containerAndAnnotationAreBothApplied_1_5.kt @@ -1,3 +1,4 @@ +// FIR_IDENTICAL // !LANGUAGE: -RepeatableAnnotations // FULL_JDK // FILE: JR.java diff --git a/compiler/testData/diagnostics/tests/annotations/repeatable/javaRepeatable_1_5.fir.kt b/compiler/testData/diagnostics/tests/annotations/repeatable/javaRepeatable_1_5.fir.kt deleted file mode 100644 index 78125ecfa94..00000000000 --- a/compiler/testData/diagnostics/tests/annotations/repeatable/javaRepeatable_1_5.fir.kt +++ /dev/null @@ -1,48 +0,0 @@ -// !LANGUAGE: -RepeatableAnnotations -// FULL_JDK -// FILE: Runtime.java - -import java.lang.annotation.*; - -@Repeatable(Runtime.Container.class) -@Retention(RetentionPolicy.RUNTIME) -public @interface Runtime { - public @interface Container { - Runtime[] value(); - } -} - -// FILE: Clazz.java - -import java.lang.annotation.*; - -@Repeatable(Clazz.Container.class) -@Retention(RetentionPolicy.CLASS) -public @interface Clazz { - public @interface Container { - Clazz[] value(); - } -} - -// FILE: Source.java - -import java.lang.annotation.*; - -@Repeatable(Source.Container.class) -@Retention(RetentionPolicy.SOURCE) -public @interface Source { - public @interface Container { - Source[] value(); - } -} - -// FILE: usage.kt - -@Runtime @Runtime -class UseRuntime - -@Clazz @Clazz -class UseClazz - -@Source @Source -class UseSource diff --git a/compiler/testData/diagnostics/tests/annotations/repeatable/javaRepeatable_1_5.kt b/compiler/testData/diagnostics/tests/annotations/repeatable/javaRepeatable_1_5.kt index 377fcb361ee..7627dcaab96 100644 --- a/compiler/testData/diagnostics/tests/annotations/repeatable/javaRepeatable_1_5.kt +++ b/compiler/testData/diagnostics/tests/annotations/repeatable/javaRepeatable_1_5.kt @@ -1,3 +1,4 @@ +// FIR_IDENTICAL // !LANGUAGE: -RepeatableAnnotations // FULL_JDK // FILE: Runtime.java diff --git a/compiler/testData/diagnostics/tests/annotations/repeatable/javaUnrepeatable_1_5.fir.kt b/compiler/testData/diagnostics/tests/annotations/repeatable/javaUnrepeatable_1_5.fir.kt deleted file mode 100644 index 82eddbec07e..00000000000 --- a/compiler/testData/diagnostics/tests/annotations/repeatable/javaUnrepeatable_1_5.fir.kt +++ /dev/null @@ -1,33 +0,0 @@ -// !LANGUAGE: -RepeatableAnnotations -// FULL_JDK -// FILE: Runtime.java - -import java.lang.annotation.*; - -@Retention(RetentionPolicy.RUNTIME) -public @interface Runtime {} - -// FILE: Clazz.java - -import java.lang.annotation.*; - -@Retention(RetentionPolicy.CLASS) -public @interface Clazz {} - -// FILE: Source.java - -import java.lang.annotation.*; - -@Retention(RetentionPolicy.SOURCE) -public @interface Source {} - -// FILE: usage.kt - -@Runtime @Runtime -class UseRuntime - -@Clazz @Clazz -class UseClazz - -@Source @Source -class UseSource diff --git a/compiler/testData/diagnostics/tests/annotations/repeatable/javaUnrepeatable_1_5.kt b/compiler/testData/diagnostics/tests/annotations/repeatable/javaUnrepeatable_1_5.kt index ac0da08e23c..9e6d007ac01 100644 --- a/compiler/testData/diagnostics/tests/annotations/repeatable/javaUnrepeatable_1_5.kt +++ b/compiler/testData/diagnostics/tests/annotations/repeatable/javaUnrepeatable_1_5.kt @@ -1,3 +1,4 @@ +// FIR_IDENTICAL // !LANGUAGE: -RepeatableAnnotations // FULL_JDK // FILE: Runtime.java diff --git a/compiler/testData/diagnostics/tests/annotations/repeatable/javaUnrepeatable_1_6.fir.kt b/compiler/testData/diagnostics/tests/annotations/repeatable/javaUnrepeatable_1_6.fir.kt deleted file mode 100644 index 8f966fbcb9c..00000000000 --- a/compiler/testData/diagnostics/tests/annotations/repeatable/javaUnrepeatable_1_6.fir.kt +++ /dev/null @@ -1,33 +0,0 @@ -// !LANGUAGE: +RepeatableAnnotations -// FULL_JDK -// FILE: Runtime.java - -import java.lang.annotation.*; - -@Retention(RetentionPolicy.RUNTIME) -public @interface Runtime {} - -// FILE: Clazz.java - -import java.lang.annotation.*; - -@Retention(RetentionPolicy.CLASS) -public @interface Clazz {} - -// FILE: Source.java - -import java.lang.annotation.*; - -@Retention(RetentionPolicy.SOURCE) -public @interface Source {} - -// FILE: usage.kt - -@Runtime @Runtime -class UseRuntime - -@Clazz @Clazz -class UseClazz - -@Source @Source -class UseSource diff --git a/compiler/testData/diagnostics/tests/annotations/repeatable/javaUnrepeatable_1_6.kt b/compiler/testData/diagnostics/tests/annotations/repeatable/javaUnrepeatable_1_6.kt index cb8a98ca5cd..3ae66084fdc 100644 --- a/compiler/testData/diagnostics/tests/annotations/repeatable/javaUnrepeatable_1_6.kt +++ b/compiler/testData/diagnostics/tests/annotations/repeatable/javaUnrepeatable_1_6.kt @@ -1,3 +1,4 @@ +// FIR_IDENTICAL // !LANGUAGE: +RepeatableAnnotations // FULL_JDK // FILE: Runtime.java diff --git a/compiler/testData/diagnostics/tests/annotations/withUseSiteTarget/kt26638.fir.kt b/compiler/testData/diagnostics/tests/annotations/withUseSiteTarget/kt26638.fir.kt deleted file mode 100644 index e9e29f0434f..00000000000 --- a/compiler/testData/diagnostics/tests/annotations/withUseSiteTarget/kt26638.fir.kt +++ /dev/null @@ -1,22 +0,0 @@ -// !DIAGNOSTICS: -UNUSED_PARAMETER -// !LANGUAGE: -ProhibitRepeatedUseSiteTargetAnnotations - -// Ann is not repeatable -annotation class Ann(val x: Int) - -@get:Ann(10) -val a: String - @Ann(20) get() = "foo" - -@set:Ann(10) -var b: String = "" - @Ann(20) set(value) { field = value } - -@setparam:Ann(10) -var c = " " - set(@Ann(20) x) {} - -@get:Ann(10) -@get:Ann(20) -val d: String - @Ann(30) @Ann(40) get() = "foo" \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/annotations/withUseSiteTarget/kt26638.kt b/compiler/testData/diagnostics/tests/annotations/withUseSiteTarget/kt26638.kt index 747913875ee..02c1b6921b0 100644 --- a/compiler/testData/diagnostics/tests/annotations/withUseSiteTarget/kt26638.kt +++ b/compiler/testData/diagnostics/tests/annotations/withUseSiteTarget/kt26638.kt @@ -1,3 +1,4 @@ +// FIR_IDENTICAL // !DIAGNOSTICS: -UNUSED_PARAMETER // !LANGUAGE: -ProhibitRepeatedUseSiteTargetAnnotations @@ -19,4 +20,4 @@ var c = " " @get:Ann(10) @get:Ann(20) val d: String - @Ann(30) @Ann(40) get() = "foo" \ No newline at end of file + @Ann(30) @Ann(40) get() = "foo" diff --git a/compiler/testData/diagnostics/tests/annotations/withUseSiteTarget/kt26638_after.fir.kt b/compiler/testData/diagnostics/tests/annotations/withUseSiteTarget/kt26638_after.fir.kt index 138bab0c129..7ac2a9b3582 100644 --- a/compiler/testData/diagnostics/tests/annotations/withUseSiteTarget/kt26638_after.fir.kt +++ b/compiler/testData/diagnostics/tests/annotations/withUseSiteTarget/kt26638_after.fir.kt @@ -4,19 +4,19 @@ // Ann is not repeatable annotation class Ann(val x: Int) -@get:Ann(10) +@get:Ann(10) val a: String @Ann(20) get() = "foo" -@set:Ann(10) +@set:Ann(10) var b: String = "" @Ann(20) set(value) { field = value } -@setparam:Ann(10) +@setparam:Ann(10) var c = " " set(@Ann(20) x) {} -@get:Ann(10) -@get:Ann(20) +@get:Ann(10) +@get:Ann(20) val d: String - @Ann(30) @Ann(40) get() = "foo" \ No newline at end of file + @Ann(30) @Ann(40) get() = "foo" diff --git a/compiler/testData/diagnostics/tests/annotations/withUseSiteTarget/repeatable.fir.kt b/compiler/testData/diagnostics/tests/annotations/withUseSiteTarget/repeatable.fir.kt index 2e7a8d1ddd8..8cea1dc73a5 100644 --- a/compiler/testData/diagnostics/tests/annotations/withUseSiteTarget/repeatable.fir.kt +++ b/compiler/testData/diagnostics/tests/annotations/withUseSiteTarget/repeatable.fir.kt @@ -9,12 +9,12 @@ class CustomDelegate { operator fun getValue(thisRef: Any?, prop: KProperty<*>): String = prop.name } -public class A(@param:Ann @Ann val x: Int, @param: RepeatableAnn @Ann val y: Int) { +public class A(@param:Ann @Ann val x: Int, @param: RepeatableAnn @Ann val y: Int) { @field:Ann @property:Ann @RepeatableAnn @property:RepeatableAnn val a: Int = 0 - @Ann @field:Ann @property:Ann + @Ann @field:Ann @property:Ann val b: Int = 0 @field:RepeatableAnn @field:RepeatableAnn @@ -46,14 +46,14 @@ annotation class fieldOrPropAnn annotation class getSetAndParamAnn public class B(@param:fieldOrPropAnn @fieldOrPropAnn val x: Int, - @property:fieldOrPropAnn @fieldOrPropAnn val y: Int) { + @property:fieldOrPropAnn @fieldOrPropAnn val y: Int) { @fieldOrPropAnn @field:fieldOrPropAnn val z: Int = 42 @getSetAndParamAnn - @setparam:getSetAndParamAnn + @setparam:getSetAndParamAnn var w: Int - @getSetAndParamAnn @get:getSetAndParamAnn get() = 0 + @getSetAndParamAnn @get:getSetAndParamAnn get() = 0 // See KT-15470: fake INAPPLICABLE_TARGET_ON_PROPERTY - @getSetAndParamAnn @set:getSetAndParamAnn set(arg) {} -} \ No newline at end of file + @getSetAndParamAnn @set:getSetAndParamAnn set(arg) {} +} diff --git a/compiler/testData/diagnostics/tests/inner/classesInClassObjectHeader.fir.kt b/compiler/testData/diagnostics/tests/inner/classesInClassObjectHeader.fir.kt index 82ae5349ff8..8aeb5dca6f8 100644 --- a/compiler/testData/diagnostics/tests/inner/classesInClassObjectHeader.fir.kt +++ b/compiler/testData/diagnostics/tests/inner/classesInClassObjectHeader.fir.kt @@ -1,5 +1,5 @@ class Test { - @`InnerAnnotation` @InnerAnnotation + @`InnerAnnotation` @InnerAnnotation companion object : StaticClass(), InnerClass() { } diff --git a/compiler/testData/diagnostics/tests/parenthesizedTypes/splitModifierList.fir.kt b/compiler/testData/diagnostics/tests/parenthesizedTypes/splitModifierList.fir.kt index 0e41b321904..c8229984dbd 100644 --- a/compiler/testData/diagnostics/tests/parenthesizedTypes/splitModifierList.fir.kt +++ b/compiler/testData/diagnostics/tests/parenthesizedTypes/splitModifierList.fir.kt @@ -5,7 +5,7 @@ annotation class A annotation class B typealias Test0 = @A @B Int -typealias Test1 = @A() (@A Int) +typealias Test1 = @A() (@A Int) typealias Test2 = @A() (@B Int) typealias Test3 = @A() (@A Int) -> Int typealias Test4 = @A() (@B Int)? @@ -13,4 +13,4 @@ typealias Test5 = @A() ( (@B Int)? ) typealias Test6 = (@A @B Int) typealias Test7 = (@A @B Int)? typealias Test8 = (@A() (@B Int)? ) -typealias Test9 = (@A() (@B Int) )? \ No newline at end of file +typealias Test9 = (@A() (@B Int) )? diff --git a/compiler/testData/diagnostics/tests/typealias/typealiasRhsRepeatedAnnotations.fir.kt b/compiler/testData/diagnostics/tests/typealias/typealiasRhsRepeatedAnnotations.fir.kt deleted file mode 100644 index 0d7b4dc99b5..00000000000 --- a/compiler/testData/diagnostics/tests/typealias/typealiasRhsRepeatedAnnotations.fir.kt +++ /dev/null @@ -1,4 +0,0 @@ -@Target(AnnotationTarget.TYPE) -annotation class A - -typealias Test1 = @A @A Int diff --git a/compiler/testData/diagnostics/tests/typealias/typealiasRhsRepeatedAnnotations.kt b/compiler/testData/diagnostics/tests/typealias/typealiasRhsRepeatedAnnotations.kt index 68c939def99..d8379196dd3 100644 --- a/compiler/testData/diagnostics/tests/typealias/typealiasRhsRepeatedAnnotations.kt +++ b/compiler/testData/diagnostics/tests/typealias/typealiasRhsRepeatedAnnotations.kt @@ -1,3 +1,4 @@ +// FIR_IDENTICAL @Target(AnnotationTarget.TYPE) annotation class A diff --git a/compiler/testData/diagnostics/testsWithStdLib/annotations/qualifiedCallValue.fir.kt b/compiler/testData/diagnostics/testsWithStdLib/annotations/qualifiedCallValue.fir.kt deleted file mode 100644 index 3ec4a43aa00..00000000000 --- a/compiler/testData/diagnostics/testsWithStdLib/annotations/qualifiedCallValue.fir.kt +++ /dev/null @@ -1,27 +0,0 @@ -// !DIAGNOSTICS: -JAVA_LANG_CLASS_PARAMETER_IN_ANNOTATION -package a.b.c - -@kotlin.Deprecated("aaa") -@ann1(kotlin.Deprecated("aaa")) - -@a.b.c.ann1() -@ann2(a.b.c.ann1()) - -@A.IAnn() -@ann3(A.IAnn()) - -@a.b.c.A.IAnn() -@ann3(a.b.c.A.IAnn()) - -@annArray(kotlin.arrayOf("a")) -fun test() = 1 - -annotation class ann1(val p: Deprecated = kotlin.Deprecated("aaa")) -annotation class ann2(val p: ann1 = a.b.c.ann1()) -annotation class ann3(val p: A.IAnn = A.IAnn(), val p2: A.IAnn = a.b.c.A.IAnn()) - -annotation class annArray(val p: Array = kotlin.arrayOf("a")) - -class A { - annotation class IAnn -} diff --git a/compiler/testData/diagnostics/testsWithStdLib/annotations/qualifiedCallValue.kt b/compiler/testData/diagnostics/testsWithStdLib/annotations/qualifiedCallValue.kt index 30af338c316..269e25b4c69 100644 --- a/compiler/testData/diagnostics/testsWithStdLib/annotations/qualifiedCallValue.kt +++ b/compiler/testData/diagnostics/testsWithStdLib/annotations/qualifiedCallValue.kt @@ -1,3 +1,4 @@ +// FIR_IDENTICAL // !DIAGNOSTICS: -JAVA_LANG_CLASS_PARAMETER_IN_ANNOTATION package a.b.c diff --git a/compiler/testData/diagnostics/testsWithStdLib/experimental/useExperimentalOnWholeModule.kt b/compiler/testData/diagnostics/testsWithStdLib/experimental/useExperimentalOnWholeModule.kt index 9f40589c284..1048be83bcf 100644 --- a/compiler/testData/diagnostics/testsWithStdLib/experimental/useExperimentalOnWholeModule.kt +++ b/compiler/testData/diagnostics/testsWithStdLib/experimental/useExperimentalOnWholeModule.kt @@ -1,5 +1,4 @@ // FIR_IDENTICAL -// FIR_IDE_IGNORE // !USE_EXPERIMENTAL: kotlin.RequiresOptIn api.ExperimentalAPI // MODULE: api // FILE: api.kt diff --git a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDataClassConverters.kt b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDataClassConverters.kt index 40ea8718190..3746e3cce93 100644 --- a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDataClassConverters.kt +++ b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDataClassConverters.kt @@ -859,6 +859,18 @@ internal val KT_DIAGNOSTIC_CONVERTER = KtDiagnosticConverterBuilder.buildConvert token, ) } + add(FirErrors.REPEATED_ANNOTATION) { firDiagnostic -> + RepeatedAnnotationImpl( + firDiagnostic as FirPsiDiagnostic, + token, + ) + } + add(FirErrors.REPEATED_ANNOTATION_WARNING) { firDiagnostic -> + RepeatedAnnotationWarningImpl( + firDiagnostic as FirPsiDiagnostic, + token, + ) + } add(FirErrors.EXPERIMENTAL_API_USAGE) { firDiagnostic -> ExperimentalApiUsageImpl( firDiagnostic.a, diff --git a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDiagnostics.kt b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDiagnostics.kt index 02bd0bf2e4b..5627a6339e0 100644 --- a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDiagnostics.kt +++ b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDiagnostics.kt @@ -618,6 +618,14 @@ sealed class KtFirDiagnostic : KtDiagnosticWithPsi { override val diagnosticClass get() = InapplicableFileTarget::class } + abstract class RepeatedAnnotation : KtFirDiagnostic() { + override val diagnosticClass get() = RepeatedAnnotation::class + } + + abstract class RepeatedAnnotationWarning : KtFirDiagnostic() { + override val diagnosticClass get() = RepeatedAnnotationWarning::class + } + abstract class ExperimentalApiUsage : KtFirDiagnostic() { override val diagnosticClass get() = ExperimentalApiUsage::class abstract val optInMarkerFqName: FqName diff --git a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDiagnosticsImpl.kt b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDiagnosticsImpl.kt index c22c0572635..5bb3ac7fdbc 100644 --- a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDiagnosticsImpl.kt +++ b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDiagnosticsImpl.kt @@ -991,6 +991,20 @@ internal class InapplicableFileTargetImpl( override val firDiagnostic: FirPsiDiagnostic by weakRef(firDiagnostic) } +internal class RepeatedAnnotationImpl( + firDiagnostic: FirPsiDiagnostic, + override val token: ValidityToken, +) : KtFirDiagnostic.RepeatedAnnotation(), KtAbstractFirDiagnostic { + override val firDiagnostic: FirPsiDiagnostic by weakRef(firDiagnostic) +} + +internal class RepeatedAnnotationWarningImpl( + firDiagnostic: FirPsiDiagnostic, + override val token: ValidityToken, +) : KtFirDiagnostic.RepeatedAnnotationWarning(), KtAbstractFirDiagnostic { + override val firDiagnostic: FirPsiDiagnostic by weakRef(firDiagnostic) +} + internal class ExperimentalApiUsageImpl( override val optInMarkerFqName: FqName, override val message: String,