diff --git a/compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromSource/bad/returnsImplies/notNull.kt b/compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromSource/bad/returnsImplies/notNull.kt index b08344fe660..d9019d0fbb1 100644 --- a/compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromSource/bad/returnsImplies/notNull.kt +++ b/compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromSource/bad/returnsImplies/notNull.kt @@ -1,3 +1,4 @@ +// !USE_EXPERIMENTAL: kotlin.RequiresOptIn import kotlin.contracts.* @OptIn(ExperimentalContracts::class) diff --git a/compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromSource/bad/returnsImplies/propertyGetter.kt b/compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromSource/bad/returnsImplies/propertyGetter.kt index 9c806b92371..61c6636b8e7 100644 --- a/compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromSource/bad/returnsImplies/propertyGetter.kt +++ b/compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromSource/bad/returnsImplies/propertyGetter.kt @@ -1,3 +1,4 @@ +// !USE_EXPERIMENTAL: kotlin.RequiresOptIn import kotlin.contracts.* @OptIn(ExperimentalContracts::class) diff --git a/compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromSource/good/returnsImplies/conditionLogic.kt b/compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromSource/good/returnsImplies/conditionLogic.kt index 403dd4ca9d7..68c724a9810 100644 --- a/compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromSource/good/returnsImplies/conditionLogic.kt +++ b/compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromSource/good/returnsImplies/conditionLogic.kt @@ -1,3 +1,4 @@ +// !USE_EXPERIMENTAL: kotlin.RequiresOptIn import kotlin.contracts.* @OptIn(ExperimentalContracts::class) diff --git a/compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromSource/good/returnsImplies/inapplicable.kt b/compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromSource/good/returnsImplies/inapplicable.kt index 794a43e3a21..31e865f5892 100644 --- a/compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromSource/good/returnsImplies/inapplicable.kt +++ b/compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromSource/good/returnsImplies/inapplicable.kt @@ -1,3 +1,4 @@ +// !USE_EXPERIMENTAL: kotlin.RequiresOptIn import kotlin.contracts.* @OptIn(ExperimentalContracts::class) diff --git a/compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromSource/good/returnsImplies/notNull.kt b/compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromSource/good/returnsImplies/notNull.kt index 2b9f4792413..ac8772a5630 100644 --- a/compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromSource/good/returnsImplies/notNull.kt +++ b/compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromSource/good/returnsImplies/notNull.kt @@ -1,3 +1,4 @@ +// !USE_EXPERIMENTAL: kotlin.RequiresOptIn import kotlin.contracts.* @OptIn(ExperimentalContracts::class) diff --git a/compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromSource/good/returnsImplies/safeCall.kt b/compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromSource/good/returnsImplies/safeCall.kt index 6e14e47c283..a4a91986da6 100644 --- a/compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromSource/good/returnsImplies/safeCall.kt +++ b/compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromSource/good/returnsImplies/safeCall.kt @@ -1,3 +1,4 @@ +// !USE_EXPERIMENTAL: kotlin.RequiresOptIn import kotlin.contracts.* @OptIn(ExperimentalContracts::class) diff --git a/compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromSource/good/returnsImplies/trickyCases.kt b/compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromSource/good/returnsImplies/trickyCases.kt index 98be9306a5a..e3432ab7c54 100644 --- a/compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromSource/good/returnsImplies/trickyCases.kt +++ b/compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromSource/good/returnsImplies/trickyCases.kt @@ -1,3 +1,4 @@ +// !USE_EXPERIMENTAL: kotlin.RequiresOptIn import kotlin.contracts.* @OptIn(ExperimentalContracts::class) 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 07918f7f32a..937b3aa28f1 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 @@ -23,6 +23,7 @@ import org.jetbrains.kotlin.fir.symbols.impl.* import org.jetbrains.kotlin.fir.types.ConeKotlinType import org.jetbrains.kotlin.lexer.KtKeywordToken import org.jetbrains.kotlin.lexer.KtModifierKeywordToken +import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.resolve.ForbiddenNamedArgumentsTarget @@ -212,6 +213,36 @@ object DIAGNOSTICS_LIST : DiagnosticList("FirErrors") { val INAPPLICABLE_FILE_TARGET by error(PositioningStrategy.ANNOTATION_USE_SITE) } + val EXPERIMENTAL by object : DiagnosticGroup("OptIn-related") { + val EXPERIMENTAL_API_USAGE by warning(PositioningStrategy.REFERENCE_BY_QUALIFIED) { + parameter("optInMarkerFqName") + parameter("message") + } + val EXPERIMENTAL_API_USAGE_ERROR by error(PositioningStrategy.REFERENCE_BY_QUALIFIED) { + parameter("optInMarkerFqName") + parameter("message") + } + val EXPERIMENTAL_OVERRIDE by warning { + parameter("optInMarkerFqName") + parameter("message") + } + val EXPERIMENTAL_OVERRIDE_ERROR by error { + parameter("optInMarkerFqName") + parameter("message") + } + val EXPERIMENTAL_IS_NOT_ENABLED by warning(PositioningStrategy.REFERENCED_NAME_BY_QUALIFIED) + val EXPERIMENTAL_CAN_ONLY_BE_USED_AS_ANNOTATION by error() + val EXPERIMENTAL_MARKER_CAN_ONLY_BE_USED_AS_ANNOTATION_OR_ARGUMENT_IN_USE_EXPERIMENTAL by error() + val USE_EXPERIMENTAL_WITHOUT_ARGUMENTS by warning() + val USE_EXPERIMENTAL_ARGUMENT_IS_NOT_MARKER by warning { + parameter("notMarkerFqName") + } + val EXPERIMENTAL_ANNOTATION_WITH_WRONG_TARGET by error { + parameter("target") + } + val EXPERIMENTAL_ANNOTATION_WITH_WRONG_RETENTION by error() + } + val EXPOSED_VISIBILITY by object : DiagnosticGroup("Exposed visibility") { val EXPOSED_TYPEALIAS_EXPANDED_TYPE by exposedVisibilityError(PositioningStrategy.DECLARATION_NAME) val EXPOSED_FUNCTION_RETURN_TYPE by exposedVisibilityError(PositioningStrategy.DECLARATION_NAME) 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 24dd1e2e902..8afdd9928e1 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 @@ -34,6 +34,7 @@ import org.jetbrains.kotlin.fir.symbols.impl.FirVariableSymbol import org.jetbrains.kotlin.fir.types.ConeKotlinType import org.jetbrains.kotlin.lexer.KtKeywordToken import org.jetbrains.kotlin.lexer.KtModifierKeywordToken +import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.psi.KtAnnotation import org.jetbrains.kotlin.psi.KtAnnotationEntry @@ -200,6 +201,19 @@ object FirErrors { val REDUNDANT_ANNOTATION_TARGET by warning1() val INAPPLICABLE_FILE_TARGET by error0(SourceElementPositioningStrategies.ANNOTATION_USE_SITE) + // OptIn-related + val EXPERIMENTAL_API_USAGE by warning2(SourceElementPositioningStrategies.REFERENCE_BY_QUALIFIED) + val EXPERIMENTAL_API_USAGE_ERROR by error2(SourceElementPositioningStrategies.REFERENCE_BY_QUALIFIED) + val EXPERIMENTAL_OVERRIDE by warning2() + val EXPERIMENTAL_OVERRIDE_ERROR by error2() + val EXPERIMENTAL_IS_NOT_ENABLED by warning0(SourceElementPositioningStrategies.REFERENCED_NAME_BY_QUALIFIED) + val EXPERIMENTAL_CAN_ONLY_BE_USED_AS_ANNOTATION by error0() + val EXPERIMENTAL_MARKER_CAN_ONLY_BE_USED_AS_ANNOTATION_OR_ARGUMENT_IN_USE_EXPERIMENTAL by error0() + val USE_EXPERIMENTAL_WITHOUT_ARGUMENTS by warning0() + val USE_EXPERIMENTAL_ARGUMENT_IS_NOT_MARKER by warning1() + val EXPERIMENTAL_ANNOTATION_WITH_WRONG_TARGET by error1() + val EXPERIMENTAL_ANNOTATION_WITH_WRONG_RETENTION by error0() + // Exposed visibility val EXPOSED_TYPEALIAS_EXPANDED_TYPE by error3(SourceElementPositioningStrategies.DECLARATION_NAME) val EXPOSED_FUNCTION_RETURN_TYPE by error3(SourceElementPositioningStrategies.DECLARATION_NAME) diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/CommonDeclarationCheckers.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/CommonDeclarationCheckers.kt index a7c7e39fc5c..60304affd4c 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/CommonDeclarationCheckers.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/CommonDeclarationCheckers.kt @@ -71,6 +71,7 @@ object CommonDeclarationCheckers : DeclarationCheckers() { override val regularClassCheckers: Set get() = setOf( FirAnnotationClassDeclarationChecker, + FirOptInAnnotationClassChecker, FirCommonConstructorDelegationIssuesChecker, FirConstructorInInterfaceChecker, FirDelegationSuperCallInEnumConstructorChecker, diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/CommonExpressionCheckers.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/CommonExpressionCheckers.kt index 93d341db359..33cb7ca3d70 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/CommonExpressionCheckers.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/CommonExpressionCheckers.kt @@ -12,6 +12,7 @@ object CommonExpressionCheckers : ExpressionCheckers() { override val annotationCallCheckers: Set get() = setOf( FirAnnotationArgumentChecker, + FirOptInAnnotationCallChecker, ) override val basicExpressionCheckers: Set 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 75a532b4754..4c4fdd9c391 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 @@ -7,6 +7,7 @@ package org.jetbrains.kotlin.fir.analysis.checkers import org.jetbrains.kotlin.builtins.StandardNames import org.jetbrains.kotlin.descriptors.annotations.KotlinTarget +import org.jetbrains.kotlin.fir.FirAnnotationContainer import org.jetbrains.kotlin.fir.FirSession import org.jetbrains.kotlin.fir.declarations.FirAnnotatedDeclaration import org.jetbrains.kotlin.fir.declarations.FirRegularClass @@ -14,6 +15,7 @@ import org.jetbrains.kotlin.fir.expressions.* import org.jetbrains.kotlin.fir.references.FirResolvedNamedReference import org.jetbrains.kotlin.fir.resolve.fullyExpandedType import org.jetbrains.kotlin.fir.resolve.toSymbol +import org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol import org.jetbrains.kotlin.fir.types.ConeClassLikeType import org.jetbrains.kotlin.fir.types.FirErrorTypeRef import org.jetbrains.kotlin.fir.types.coneType @@ -49,11 +51,7 @@ fun FirAnnotationCall.getAllowedAnnotationTargets(session: FirSession): Set { val targetAnnotation = getTargetAnnotation() ?: return defaultAnnotationTargets if (targetAnnotation.argumentList.arguments.isEmpty()) return emptySet() - val arguments = when (val targetArgument = targetAnnotation.findSingleArgumentByName(TARGET_PARAMETER_NAME)) { - is FirVarargArgumentsExpression -> targetArgument.arguments - is FirArrayOfCall -> targetArgument.arguments - else -> return defaultAnnotationTargets - } + val arguments = targetAnnotation.findSingleArgumentByName(TARGET_PARAMETER_NAME)?.unfoldArrayOrVararg().orEmpty() return arguments.mapNotNullTo(mutableSetOf()) { argument -> val targetExpression = argument as? FirQualifiedAccessExpression @@ -70,7 +68,7 @@ fun FirAnnotatedDeclaration.getTargetAnnotation(): FirAnnotationCall? { return getAnnotationByFqName(StandardNames.FqNames.target) } -fun FirAnnotatedDeclaration.getAnnotationByFqName(fqName: FqName): FirAnnotationCall? { +fun FirAnnotationContainer.getAnnotationByFqName(fqName: FqName): FirAnnotationCall? { return annotations.find { (it.annotationTypeRef.coneType as? ConeClassLikeType)?.lookupTag?.classId?.asSingleFqName() == fqName } @@ -79,7 +77,7 @@ fun FirAnnotatedDeclaration.getAnnotationByFqName(fqName: FqName): FirAnnotation fun FirAnnotationCall.findSingleArgumentByName(name: Name): FirExpression? { val argumentMapping = argumentMapping if (argumentMapping != null) { - return argumentMapping.keys.firstOrNull()?.takeIf { argumentMapping[it]?.name == name } + return argumentMapping.keys.firstOrNull()?.takeIf { argumentMapping[it]?.name == name }?.unwrapArgument() } // NB: we have to consider both cases, because deserializer does not create argument mapping val arguments = argumentList.arguments @@ -87,3 +85,19 @@ fun FirAnnotationCall.findSingleArgumentByName(name: Name): FirExpression? { return firstArgument.takeIf { it.name == name }?.expression } +fun FirExpression.extractClassesFromArgument(): List { + return unfoldArrayOrVararg().mapNotNull { + if (it !is FirGetClassCall) return@mapNotNull null + val qualifier = it.argument as? FirResolvedQualifier ?: return@mapNotNull null + qualifier.symbol as? FirRegularClassSymbol + } +} + +private fun FirExpression.unfoldArrayOrVararg(): List { + return when (this) { + is FirVarargArgumentsExpression -> arguments + is FirArrayOfCall -> arguments + else -> return emptyList() + } +} + diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/FirSinceKotlinHelpers.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/FirSinceKotlinHelpers.kt new file mode 100644 index 00000000000..f3fbdc1f239 --- /dev/null +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/FirSinceKotlinHelpers.kt @@ -0,0 +1,98 @@ +/* + * 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.config.ApiVersion +import org.jetbrains.kotlin.fir.FirSession +import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext +import org.jetbrains.kotlin.fir.declarations.FirAnnotatedDeclaration +import org.jetbrains.kotlin.fir.declarations.FirConstructor +import org.jetbrains.kotlin.fir.declarations.FirTypeAlias +import org.jetbrains.kotlin.fir.expressions.FirConstExpression +import org.jetbrains.kotlin.fir.expressions.arguments +import org.jetbrains.kotlin.fir.languageVersionSettings +import org.jetbrains.kotlin.fir.resolve.symbolProvider +import org.jetbrains.kotlin.fir.resolve.toSymbol +import org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol +import org.jetbrains.kotlin.fir.types.ConeClassLikeType +import org.jetbrains.kotlin.fir.types.coneType +import org.jetbrains.kotlin.resolve.SINCE_KOTLIN_FQ_NAME +import org.jetbrains.kotlin.resolve.checkers.OptInNames + +sealed class FirSinceKotlinAccessibility { + object Accessible : FirSinceKotlinAccessibility() + + data class NotAccessibleButWasExperimental( + val version: ApiVersion, + val markerClasses: List + ) : FirSinceKotlinAccessibility() + + data class NotAccessible( + val version: ApiVersion + ) : FirSinceKotlinAccessibility() +} + +private data class FirSinceKotlinValue( + val apiVersion: ApiVersion, + val wasExperimentalMarkerClasses: List +) + +fun FirAnnotatedDeclaration.checkSinceKotlinVersionAccessibility(context: CheckerContext): FirSinceKotlinAccessibility { + val value = getOwnSinceKotlinVersion(context.session) + val version = value?.apiVersion + val languageVersionSettings = context.session.languageVersionSettings + + // Allow access in the following cases: + // 1) There's no @SinceKotlin annotation for this descriptor + // 2) There's a @SinceKotlin annotation but its value is some unrecognizable nonsense + // 3) The value as a version is not greater than our API version + if (version == null || version <= languageVersionSettings.apiVersion) return FirSinceKotlinAccessibility.Accessible + + val wasExperimentalFqNames = value.wasExperimentalMarkerClasses + if (wasExperimentalFqNames.isNotEmpty()) { + return FirSinceKotlinAccessibility.NotAccessibleButWasExperimental(version, wasExperimentalFqNames) + } + + return FirSinceKotlinAccessibility.NotAccessible(version) +} + +private fun FirAnnotatedDeclaration.getOwnSinceKotlinVersion(session: FirSession): FirSinceKotlinValue? { + var result: FirSinceKotlinValue? = null + + // TODO: use-site targeted annotations + fun FirAnnotatedDeclaration.consider() { + val sinceKotlinSingleArgument = getAnnotationByFqName(SINCE_KOTLIN_FQ_NAME)?.arguments?.singleOrNull() + val apiVersion = ((sinceKotlinSingleArgument as? FirConstExpression<*>)?.value as? String)?.let(ApiVersion.Companion::parse) + if (apiVersion != null) { + // TODO: combine wasExperimentalMarkerClasses in case of several associated declarations with the same maximal API version + if (result == null || apiVersion > result!!.apiVersion) { + result = FirSinceKotlinValue(apiVersion, loadWasExperimentalMarkerClasses()) + } + } + } + + this.consider() + if (this is FirConstructor) { + val classId = symbol.callableId.classId + if (classId != null) { + val classSymbol = session.symbolProvider.getClassLikeSymbolByFqName(classId) + classSymbol?.fir?.consider() + } + } + + if (this is FirTypeAlias) { + (this.expandedTypeRef.coneType as? ConeClassLikeType)?.lookupTag?.toSymbol(session)?.fir?.consider() + } + + return result +} + +private fun FirAnnotatedDeclaration.loadWasExperimentalMarkerClasses(): List { + val wasExperimental = getAnnotationByFqName(OptInNames.WAS_EXPERIMENTAL_FQ_NAME) ?: return emptyList() + val annotationClasses = wasExperimental.findSingleArgumentByName(OptInNames.WAS_EXPERIMENTAL_ANNOTATION_CLASS) ?: return emptyList() + return annotationClasses.extractClassesFromArgument() +} + diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirOptInAnnotationClassChecker.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirOptInAnnotationClassChecker.kt new file mode 100644 index 00000000000..e9c839f56ac --- /dev/null +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirOptInAnnotationClassChecker.kt @@ -0,0 +1,39 @@ +/* + * 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.declaration + +import org.jetbrains.kotlin.descriptors.ClassKind +import org.jetbrains.kotlin.descriptors.annotations.KotlinTarget +import org.jetbrains.kotlin.fir.analysis.checkers.* +import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext +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.FirRegularClass +import org.jetbrains.kotlin.resolve.checkers.Experimentality +import org.jetbrains.kotlin.resolve.checkers.OptInNames + +object FirOptInAnnotationClassChecker : FirRegularClassChecker() { + override fun check(declaration: FirRegularClass, context: CheckerContext, reporter: DiagnosticReporter) { + if (declaration.classKind != ClassKind.ANNOTATION_CLASS) return + if (declaration.getAnnotationByFqName(OptInNames.REQUIRES_OPT_IN_FQ_NAME) == null) return + if (declaration.getRetention() == AnnotationRetention.SOURCE) { + val target = declaration.getRetentionAnnotation() + reporter.reportOn(target?.source, FirErrors.EXPERIMENTAL_ANNOTATION_WITH_WRONG_RETENTION, context) + + } + val wrongTargets = declaration.getAllowedAnnotationTargets().intersect(Experimentality.WRONG_TARGETS_FOR_MARKER) + if (wrongTargets.isNotEmpty()) { + val target = declaration.getTargetAnnotation() + reporter.reportOn( + target?.source, + FirErrors.EXPERIMENTAL_ANNOTATION_WITH_WRONG_TARGET, + wrongTargets.joinToString(transform = KotlinTarget::description), + context + ) + } + } +} \ No newline at end of file diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/FirClassLiteralChecker.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/FirClassLiteralChecker.kt index 9cd285715d7..53bf9408465 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/FirClassLiteralChecker.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/FirClassLiteralChecker.kt @@ -20,11 +20,19 @@ import org.jetbrains.kotlin.fir.scopes.impl.toConeType import org.jetbrains.kotlin.fir.symbols.impl.FirTypeParameterSymbol import org.jetbrains.kotlin.fir.types.* import org.jetbrains.kotlin.lexer.KtTokens.QUEST +import org.jetbrains.kotlin.resolve.checkers.OptInNames object FirClassLiteralChecker : FirGetClassCallChecker() { override fun check(expression: FirGetClassCall, context: CheckerContext, reporter: DiagnosticReporter) { val source = expression.source ?: return if (source.kind is FirFakeSourceElementKind) return + val argument = expression.argument + if (argument is FirResolvedQualifier) { + val fqName = argument.classId?.asSingleFqName() + if (fqName in OptInNames.EXPERIMENTAL_FQ_NAMES || fqName in OptInNames.USE_EXPERIMENTAL_FQ_NAMES) { + reporter.reportOn(argument.source, FirErrors.EXPERIMENTAL_CAN_ONLY_BE_USED_AS_ANNOTATION, context) + } + } // Note that raw FIR drops marked nullability "?" in, e.g., `A?::class`, `A::class`, or `A?::class`. // That is, AST structures for those expressions have token type QUEST, whereas FIR element doesn't have any information about it. @@ -36,7 +44,6 @@ object FirClassLiteralChecker : FirGetClassCallChecker() { // // Only the 2nd example is valid, and we want to check if token type QUEST doesn't exist at the same level as COLONCOLON. val markedNullable = source.getChild(QUEST, depth = 1) != null - val argument = expression.argument val isNullable = markedNullable || (argument as? FirResolvedQualifier)?.isNullableLHSForCallableReference == true || argument.typeRef.coneType.isMarkedNullable || diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/FirOptInAnnotationCallChecker.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/FirOptInAnnotationCallChecker.kt new file mode 100644 index 00000000000..93962a8c218 --- /dev/null +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/FirOptInAnnotationCallChecker.kt @@ -0,0 +1,63 @@ +/* + * 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.config.AnalysisFlags +import org.jetbrains.kotlin.fir.FirSourceElement +import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext +import org.jetbrains.kotlin.fir.analysis.checkers.extractClassesFromArgument +import org.jetbrains.kotlin.fir.analysis.checkers.findSingleArgumentByName +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.expressions.* +import org.jetbrains.kotlin.fir.languageVersionSettings +import org.jetbrains.kotlin.fir.types.ConeClassLikeType +import org.jetbrains.kotlin.fir.types.coneTypeSafe +import org.jetbrains.kotlin.resolve.checkers.OptInNames + +object FirOptInAnnotationCallChecker : FirAnnotationCallChecker() { + override fun check(expression: FirAnnotationCall, context: CheckerContext, reporter: DiagnosticReporter) { + val lookupTag = expression.annotationTypeRef.coneTypeSafe()?.lookupTag ?: return + val fqName = lookupTag.classId.asSingleFqName() + val isMarker = fqName == OptInNames.REQUIRES_OPT_IN_FQ_NAME + val isOptIn = fqName == OptInNames.OPT_IN_FQ_NAME + if (isMarker || isOptIn) { + checkUsageOfKotlinExperimentalOrUseExperimental(expression.source, context, reporter) + if (isOptIn) { + val arguments = expression.arguments + if (arguments.isEmpty()) { + reporter.reportOn(expression.source, FirErrors.USE_EXPERIMENTAL_WITHOUT_ARGUMENTS, context) + } else { + val annotationClasses = expression.findSingleArgumentByName(OptInNames.USE_EXPERIMENTAL_ANNOTATION_CLASS) + for (classSymbol in annotationClasses?.extractClassesFromArgument().orEmpty()) { + with(FirOptInUsageBaseChecker) { + if (classSymbol.fir.loadExperimentalityForMarkerAnnotation() == null) { + reporter.reportOn( + expression.source, + FirErrors.USE_EXPERIMENTAL_ARGUMENT_IS_NOT_MARKER, + classSymbol.classId.asSingleFqName(), + context + ) + } + } + } + } + } + } + } + + private fun checkUsageOfKotlinExperimentalOrUseExperimental( + element: FirSourceElement?, + context: CheckerContext, + reporter: DiagnosticReporter + ) { + val useExperimentalFqNames = context.session.languageVersionSettings.getFlag(AnalysisFlags.useExperimental) + if (OptInNames.REQUIRES_OPT_IN_FQ_NAME.asString() !in useExperimentalFqNames) { + reporter.reportOn(element, FirErrors.EXPERIMENTAL_IS_NOT_ENABLED, context) + } + } +} \ No newline at end of file diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/FirOptInUsageBaseChecker.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/FirOptInUsageBaseChecker.kt new file mode 100644 index 00000000000..c88318858c1 --- /dev/null +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/FirOptInUsageBaseChecker.kt @@ -0,0 +1,37 @@ +/* + * 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.findSingleArgumentByName +import org.jetbrains.kotlin.fir.analysis.checkers.getAnnotationByFqName +import org.jetbrains.kotlin.fir.declarations.FirRegularClass +import org.jetbrains.kotlin.fir.expressions.FirConstExpression +import org.jetbrains.kotlin.fir.expressions.FirQualifiedAccessExpression +import org.jetbrains.kotlin.fir.references.FirResolvedNamedReference +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.resolve.checkers.Experimentality +import org.jetbrains.kotlin.resolve.checkers.OptInNames + +object FirOptInUsageBaseChecker { + internal fun FirRegularClass.loadExperimentalityForMarkerAnnotation(): Experimentality? { + val experimental = getAnnotationByFqName(OptInNames.REQUIRES_OPT_IN_FQ_NAME) ?: return null + + val levelArgument = experimental.findSingleArgumentByName(LEVEL) as? FirQualifiedAccessExpression + val levelName = (levelArgument?.calleeReference as? FirResolvedNamedReference)?.name?.asString() + val level = OptInLevel.values().firstOrNull { it.name == levelName } ?: OptInLevel.DEFAULT + val message = (experimental.findSingleArgumentByName(MESSAGE) as? FirConstExpression<*>)?.value as? String + return Experimentality(symbol.classId.asSingleFqName(), level.severity, message) + } + + private val LEVEL = Name.identifier("level") + private val MESSAGE = Name.identifier("message") + + private enum class OptInLevel(val severity: Experimentality.Severity) { + WARNING(Experimentality.Severity.WARNING), + ERROR(Experimentality.Severity.ERROR), + DEFAULT(Experimentality.DEFAULT_SEVERITY) + } +} \ No newline at end of file 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 0102195034f..91f10dba8d7 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 @@ -7,6 +7,7 @@ package org.jetbrains.kotlin.fir.analysis.diagnostics import org.jetbrains.kotlin.diagnostics.rendering.* import org.jetbrains.kotlin.diagnostics.rendering.Renderers.RENDER_POSITION_VARIANCE +import org.jetbrains.kotlin.diagnostics.rendering.Renderers.STRING import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDiagnosticRenderers.AMBIGUOUS_CALLS import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDiagnosticRenderers.DECLARATION_NAME import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDiagnosticRenderers.FIR @@ -117,6 +118,15 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.EXPECTED_DELEGATE import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.EXPECTED_LATEINIT_PROPERTY import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.EXPECTED_PRIVATE_DECLARATION import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.EXPECTED_PROPERTY_INITIALIZER +import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.EXPERIMENTAL_ANNOTATION_WITH_WRONG_RETENTION +import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.EXPERIMENTAL_ANNOTATION_WITH_WRONG_TARGET +import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.EXPERIMENTAL_API_USAGE +import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.EXPERIMENTAL_API_USAGE_ERROR +import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.EXPERIMENTAL_CAN_ONLY_BE_USED_AS_ANNOTATION +import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.EXPERIMENTAL_IS_NOT_ENABLED +import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.EXPERIMENTAL_MARKER_CAN_ONLY_BE_USED_AS_ANNOTATION_OR_ARGUMENT_IN_USE_EXPERIMENTAL +import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.EXPERIMENTAL_OVERRIDE +import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.EXPERIMENTAL_OVERRIDE_ERROR import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.EXPLICIT_DELEGATION_CALL_REQUIRED import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.EXPOSED_FUNCTION_RETURN_TYPE import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.EXPOSED_PARAMETER_TYPE @@ -336,6 +346,8 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.USELESS_ELVIS import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.USELESS_ELVIS_RIGHT_IS_NULL import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.USELESS_IS_CHECK import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.USELESS_VARARG_ON_PARAMETER +import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.USE_EXPERIMENTAL_ARGUMENT_IS_NOT_MARKER +import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.USE_EXPERIMENTAL_WITHOUT_ARGUMENTS import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.VALUE_CLASS_CANNOT_BE_CLONEABLE import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.VALUE_PARAMETER_WITH_NO_TYPE_ANNOTATION import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.VAL_OR_VAR_ON_CATCH_PARAMETER @@ -363,7 +375,6 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.WRONG_MODIFIER_TA import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.WRONG_NUMBER_OF_TYPE_ARGUMENTS import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.WRONG_SETTER_PARAMETER_TYPE import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.WRONG_SETTER_RETURN_TYPE -import org.jetbrains.kotlin.resolve.VarianceConflictDiagnosticData @Suppress("unused") class FirDefaultErrorMessages : DefaultErrorMessages.Extension { @@ -399,10 +410,10 @@ class FirDefaultErrorMessages : DefaultErrorMessages.Extension { map.put(VARIABLE_EXPECTED, "Variable expected") map.put(DELEGATION_IN_INTERFACE, "Interfaces cannot use delegation") map.put(NESTED_CLASS_NOT_ALLOWED, "{0} is not allowed here", TO_STRING) - map.put(VAL_OR_VAR_ON_LOOP_PARAMETER, "''{0}'' on loop parameter is not allowed", TO_STRING); - map.put(VAL_OR_VAR_ON_FUN_PARAMETER, "''{0}'' on function parameter is not allowed", TO_STRING); - map.put(VAL_OR_VAR_ON_CATCH_PARAMETER, "''{0}'' on catch parameter is not allowed", TO_STRING); - map.put(VAL_OR_VAR_ON_SECONDARY_CONSTRUCTOR_PARAMETER, "''{0}'' on secondary constructor parameter is not allowed", TO_STRING); + map.put(VAL_OR_VAR_ON_LOOP_PARAMETER, "''{0}'' on loop parameter is not allowed", TO_STRING) + map.put(VAL_OR_VAR_ON_FUN_PARAMETER, "''{0}'' on function parameter is not allowed", TO_STRING) + map.put(VAL_OR_VAR_ON_CATCH_PARAMETER, "''{0}'' on catch parameter is not allowed", TO_STRING) + map.put(VAL_OR_VAR_ON_SECONDARY_CONSTRUCTOR_PARAMETER, "''{0}'' on secondary constructor parameter is not allowed", TO_STRING) // Unresolved map.put(INVISIBLE_REFERENCE, "Symbol {0} is invisible", SYMBOL) @@ -528,6 +539,36 @@ class FirDefaultErrorMessages : DefaultErrorMessages.Extension { TO_STRING ) + // OptIn + map.put(EXPERIMENTAL_API_USAGE, "{1}", TO_STRING, STRING) + map.put(EXPERIMENTAL_API_USAGE_ERROR, "{1}", TO_STRING, STRING) + + map.put(EXPERIMENTAL_OVERRIDE, "{1}", TO_STRING, STRING) + map.put(EXPERIMENTAL_OVERRIDE_ERROR, "{1}", TO_STRING, STRING) + + map.put(EXPERIMENTAL_IS_NOT_ENABLED, "This class can only be used with the compiler argument '-Xopt-in=kotlin.RequiresOptIn'") + map.put(EXPERIMENTAL_CAN_ONLY_BE_USED_AS_ANNOTATION, "This class can only be used as an annotation") + map.put( + EXPERIMENTAL_MARKER_CAN_ONLY_BE_USED_AS_ANNOTATION_OR_ARGUMENT_IN_USE_EXPERIMENTAL, + "This class can only be used as an annotation or as an argument to @OptIn" + ) + map.put(USE_EXPERIMENTAL_WITHOUT_ARGUMENTS, "@OptIn without any arguments has no effect") + map.put( + USE_EXPERIMENTAL_ARGUMENT_IS_NOT_MARKER, + "Annotation ''{0}'' is not an opt-in requirement marker, therefore its usage in @OptIn is ignored", + TO_STRING + ) + map.put( + EXPERIMENTAL_ANNOTATION_WITH_WRONG_TARGET, + "Opt-in requirement marker annotation cannot be used on the following code elements: {0}. Please remove these targets", + STRING + ) + map.put( + EXPERIMENTAL_ANNOTATION_WITH_WRONG_RETENTION, + "Opt-in requirement marker annotation cannot be used with SOURCE retention. Please replace retention with BINARY" + ) + + // Exposed visibility group // # map.put( EXPOSED_TYPEALIAS_EXPANDED_TYPE, @@ -585,7 +626,13 @@ class FirDefaultErrorMessages : DefaultErrorMessages.Extension { map.put(TYPE_MISMATCH, "Type mismatch: inferred type is {1} but {0} was expected", TO_STRING, TO_STRING) map.put(THROWABLE_TYPE_MISMATCH, "Throwable type mismatch: actual type is {0}", TO_STRING) map.put(CONDITION_TYPE_MISMATCH, "Condition type mismatch: inferred type is {0} but Boolean was expected", TO_STRING) - map.put(ARGUMENT_TYPE_MISMATCH, "Argument type mismatch: actual type is {1} but {0} was expected", RENDER_TYPE, RENDER_TYPE, NOT_RENDERED) + map.put( + ARGUMENT_TYPE_MISMATCH, + "Argument type mismatch: actual type is {1} but {0} was expected", + RENDER_TYPE, + RENDER_TYPE, + NOT_RENDERED + ) map.put(ASSIGNMENT_TYPE_MISMATCH, "Assignment type mismatch: actual type is {1} but {0} was expected", RENDER_TYPE, RENDER_TYPE) map.put( RESULT_TYPE_MISMATCH, diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/ExperimentalMarkerDeclarationAnnotationChecker.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/ExperimentalMarkerDeclarationAnnotationChecker.kt index ab3c37a86f6..2031c42f368 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/ExperimentalMarkerDeclarationAnnotationChecker.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/ExperimentalMarkerDeclarationAnnotationChecker.kt @@ -33,15 +33,6 @@ import org.jetbrains.kotlin.resolve.descriptorUtil.getAnnotationRetention import org.jetbrains.kotlin.utils.addToStdlib.safeAs class ExperimentalMarkerDeclarationAnnotationChecker(private val module: ModuleDescriptor) : AdditionalAnnotationChecker { - companion object { - private val WRONG_TARGETS_FOR_MARKER = setOf( - KotlinTarget.EXPRESSION, - KotlinTarget.FILE, - KotlinTarget.TYPE, - KotlinTarget.TYPE_PARAMETER - ) - } - override fun checkEntries( entries: List, actualTargets: List, @@ -154,7 +145,7 @@ class ExperimentalMarkerDeclarationAnnotationChecker(private val module: ModuleD if (targetEntry != null) { val (entry, descriptor) = targetEntry val allowedTargets = AnnotationChecker.loadAnnotationTargets(descriptor!!) ?: return - val wrongTargets = allowedTargets.intersect(WRONG_TARGETS_FOR_MARKER) + val wrongTargets = allowedTargets.intersect(Experimentality.WRONG_TARGETS_FOR_MARKER) if (wrongTargets.isNotEmpty()) { trace.report( Errors.EXPERIMENTAL_ANNOTATION_WITH_WRONG_TARGET.on( diff --git a/compiler/testData/diagnostics/testsWithStdLib/experimental/experimentalIsNotEnabled.fir.kt b/compiler/testData/diagnostics/testsWithStdLib/experimental/experimentalIsNotEnabled.fir.kt index 1672323348e..cd63d0e74f1 100644 --- a/compiler/testData/diagnostics/testsWithStdLib/experimental/experimentalIsNotEnabled.fir.kt +++ b/compiler/testData/diagnostics/testsWithStdLib/experimental/experimentalIsNotEnabled.fir.kt @@ -1,6 +1,6 @@ // FILE: api.kt -@RequiresOptIn +@RequiresOptIn @Retention(AnnotationRetention.BINARY) annotation class Marker @@ -18,7 +18,7 @@ fun use2() { f() } -@OptIn(Marker::class) +@OptIn(Marker::class) fun use3() { f() } diff --git a/compiler/testData/diagnostics/testsWithStdLib/experimental/incorrectTargetsForExperimentalAnnotation.fir.kt b/compiler/testData/diagnostics/testsWithStdLib/experimental/incorrectTargetsForExperimentalAnnotation.fir.kt index 31484c4ba77..a1632a96fbb 100644 --- a/compiler/testData/diagnostics/testsWithStdLib/experimental/incorrectTargetsForExperimentalAnnotation.fir.kt +++ b/compiler/testData/diagnostics/testsWithStdLib/experimental/incorrectTargetsForExperimentalAnnotation.fir.kt @@ -12,21 +12,21 @@ import kotlin.annotation.AnnotationTarget.* annotation class E1 @RequiresOptIn(level = RequiresOptIn.Level.WARNING) -@Target(FILE) +@Target(FILE) annotation class E2 @RequiresOptIn(level = RequiresOptIn.Level.WARNING) -@Target(EXPRESSION) -@Retention(AnnotationRetention.SOURCE) +@Target(EXPRESSION) +@Retention(AnnotationRetention.SOURCE) annotation class E3 @RequiresOptIn(level = RequiresOptIn.Level.WARNING) -@Target(TYPE_PARAMETER) +@Target(TYPE_PARAMETER) @Retention(AnnotationRetention.BINARY) annotation class E3A @RequiresOptIn(level = RequiresOptIn.Level.WARNING) -@Target(TYPE) +@Target(TYPE) @Retention(AnnotationRetention.BINARY) annotation class E3B diff --git a/compiler/testData/diagnostics/testsWithStdLib/experimental/incorrectUseExperimental.fir.kt b/compiler/testData/diagnostics/testsWithStdLib/experimental/incorrectUseExperimental.fir.kt deleted file mode 100644 index 5154dcec394..00000000000 --- a/compiler/testData/diagnostics/testsWithStdLib/experimental/incorrectUseExperimental.fir.kt +++ /dev/null @@ -1,9 +0,0 @@ -// !USE_EXPERIMENTAL: kotlin.RequiresOptIn - -annotation class NotAMarker - -@OptIn -fun f1() {} - -@OptIn(NotAMarker::class) -fun f2() {} diff --git a/compiler/testData/diagnostics/testsWithStdLib/experimental/incorrectUseExperimental.kt b/compiler/testData/diagnostics/testsWithStdLib/experimental/incorrectUseExperimental.kt index 96f5e61ae04..704e23b3e0d 100644 --- a/compiler/testData/diagnostics/testsWithStdLib/experimental/incorrectUseExperimental.kt +++ b/compiler/testData/diagnostics/testsWithStdLib/experimental/incorrectUseExperimental.kt @@ -1,3 +1,4 @@ +// FIR_IDENTICAL // !USE_EXPERIMENTAL: kotlin.RequiresOptIn annotation class NotAMarker diff --git a/compiler/testData/diagnostics/testsWithStdLib/experimental/usageNotAsAnnotation.fir.kt b/compiler/testData/diagnostics/testsWithStdLib/experimental/usageNotAsAnnotation.fir.kt index 9d474cd8791..3736163f8d3 100644 --- a/compiler/testData/diagnostics/testsWithStdLib/experimental/usageNotAsAnnotation.fir.kt +++ b/compiler/testData/diagnostics/testsWithStdLib/experimental/usageNotAsAnnotation.fir.kt @@ -35,10 +35,10 @@ fun f4(u: OptIn0 /* TODO */) {} annotation class VarargKClasses(vararg val k: KClass<*>) @VarargKClasses( - RequiresOptIn::class, - OptIn::class, - kotlin.RequiresOptIn::class, - kotlin.OptIn::class + RequiresOptIn::class, + OptIn::class, + kotlin.RequiresOptIn::class, + kotlin.OptIn::class ) fun f5() {} diff --git a/compiler/testData/diagnostics/testsWithStdLib/regression/kt34391.fir.kt b/compiler/testData/diagnostics/testsWithStdLib/regression/kt34391.fir.kt index d480fe08794..16e588632e9 100644 --- a/compiler/testData/diagnostics/testsWithStdLib/regression/kt34391.fir.kt +++ b/compiler/testData/diagnostics/testsWithStdLib/regression/kt34391.fir.kt @@ -15,5 +15,5 @@ class A { } } -@RequiresOptIn(level = RequiresOptIn.Level.ERROR) +@RequiresOptIn(level = RequiresOptIn.Level.ERROR) annotation class ExperimentalTime diff --git a/core/compiler.common/src/org/jetbrains/kotlin/resolve/checkers/Experimentality.kt b/core/compiler.common/src/org/jetbrains/kotlin/resolve/checkers/Experimentality.kt index aa640f6b607..49e3fce825e 100644 --- a/core/compiler.common/src/org/jetbrains/kotlin/resolve/checkers/Experimentality.kt +++ b/core/compiler.common/src/org/jetbrains/kotlin/resolve/checkers/Experimentality.kt @@ -5,6 +5,7 @@ package org.jetbrains.kotlin.resolve.checkers +import org.jetbrains.kotlin.descriptors.annotations.KotlinTarget import org.jetbrains.kotlin.name.FqName data class Experimentality(val annotationFqName: FqName, val severity: Severity, val message: String?) { @@ -12,5 +13,12 @@ data class Experimentality(val annotationFqName: FqName, val severity: Severity, companion object { val DEFAULT_SEVERITY = Severity.ERROR + + val WRONG_TARGETS_FOR_MARKER = setOf( + KotlinTarget.EXPRESSION, + KotlinTarget.FILE, + KotlinTarget.TYPE, + KotlinTarget.TYPE_PARAMETER + ) } } diff --git a/idea/idea-frontend-fir/idea-frontend-fir-generator/src/org/jetbrains/kotlin/idea/frontend/api/fir/generator/HLDiagnosticConverter.kt b/idea/idea-frontend-fir/idea-frontend-fir-generator/src/org/jetbrains/kotlin/idea/frontend/api/fir/generator/HLDiagnosticConverter.kt index 3a13e461c7d..628487f2bcd 100644 --- a/idea/idea-frontend-fir/idea-frontend-fir-generator/src/org/jetbrains/kotlin/idea/frontend/api/fir/generator/HLDiagnosticConverter.kt +++ b/idea/idea-frontend-fir/idea-frontend-fir-generator/src/org/jetbrains/kotlin/idea/frontend/api/fir/generator/HLDiagnosticConverter.kt @@ -26,6 +26,7 @@ import org.jetbrains.kotlin.idea.frontend.api.symbols.* import org.jetbrains.kotlin.idea.frontend.api.types.KtType import org.jetbrains.kotlin.lexer.KtKeywordToken import org.jetbrains.kotlin.lexer.KtModifierKeywordToken +import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.psi.KtExpression import org.jetbrains.kotlin.resolve.ForbiddenNamedArgumentsTarget @@ -252,7 +253,8 @@ private object FirToKtConversionCreator { ForbiddenNamedArgumentsTarget::class, LanguageFeature::class, LanguageVersionSettings::class, - Variance::class + Variance::class, + FqName::class, ) private val KType.kClass: KClass<*> 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 9e6eed5546e..6c9031c7c2c 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 @@ -717,6 +717,82 @@ internal val KT_DIAGNOSTIC_CONVERTER = KtDiagnosticConverterBuilder.buildConvert token, ) } + add(FirErrors.EXPERIMENTAL_API_USAGE) { firDiagnostic -> + ExperimentalApiUsageImpl( + firDiagnostic.a, + firDiagnostic.b, + firDiagnostic as FirPsiDiagnostic<*>, + token, + ) + } + add(FirErrors.EXPERIMENTAL_API_USAGE_ERROR) { firDiagnostic -> + ExperimentalApiUsageErrorImpl( + firDiagnostic.a, + firDiagnostic.b, + firDiagnostic as FirPsiDiagnostic<*>, + token, + ) + } + add(FirErrors.EXPERIMENTAL_OVERRIDE) { firDiagnostic -> + ExperimentalOverrideImpl( + firDiagnostic.a, + firDiagnostic.b, + firDiagnostic as FirPsiDiagnostic<*>, + token, + ) + } + add(FirErrors.EXPERIMENTAL_OVERRIDE_ERROR) { firDiagnostic -> + ExperimentalOverrideErrorImpl( + firDiagnostic.a, + firDiagnostic.b, + firDiagnostic as FirPsiDiagnostic<*>, + token, + ) + } + add(FirErrors.EXPERIMENTAL_IS_NOT_ENABLED) { firDiagnostic -> + ExperimentalIsNotEnabledImpl( + firDiagnostic as FirPsiDiagnostic<*>, + token, + ) + } + add(FirErrors.EXPERIMENTAL_CAN_ONLY_BE_USED_AS_ANNOTATION) { firDiagnostic -> + ExperimentalCanOnlyBeUsedAsAnnotationImpl( + firDiagnostic as FirPsiDiagnostic<*>, + token, + ) + } + add(FirErrors.EXPERIMENTAL_MARKER_CAN_ONLY_BE_USED_AS_ANNOTATION_OR_ARGUMENT_IN_USE_EXPERIMENTAL) { firDiagnostic -> + ExperimentalMarkerCanOnlyBeUsedAsAnnotationOrArgumentInUseExperimentalImpl( + firDiagnostic as FirPsiDiagnostic<*>, + token, + ) + } + add(FirErrors.USE_EXPERIMENTAL_WITHOUT_ARGUMENTS) { firDiagnostic -> + UseExperimentalWithoutArgumentsImpl( + firDiagnostic as FirPsiDiagnostic<*>, + token, + ) + } + add(FirErrors.USE_EXPERIMENTAL_ARGUMENT_IS_NOT_MARKER) { firDiagnostic -> + UseExperimentalArgumentIsNotMarkerImpl( + firDiagnostic.a, + firDiagnostic as FirPsiDiagnostic<*>, + token, + ) + } + add(FirErrors.EXPERIMENTAL_ANNOTATION_WITH_WRONG_TARGET) { firDiagnostic -> + ExperimentalAnnotationWithWrongTargetImpl( + firDiagnostic.a, + firDiagnostic as FirPsiDiagnostic<*>, + token, + ) + } + add(FirErrors.EXPERIMENTAL_ANNOTATION_WITH_WRONG_RETENTION) { firDiagnostic -> + ExperimentalAnnotationWithWrongRetentionImpl( + 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 cb46409f8e5..0e190d16e78 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 @@ -26,6 +26,7 @@ import org.jetbrains.kotlin.idea.frontend.api.symbols.KtVariableSymbol import org.jetbrains.kotlin.idea.frontend.api.types.KtType import org.jetbrains.kotlin.lexer.KtKeywordToken import org.jetbrains.kotlin.lexer.KtModifierKeywordToken +import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.psi.KtAnnotation import org.jetbrains.kotlin.psi.KtAnnotationEntry @@ -513,6 +514,60 @@ sealed class KtFirDiagnostic : KtDiagnosticWithPsi { override val diagnosticClass get() = InapplicableFileTarget::class } + abstract class ExperimentalApiUsage : KtFirDiagnostic() { + override val diagnosticClass get() = ExperimentalApiUsage::class + abstract val optInMarkerFqName: FqName + abstract val message: String + } + + abstract class ExperimentalApiUsageError : KtFirDiagnostic() { + override val diagnosticClass get() = ExperimentalApiUsageError::class + abstract val optInMarkerFqName: FqName + abstract val message: String + } + + abstract class ExperimentalOverride : KtFirDiagnostic() { + override val diagnosticClass get() = ExperimentalOverride::class + abstract val optInMarkerFqName: FqName + abstract val message: String + } + + abstract class ExperimentalOverrideError : KtFirDiagnostic() { + override val diagnosticClass get() = ExperimentalOverrideError::class + abstract val optInMarkerFqName: FqName + abstract val message: String + } + + abstract class ExperimentalIsNotEnabled : KtFirDiagnostic() { + override val diagnosticClass get() = ExperimentalIsNotEnabled::class + } + + abstract class ExperimentalCanOnlyBeUsedAsAnnotation : KtFirDiagnostic() { + override val diagnosticClass get() = ExperimentalCanOnlyBeUsedAsAnnotation::class + } + + abstract class ExperimentalMarkerCanOnlyBeUsedAsAnnotationOrArgumentInUseExperimental : KtFirDiagnostic() { + override val diagnosticClass get() = ExperimentalMarkerCanOnlyBeUsedAsAnnotationOrArgumentInUseExperimental::class + } + + abstract class UseExperimentalWithoutArguments : KtFirDiagnostic() { + override val diagnosticClass get() = UseExperimentalWithoutArguments::class + } + + abstract class UseExperimentalArgumentIsNotMarker : KtFirDiagnostic() { + override val diagnosticClass get() = UseExperimentalArgumentIsNotMarker::class + abstract val notMarkerFqName: FqName + } + + abstract class ExperimentalAnnotationWithWrongTarget : KtFirDiagnostic() { + override val diagnosticClass get() = ExperimentalAnnotationWithWrongTarget::class + abstract val target: String + } + + abstract class ExperimentalAnnotationWithWrongRetention : KtFirDiagnostic() { + override val diagnosticClass get() = ExperimentalAnnotationWithWrongRetention::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 5d49163206f..99a7699fc45 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 @@ -28,6 +28,7 @@ import org.jetbrains.kotlin.idea.frontend.api.tokens.ValidityToken import org.jetbrains.kotlin.idea.frontend.api.types.KtType import org.jetbrains.kotlin.lexer.KtKeywordToken import org.jetbrains.kotlin.lexer.KtModifierKeywordToken +import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.psi.KtAnnotation import org.jetbrains.kotlin.psi.KtAnnotationEntry @@ -832,6 +833,93 @@ internal class InapplicableFileTargetImpl( override val firDiagnostic: FirPsiDiagnostic<*> by weakRef(firDiagnostic) } +internal class ExperimentalApiUsageImpl( + override val optInMarkerFqName: FqName, + override val message: String, + firDiagnostic: FirPsiDiagnostic<*>, + override val token: ValidityToken, +) : KtFirDiagnostic.ExperimentalApiUsage(), KtAbstractFirDiagnostic { + override val firDiagnostic: FirPsiDiagnostic<*> by weakRef(firDiagnostic) +} + +internal class ExperimentalApiUsageErrorImpl( + override val optInMarkerFqName: FqName, + override val message: String, + firDiagnostic: FirPsiDiagnostic<*>, + override val token: ValidityToken, +) : KtFirDiagnostic.ExperimentalApiUsageError(), KtAbstractFirDiagnostic { + override val firDiagnostic: FirPsiDiagnostic<*> by weakRef(firDiagnostic) +} + +internal class ExperimentalOverrideImpl( + override val optInMarkerFqName: FqName, + override val message: String, + firDiagnostic: FirPsiDiagnostic<*>, + override val token: ValidityToken, +) : KtFirDiagnostic.ExperimentalOverride(), KtAbstractFirDiagnostic { + override val firDiagnostic: FirPsiDiagnostic<*> by weakRef(firDiagnostic) +} + +internal class ExperimentalOverrideErrorImpl( + override val optInMarkerFqName: FqName, + override val message: String, + firDiagnostic: FirPsiDiagnostic<*>, + override val token: ValidityToken, +) : KtFirDiagnostic.ExperimentalOverrideError(), KtAbstractFirDiagnostic { + override val firDiagnostic: FirPsiDiagnostic<*> by weakRef(firDiagnostic) +} + +internal class ExperimentalIsNotEnabledImpl( + firDiagnostic: FirPsiDiagnostic<*>, + override val token: ValidityToken, +) : KtFirDiagnostic.ExperimentalIsNotEnabled(), KtAbstractFirDiagnostic { + override val firDiagnostic: FirPsiDiagnostic<*> by weakRef(firDiagnostic) +} + +internal class ExperimentalCanOnlyBeUsedAsAnnotationImpl( + firDiagnostic: FirPsiDiagnostic<*>, + override val token: ValidityToken, +) : KtFirDiagnostic.ExperimentalCanOnlyBeUsedAsAnnotation(), KtAbstractFirDiagnostic { + override val firDiagnostic: FirPsiDiagnostic<*> by weakRef(firDiagnostic) +} + +internal class ExperimentalMarkerCanOnlyBeUsedAsAnnotationOrArgumentInUseExperimentalImpl( + firDiagnostic: FirPsiDiagnostic<*>, + override val token: ValidityToken, +) : KtFirDiagnostic.ExperimentalMarkerCanOnlyBeUsedAsAnnotationOrArgumentInUseExperimental(), KtAbstractFirDiagnostic { + override val firDiagnostic: FirPsiDiagnostic<*> by weakRef(firDiagnostic) +} + +internal class UseExperimentalWithoutArgumentsImpl( + firDiagnostic: FirPsiDiagnostic<*>, + override val token: ValidityToken, +) : KtFirDiagnostic.UseExperimentalWithoutArguments(), KtAbstractFirDiagnostic { + override val firDiagnostic: FirPsiDiagnostic<*> by weakRef(firDiagnostic) +} + +internal class UseExperimentalArgumentIsNotMarkerImpl( + override val notMarkerFqName: FqName, + firDiagnostic: FirPsiDiagnostic<*>, + override val token: ValidityToken, +) : KtFirDiagnostic.UseExperimentalArgumentIsNotMarker(), KtAbstractFirDiagnostic { + override val firDiagnostic: FirPsiDiagnostic<*> by weakRef(firDiagnostic) +} + +internal class ExperimentalAnnotationWithWrongTargetImpl( + override val target: String, + firDiagnostic: FirPsiDiagnostic<*>, + override val token: ValidityToken, +) : KtFirDiagnostic.ExperimentalAnnotationWithWrongTarget(), KtAbstractFirDiagnostic { + override val firDiagnostic: FirPsiDiagnostic<*> by weakRef(firDiagnostic) +} + +internal class ExperimentalAnnotationWithWrongRetentionImpl( + firDiagnostic: FirPsiDiagnostic<*>, + override val token: ValidityToken, +) : KtFirDiagnostic.ExperimentalAnnotationWithWrongRetention(), KtAbstractFirDiagnostic { + override val firDiagnostic: FirPsiDiagnostic<*> by weakRef(firDiagnostic) +} + internal class ExposedTypealiasExpandedTypeImpl( override val elementVisibility: EffectiveVisibility, override val restrictingDeclaration: KtSymbol,