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 832ea6d7aef..4dab107f228 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 @@ -172,6 +172,7 @@ object DIAGNOSTICS_LIST : DiagnosticList() { val DEPRECATED_SINCE_KOTLIN_OUTSIDE_KOTLIN_SUBPACKAGE by error(PositioningStrategy.REFERENCED_NAME_BY_QUALIFIED) val ANNOTATION_ON_SUPERCLASS by error() + val RESTRICTED_RETENTION_FOR_EXPRESSION_ANNOTATION by error() } val EXPOSED_VISIBILITY by object : DiagnosticGroup("Exposed visibility") { 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 5b390cbff12..bda4ef2de9d 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 @@ -178,6 +178,7 @@ object FirErrors { val DEPRECATED_SINCE_KOTLIN_WITH_DEPRECATED_LEVEL by error0(SourceElementPositioningStrategies.REFERENCED_NAME_BY_QUALIFIED) val DEPRECATED_SINCE_KOTLIN_OUTSIDE_KOTLIN_SUBPACKAGE by error0(SourceElementPositioningStrategies.REFERENCED_NAME_BY_QUALIFIED) val ANNOTATION_ON_SUPERCLASS by error0() + val RESTRICTED_RETENTION_FOR_EXPRESSION_ANNOTATION by error0() // Exposed visibility val EXPOSED_TYPEALIAS_EXPANDED_TYPE by error3(SourceElementPositioningStrategies.DECLARATION_NAME) 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 new file mode 100644 index 00000000000..b6fc09d2a7e --- /dev/null +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/FirAnnotationHelpers.kt @@ -0,0 +1,89 @@ +/* + * Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.fir.analysis.checkers + +import org.jetbrains.kotlin.builtins.StandardNames +import org.jetbrains.kotlin.fir.FirSession +import org.jetbrains.kotlin.fir.declarations.FirAnnotatedDeclaration +import org.jetbrains.kotlin.fir.declarations.FirRegularClass +import org.jetbrains.kotlin.fir.expressions.FirAnnotationCall +import org.jetbrains.kotlin.fir.expressions.FirQualifiedAccessExpression +import org.jetbrains.kotlin.fir.expressions.FirVarargArgumentsExpression +import org.jetbrains.kotlin.fir.expressions.argumentMapping +import org.jetbrains.kotlin.fir.references.FirResolvedNamedReference +import org.jetbrains.kotlin.fir.resolve.toSymbol +import org.jetbrains.kotlin.fir.types.ConeClassLikeType +import org.jetbrains.kotlin.fir.types.coneType +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.name.Name + +private val RETENTION_PARAMETER_NAME = Name.identifier("value") +private val TARGET_PARAMETER_NAME = Name.identifier("allowedTargets") + +fun FirAnnotationCall.getRetention(session: FirSession): AnnotationRetention { + val annotationClass = (this.annotationTypeRef.coneType as? ConeClassLikeType)?.lookupTag?.toSymbol(session)?.fir as? FirRegularClass + return annotationClass?.getRetention() ?: AnnotationRetention.RUNTIME +} + +fun FirRegularClass.getRetention(): AnnotationRetention { + val retentionAnnotation = getRetentionAnnotation() ?: return AnnotationRetention.RUNTIME + val argumentMapping = retentionAnnotation.argumentMapping ?: return AnnotationRetention.RUNTIME + val retentionArgument = argumentMapping.keys.firstOrNull() as? FirQualifiedAccessExpression + ?: return AnnotationRetention.RUNTIME + if (argumentMapping[retentionArgument]?.name != RETENTION_PARAMETER_NAME) { + return AnnotationRetention.RUNTIME + } + val retentionName = (retentionArgument.calleeReference as? FirResolvedNamedReference)?.name?.asString() + ?: return AnnotationRetention.RUNTIME + return AnnotationRetention.values().firstOrNull { it.name == retentionName } ?: AnnotationRetention.RUNTIME +} + +private val defaultAnnotationTargets = listOf( + AnnotationTarget.CLASS, + AnnotationTarget.PROPERTY, + AnnotationTarget.FIELD, + AnnotationTarget.LOCAL_VARIABLE, + AnnotationTarget.VALUE_PARAMETER, + AnnotationTarget.CONSTRUCTOR, + AnnotationTarget.FUNCTION, + AnnotationTarget.PROPERTY_GETTER, + AnnotationTarget.PROPERTY_SETTER, +) + +fun FirAnnotationCall.getAllowedAnnotationTargets(session: FirSession): List { + val annotationClass = (this.annotationTypeRef.coneType as? ConeClassLikeType)?.lookupTag?.toSymbol(session)?.fir as? FirRegularClass + return annotationClass?.getAllowedAnnotationTargets() ?: defaultAnnotationTargets +} + +fun FirRegularClass.getAllowedAnnotationTargets(): List { + val targetAnnotation = getTargetAnnotation() ?: return defaultAnnotationTargets + val argumentMapping = targetAnnotation.argumentMapping ?: return defaultAnnotationTargets + val targetArgument = argumentMapping.keys.firstOrNull() as? FirVarargArgumentsExpression + ?: return defaultAnnotationTargets + if (argumentMapping[targetArgument]?.name != TARGET_PARAMETER_NAME) { + return defaultAnnotationTargets + } + return targetArgument.arguments.mapNotNull { argument -> + val targetExpression = argument as? FirQualifiedAccessExpression + val targetName = (targetExpression?.calleeReference as? FirResolvedNamedReference)?.name?.asString() ?: return@mapNotNull null + AnnotationTarget.values().firstOrNull { target -> target.name == targetName } + } +} + +fun FirAnnotatedDeclaration.getRetentionAnnotation(): FirAnnotationCall? { + return getAnnotationByFqName(StandardNames.FqNames.retention) +} + +fun FirAnnotatedDeclaration.getTargetAnnotation(): FirAnnotationCall? { + return getAnnotationByFqName(StandardNames.FqNames.target) +} + +fun FirAnnotatedDeclaration.getAnnotationByFqName(fqName: FqName): FirAnnotationCall? { + return annotations.find { + (it.annotationTypeRef.coneType as? ConeClassLikeType)?.lookupTag?.classId?.asSingleFqName() == fqName + } +} + diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirAnnotationClassDeclarationChecker.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirAnnotationClassDeclarationChecker.kt index 76d3d3c435f..08282d38336 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirAnnotationClassDeclarationChecker.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirAnnotationClassDeclarationChecker.kt @@ -10,6 +10,7 @@ import org.jetbrains.kotlin.KtNodeTypes.VALUE_PARAMETER import org.jetbrains.kotlin.descriptors.ClassKind.ANNOTATION_CLASS import org.jetbrains.kotlin.descriptors.ClassKind.ENUM_CLASS import org.jetbrains.kotlin.fir.FirSession +import org.jetbrains.kotlin.fir.analysis.checkers.* import org.jetbrains.kotlin.fir.analysis.checkers.checkConstantArguments import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext import org.jetbrains.kotlin.fir.analysis.diagnostics.* @@ -29,73 +30,84 @@ object FirAnnotationClassDeclarationChecker : FirRegularClassChecker() { reporter.reportOn(declaration.source, FirErrors.SUPERTYPES_FOR_ANNOTATION_CLASS, context) } - for (it in declaration.declarations) { - when { - it is FirConstructor && it.isPrimary -> { - for (parameter in it.valueParameters) { - val source = parameter.source ?: continue - if (!source.hasValOrVar()) { - reporter.reportOn(source, FirErrors.MISSING_VAL_ON_ANNOTATION_PARAMETER, context) - } else if (source.hasVar()) { - reporter.reportOn(source, FirErrors.VAR_ANNOTATION_PARAMETER, context) - } - val defaultValue = parameter.defaultValue - if (defaultValue != null && checkConstantArguments(defaultValue, context.session) != null) { - reporter.reportOn(defaultValue.source, FirErrors.ANNOTATION_PARAMETER_DEFAULT_VALUE_MUST_BE_CONSTANT, context) - } + for (member in declaration.declarations) { + checkAnnotationClassMember(member, context, reporter) + } - val typeRef = parameter.returnTypeRef - val coneType = typeRef.coneTypeSafe() - val classId = coneType?.classId + if (declaration.getRetention() != AnnotationRetention.SOURCE && + AnnotationTarget.EXPRESSION in declaration.getAllowedAnnotationTargets() + ) { + val target = declaration.getRetentionAnnotation() ?: declaration.getTargetAnnotation() ?: declaration + reporter.reportOn(target.source, FirErrors.RESTRICTED_RETENTION_FOR_EXPRESSION_ANNOTATION, context) + } + } - if (coneType != null) when { - classId == ClassId.fromString("") -> { - // TODO: replace with UNRESOLVED_REFERENCE check - } - coneType.isNullable -> { - reporter.reportOn(typeRef.source, FirErrors.NULLABLE_TYPE_OF_ANNOTATION_MEMBER, context) - } - coneType.isPrimitiveOrNullablePrimitive -> { - // DO NOTHING: primitives are allowed as annotation class parameter - } - coneType.isUnsignedTypeOrNullableUnsignedType -> { - // TODO: replace with EXPERIMENTAL_UNSIGNED_LITERALS check - } - classId == StandardClassIds.KClass -> { - // DO NOTHING: KClass is allowed - } - classId == StandardClassIds.String -> { - // DO NOTHING: String is allowed - } - classId in primitiveArrayTypeByElementType.values -> { - // DO NOTHING: primitive arrays are allowed - } - classId == StandardClassIds.Array -> { - if (!isAllowedArray(typeRef, context.session)) - reporter.reportOn(typeRef.source, FirErrors.INVALID_TYPE_OF_ANNOTATION_MEMBER, context) - } - isAllowedClassKind(coneType, context.session) -> { - // DO NOTHING: annotation or enum classes are allowed - } - else -> { + private fun checkAnnotationClassMember(member: FirDeclaration, context: CheckerContext, reporter: DiagnosticReporter) { + when { + member is FirConstructor && member.isPrimary -> { + for (parameter in member.valueParameters) { + val source = parameter.source ?: continue + if (!source.hasValOrVar()) { + reporter.reportOn(source, FirErrors.MISSING_VAL_ON_ANNOTATION_PARAMETER, context) + } else if (source.hasVar()) { + reporter.reportOn(source, FirErrors.VAR_ANNOTATION_PARAMETER, context) + } + val defaultValue = parameter.defaultValue + if (defaultValue != null && checkConstantArguments(defaultValue, context.session) != null) { + reporter.reportOn(defaultValue.source, FirErrors.ANNOTATION_PARAMETER_DEFAULT_VALUE_MUST_BE_CONSTANT, context) + } + + val typeRef = parameter.returnTypeRef + val coneType = typeRef.coneTypeSafe() + val classId = coneType?.classId + + if (coneType != null) when { + classId == ClassId.fromString("") -> { + // TODO: replace with UNRESOLVED_REFERENCE check + } + coneType.isNullable -> { + reporter.reportOn(typeRef.source, FirErrors.NULLABLE_TYPE_OF_ANNOTATION_MEMBER, context) + } + coneType.isPrimitiveOrNullablePrimitive -> { + // DO NOTHING: primitives are allowed as annotation class parameter + } + coneType.isUnsignedTypeOrNullableUnsignedType -> { + // TODO: replace with EXPERIMENTAL_UNSIGNED_LITERALS check + } + classId == StandardClassIds.KClass -> { + // DO NOTHING: KClass is allowed + } + classId == StandardClassIds.String -> { + // DO NOTHING: String is allowed + } + classId in primitiveArrayTypeByElementType.values -> { + // DO NOTHING: primitive arrays are allowed + } + classId == StandardClassIds.Array -> { + if (!isAllowedArray(typeRef, context.session)) reporter.reportOn(typeRef.source, FirErrors.INVALID_TYPE_OF_ANNOTATION_MEMBER, context) - } + } + isAllowedClassKind(coneType, context.session) -> { + // DO NOTHING: annotation or enum classes are allowed + } + else -> { + reporter.reportOn(typeRef.source, FirErrors.INVALID_TYPE_OF_ANNOTATION_MEMBER, context) } } } - it is FirRegularClass -> { - // DO NOTHING: nested annotation classes are allowed in 1.3+ - } - it is FirProperty && it.source?.elementType == VALUE_PARAMETER -> { - // DO NOTHING to avoid reporting constructor properties - } - it is FirSimpleFunction && it.source?.elementType != FUN -> { - // DO NOTHING to avoid reporting synthetic functions - // TODO: replace with origin check - } - else -> { - reporter.reportOn(it.source, FirErrors.ANNOTATION_CLASS_MEMBER, context) - } + } + member is FirRegularClass -> { + // DO NOTHING: nested annotation classes are allowed in 1.3+ + } + member is FirProperty && member.source?.elementType == VALUE_PARAMETER -> { + // DO NOTHING to avoid reporting constructor properties + } + member is FirSimpleFunction && member.source?.elementType != FUN -> { + // DO NOTHING to avoid reporting synthetic functions + // TODO: replace with origin check + } + else -> { + reporter.reportOn(member.source, FirErrors.ANNOTATION_CLASS_MEMBER, 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 new file mode 100644 index 00000000000..ce6e648c894 --- /dev/null +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/FirExpressionAnnotationChecker.kt @@ -0,0 +1,24 @@ +/* + * Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.fir.analysis.checkers.expression + +import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext +import org.jetbrains.kotlin.fir.analysis.checkers.getAllowedAnnotationTargets +import org.jetbrains.kotlin.fir.analysis.checkers.getRetention +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.analysis.diagnostics.withSuppressedDiagnostics +import org.jetbrains.kotlin.fir.expressions.FirStatement + +object FirExpressionAnnotationChecker : FirBasicExpressionChecker() { + override fun check(expression: FirStatement, context: CheckerContext, reporter: DiagnosticReporter) { + for (annotation in expression.annotations) { + withSuppressedDiagnostics(annotation, context) { + } + } + } +} \ No newline at end of file diff --git a/compiler/fir/entrypoint/src/org/jetbrains/kotlin/fir/checkers/CommonExpressionCheckers.kt b/compiler/fir/entrypoint/src/org/jetbrains/kotlin/fir/checkers/CommonExpressionCheckers.kt index 4f5f86c0a43..b1a56680bc1 100644 --- a/compiler/fir/entrypoint/src/org/jetbrains/kotlin/fir/checkers/CommonExpressionCheckers.kt +++ b/compiler/fir/entrypoint/src/org/jetbrains/kotlin/fir/checkers/CommonExpressionCheckers.kt @@ -16,7 +16,8 @@ object CommonExpressionCheckers : ExpressionCheckers() { override val basicExpressionCheckers: Set get() = setOf( - FirReservedUnderscoreExpressionChecker + FirReservedUnderscoreExpressionChecker, + FirExpressionAnnotationChecker, ) override val qualifiedAccessCheckers: Set diff --git a/compiler/testData/diagnostics/tests/annotations/RetentionsOfAnnotationWithExpressionTarget_after.fir.kt b/compiler/testData/diagnostics/tests/annotations/RetentionsOfAnnotationWithExpressionTarget_after.fir.kt deleted file mode 100644 index a20223575a0..00000000000 --- a/compiler/testData/diagnostics/tests/annotations/RetentionsOfAnnotationWithExpressionTarget_after.fir.kt +++ /dev/null @@ -1,13 +0,0 @@ -// !LANGUAGE: +RestrictRetentionForExpressionAnnotations - -@Target(AnnotationTarget.EXPRESSION) -@Retention(AnnotationRetention.SOURCE) -annotation class TestRetentionSource - -@Target(AnnotationTarget.EXPRESSION) -@Retention(AnnotationRetention.BINARY) -annotation class TestRetentionBinary - -@Target(AnnotationTarget.EXPRESSION) -@Retention(AnnotationRetention.RUNTIME) -annotation class TestRetentionRuntime \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/annotations/RetentionsOfAnnotationWithExpressionTarget_after.kt b/compiler/testData/diagnostics/tests/annotations/RetentionsOfAnnotationWithExpressionTarget_after.kt index d978f5d96f8..b229fad848b 100644 --- a/compiler/testData/diagnostics/tests/annotations/RetentionsOfAnnotationWithExpressionTarget_after.kt +++ b/compiler/testData/diagnostics/tests/annotations/RetentionsOfAnnotationWithExpressionTarget_after.kt @@ -1,3 +1,4 @@ +// FIR_IDENTICAL // !LANGUAGE: +RestrictRetentionForExpressionAnnotations @Target(AnnotationTarget.EXPRESSION) diff --git a/compiler/testData/diagnostics/tests/annotations/RetentionsOfAnnotationWithExpressionTarget_before.fir.kt b/compiler/testData/diagnostics/tests/annotations/RetentionsOfAnnotationWithExpressionTarget_before.fir.kt index a3cfeb4173d..1fee868317f 100644 --- a/compiler/testData/diagnostics/tests/annotations/RetentionsOfAnnotationWithExpressionTarget_before.fir.kt +++ b/compiler/testData/diagnostics/tests/annotations/RetentionsOfAnnotationWithExpressionTarget_before.fir.kt @@ -5,9 +5,9 @@ annotation class TestRetentionSource @Target(AnnotationTarget.EXPRESSION) -@Retention(AnnotationRetention.BINARY) +@Retention(AnnotationRetention.BINARY) annotation class TestRetentionBinary @Target(AnnotationTarget.EXPRESSION) -@Retention(AnnotationRetention.RUNTIME) +@Retention(AnnotationRetention.RUNTIME) annotation class TestRetentionRuntime \ No newline at end of file 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 cc82805b21f..8d26fa1d2ab 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 @@ -607,6 +607,12 @@ internal val KT_DIAGNOSTIC_CONVERTER = KtDiagnosticConverterBuilder.buildConvert token, ) } + add(FirErrors.RESTRICTED_RETENTION_FOR_EXPRESSION_ANNOTATION) { firDiagnostic -> + RestrictedRetentionForExpressionAnnotationImpl( + firDiagnostic as FirPsiDiagnostic<*>, + token, + ) + } add(FirErrors.EXPOSED_TYPEALIAS_EXPANDED_TYPE) { firDiagnostic -> ExposedTypealiasExpandedTypeImpl( 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 43134979767..432467bb54d 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 @@ -434,6 +434,10 @@ sealed class KtFirDiagnostic : KtDiagnosticWithPsi { override val diagnosticClass get() = AnnotationOnSuperclass::class } + abstract class RestrictedRetentionForExpressionAnnotation : KtFirDiagnostic() { + override val diagnosticClass get() = RestrictedRetentionForExpressionAnnotation::class + } + abstract class ExposedTypealiasExpandedType : KtFirDiagnostic() { override val diagnosticClass get() = ExposedTypealiasExpandedType::class abstract val elementVisibility: EffectiveVisibility 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 b4c28341e7d..a095ba95778 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 @@ -705,6 +705,13 @@ internal class AnnotationOnSuperclassImpl( override val firDiagnostic: FirPsiDiagnostic<*> by weakRef(firDiagnostic) } +internal class RestrictedRetentionForExpressionAnnotationImpl( + firDiagnostic: FirPsiDiagnostic<*>, + override val token: ValidityToken, +) : KtFirDiagnostic.RestrictedRetentionForExpressionAnnotation(), KtAbstractFirDiagnostic { + override val firDiagnostic: FirPsiDiagnostic<*> by weakRef(firDiagnostic) +} + internal class ExposedTypealiasExpandedTypeImpl( override val elementVisibility: EffectiveVisibility, override val restrictingDeclaration: KtSymbol,