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 54d1ab78ac6..ece43e0cb12 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 @@ -280,11 +280,11 @@ object DIAGNOSTICS_LIST : DiagnosticList("FirErrors") { parameter("optInMarkerFqName") parameter("message") } - val OPT_IN_OVERRIDE by warning { + val OPT_IN_OVERRIDE by warning(PositioningStrategy.DECLARATION_NAME) { parameter("optInMarkerFqName") parameter("message") } - val OPT_IN_OVERRIDE_ERROR by error { + val OPT_IN_OVERRIDE_ERROR by error(PositioningStrategy.DECLARATION_NAME) { parameter("optInMarkerFqName") parameter("message") } 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 c166370f9fb..f77e16dbfcf 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 @@ -242,8 +242,8 @@ object FirErrors { // OptIn val OPT_IN_USAGE by warning2(SourceElementPositioningStrategies.REFERENCE_BY_QUALIFIED) val OPT_IN_USAGE_ERROR by error2(SourceElementPositioningStrategies.REFERENCE_BY_QUALIFIED) - val OPT_IN_OVERRIDE by warning2() - val OPT_IN_OVERRIDE_ERROR by error2() + val OPT_IN_OVERRIDE by warning2(SourceElementPositioningStrategies.DECLARATION_NAME) + val OPT_IN_OVERRIDE_ERROR by error2(SourceElementPositioningStrategies.DECLARATION_NAME) val OPT_IN_IS_NOT_ENABLED by warning0(SourceElementPositioningStrategies.REFERENCED_NAME_BY_QUALIFIED) val OPT_IN_CAN_ONLY_BE_USED_AS_ANNOTATION by error0() val OPT_IN_MARKER_CAN_ONLY_BE_USED_AS_ANNOTATION_OR_ARGUMENT_IN_OPT_IN by error0() diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirOverrideChecker.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirOverrideChecker.kt index 691ae83aa39..8b550d44b9c 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirOverrideChecker.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirOverrideChecker.kt @@ -10,10 +10,13 @@ import org.jetbrains.kotlin.descriptors.Visibilities import org.jetbrains.kotlin.fir.* 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.expression.FirOptInUsageBaseChecker +import org.jetbrains.kotlin.fir.analysis.checkers.expression.FirOptInUsageBaseChecker.Experimentality import org.jetbrains.kotlin.fir.analysis.checkers.unsubstitutedScope 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.analysis.overridesBackwardCompatibilityHelper import org.jetbrains.kotlin.fir.declarations.* import org.jetbrains.kotlin.fir.declarations.utils.isFinal @@ -47,7 +50,10 @@ object FirOverrideChecker : FirClassChecker() { for (it in declaration.declarations) { if (it is FirSimpleFunction || it is FirProperty) { - checkMember((it as FirCallableDeclaration).symbol, declaration, reporter, typeCheckerState, firTypeScope, context) + val callable = it as FirCallableDeclaration + withSuppressedDiagnostics(callable, context) { + checkMember(callable.symbol, declaration, reporter, typeCheckerState, firTypeScope, context) + } } } } @@ -271,6 +277,8 @@ object FirOverrideChecker : FirClassChecker() { return } + checkOverriddenExperimentalities(member, overriddenMemberSymbols, context, reporter) + checkModality(overriddenMemberSymbols)?.let { reporter.reportOverridingFinalMember(member, it, context) } @@ -302,6 +310,24 @@ object FirOverrideChecker : FirClassChecker() { } } + @OptIn(SymbolInternals::class) + private fun checkOverriddenExperimentalities( + memberSymbol: FirCallableSymbol<*>, + overriddenMemberSymbols: List>, + context: CheckerContext, + reporter: DiagnosticReporter + ) { + with(FirOptInUsageBaseChecker) { + val experimentalities = mutableSetOf() + for (overriddenMemberSymbol in overriddenMemberSymbols) { + overriddenMemberSymbol.loadExperimentalitiesFromAnnotationTo(context.session, experimentalities) + } + reportNotAcceptedOverrideExperimentalities( + experimentalities, memberSymbol, context, reporter + ) + } + } + private fun DiagnosticReporter.reportNothingToOverride(declaration: FirCallableSymbol<*>, context: CheckerContext) { reportOn(declaration.source, FirErrors.NOTHING_TO_OVERRIDE, declaration, context) } 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 index 0b99e35d9a8..b447d95a865 100644 --- 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 @@ -32,6 +32,7 @@ import org.jetbrains.kotlin.fir.types.* import org.jetbrains.kotlin.name.ClassId import org.jetbrains.kotlin.fir.symbols.SymbolInternals import org.jetbrains.kotlin.fir.symbols.ensureResolved +import org.jetbrains.kotlin.fir.symbols.impl.FirCallableSymbol import org.jetbrains.kotlin.fir.symbols.impl.FirNamedFunctionSymbol import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol import org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol @@ -54,6 +55,29 @@ object FirOptInUsageBaseChecker { return fir.loadExperimentalityForMarkerAnnotation() } + fun FirBasedSymbol<*>.loadExperimentalitiesFromAnnotationTo(session: FirSession, result: MutableCollection) { + ensureResolved(FirResolvePhase.STATUS) + @OptIn(SymbolInternals::class) + (fir as? FirAnnotatedDeclaration)?.loadExperimentalitiesFromAnnotationTo(session, result) + } + + private fun FirAnnotatedDeclaration.loadExperimentalitiesFromAnnotationTo( + session: FirSession, + result: MutableCollection, + fromSetter: Boolean = false + ) { + for (annotation in annotations) { + val annotationType = annotation.annotationTypeRef.coneTypeSafe() + if (annotation.useSiteTarget != AnnotationUseSiteTarget.PROPERTY_SETTER || fromSetter) { + result.addIfNotNull( + annotationType?.lookupTag?.toFirRegularClassSymbol( + session + )?.loadExperimentalityForMarkerAnnotation() + ) + } + } + } + fun loadExperimentalitiesFromTypeArguments( context: CheckerContext, typeArguments: List @@ -133,16 +157,7 @@ object FirOptInUsageBaseChecker { parentClassSymbol?.loadExperimentalities(context, result, visited, fromSetter = false, dispatchReceiverType = null) } - for (annotation in fir.annotations) { - val annotationType = annotation.annotationTypeRef.coneTypeSafe() - if (annotation.useSiteTarget != AnnotationUseSiteTarget.PROPERTY_SETTER || fromSetter) { - result.addIfNotNull( - annotationType?.lookupTag?.toFirRegularClassSymbol( - session - )?.loadExperimentalityForMarkerAnnotation() - ) - } - } + fir.loadExperimentalitiesFromAnnotationTo(session, result, fromSetter) if (fir is FirTypeAlias) { fir.expandedTypeRef.coneType.addExperimentalities(context, result, visited) @@ -195,19 +210,39 @@ object FirOptInUsageBaseChecker { ) { for ((annotationClassId, severity, message) in experimentalities) { if (!isExperimentalityAcceptableInContext(annotationClassId, context)) { - val diagnostic = when (severity) { - Experimentality.Severity.WARNING -> FirErrors.OPT_IN_USAGE - Experimentality.Severity.ERROR -> FirErrors.OPT_IN_USAGE_ERROR - } - val reportedMessage = message ?: when (severity) { - Experimentality.Severity.WARNING -> "This declaration is experimental and its usage should be marked" - Experimentality.Severity.ERROR -> "This declaration is experimental and its usage must be marked" + val (diagnostic, verb) = when (severity) { + Experimentality.Severity.WARNING -> FirErrors.OPT_IN_USAGE to "should" + Experimentality.Severity.ERROR -> FirErrors.OPT_IN_USAGE_ERROR to "must" } + val reportedMessage = message ?: "This declaration is experimental and its usage $verb be marked" reporter.reportOn(element.source, diagnostic, annotationClassId.asSingleFqName(), reportedMessage, context) } } } + @SymbolInternals + fun reportNotAcceptedOverrideExperimentalities( + experimentalities: Collection, + symbol: FirCallableSymbol<*>, + context: CheckerContext, + reporter: DiagnosticReporter + ) { + for ((annotationClassId, severity, _) in experimentalities) { + if (!symbol.fir.isExperimentalityAcceptable(annotationClassId) && + !isExperimentalityAcceptableInContext(annotationClassId, context) + ) { + val (diagnostic, verb) = when (severity) { + Experimentality.Severity.WARNING -> FirErrors.OPT_IN_OVERRIDE to "should" + Experimentality.Severity.ERROR -> FirErrors.OPT_IN_OVERRIDE_ERROR to "must" + } + val reportedMessage = "This declaration overrides experimental member of supertype " + + "'${symbol.callableId.className?.shortName()?.asString()}' and $verb be annotated " + + "with '@${annotationClassId.asFqNameString()}'" + reporter.reportOn(symbol.source, diagnostic, annotationClassId.asSingleFqName(), reportedMessage, context) + } + } + } + private fun isExperimentalityAcceptableInContext( annotationClassId: ClassId, context: CheckerContext diff --git a/compiler/testData/diagnostics/testsWithStdLib/experimental/errors.fir.kt b/compiler/testData/diagnostics/testsWithStdLib/experimental/errors.fir.kt deleted file mode 100644 index 5572039fd4a..00000000000 --- a/compiler/testData/diagnostics/testsWithStdLib/experimental/errors.fir.kt +++ /dev/null @@ -1,27 +0,0 @@ -// !OPT_IN: kotlin.RequiresOptIn -// FILE: api.kt - -package api - -@RequiresOptIn -@Retention(AnnotationRetention.BINARY) -annotation class E - -open class Base { - @E - open fun foo() {} -} - -// FILE: usage.kt - -package usage - -import api.* - -class Derived : Base() { - override fun foo() {} -} - -fun test(b: Base) { - b.foo() -} diff --git a/compiler/testData/diagnostics/testsWithStdLib/experimental/errors.kt b/compiler/testData/diagnostics/testsWithStdLib/experimental/errors.kt index d210fb4093a..1d9b8ff26a6 100644 --- a/compiler/testData/diagnostics/testsWithStdLib/experimental/errors.kt +++ b/compiler/testData/diagnostics/testsWithStdLib/experimental/errors.kt @@ -1,3 +1,4 @@ +// FIR_IDENTICAL // !OPT_IN: kotlin.RequiresOptIn // FILE: api.kt diff --git a/compiler/testData/diagnostics/testsWithStdLib/experimental/override.fir.kt b/compiler/testData/diagnostics/testsWithStdLib/experimental/override.fir.kt deleted file mode 100644 index 86dca23475c..00000000000 --- a/compiler/testData/diagnostics/testsWithStdLib/experimental/override.fir.kt +++ /dev/null @@ -1,45 +0,0 @@ -// !OPT_IN: kotlin.RequiresOptIn -// FILE: api.kt - -package api - -@RequiresOptIn(level = RequiresOptIn.Level.WARNING) -@Retention(AnnotationRetention.BINARY) -annotation class E - -open class Base { - @E - open fun foo() {} -} - -class DerivedInSameModule : Base() { - override fun foo() {} -} - -// FILE: usage-propagate.kt - -package usage1 - -import api.* - -open class Derived : Base() { - @E - override fun foo() {} -} - -class SubDerived : Derived() - -@E -class Derived2 : Base() { - override fun foo() {} -} - -// FILE: usage-none.kt - -package usage2 - -import api.* - -class Derived : Base() { - override fun foo() {} -} diff --git a/compiler/testData/diagnostics/testsWithStdLib/experimental/override.kt b/compiler/testData/diagnostics/testsWithStdLib/experimental/override.kt index 41238bde81a..7fae82f7d50 100644 --- a/compiler/testData/diagnostics/testsWithStdLib/experimental/override.kt +++ b/compiler/testData/diagnostics/testsWithStdLib/experimental/override.kt @@ -1,3 +1,4 @@ +// FIR_IDENTICAL // !OPT_IN: kotlin.RequiresOptIn // FILE: api.kt diff --git a/compiler/testData/diagnostics/testsWithStdLib/experimental/overrideDifferentExperimentalities.fir.kt b/compiler/testData/diagnostics/testsWithStdLib/experimental/overrideDifferentExperimentalities.fir.kt deleted file mode 100644 index ea36e226f68..00000000000 --- a/compiler/testData/diagnostics/testsWithStdLib/experimental/overrideDifferentExperimentalities.fir.kt +++ /dev/null @@ -1,38 +0,0 @@ -// !OPT_IN: kotlin.RequiresOptIn - -@RequiresOptIn(level = RequiresOptIn.Level.WARNING) -@Retention(AnnotationRetention.BINARY) -annotation class E1 - -@RequiresOptIn(level = RequiresOptIn.Level.WARNING) -@Retention(AnnotationRetention.BINARY) -annotation class E3 - -interface Base1 { - @E1 - fun foo() -} - -interface Base2 { - fun foo() -} - -interface Base3 { - @E3 - fun foo() -} - -class DerivedA : Base1, Base2, Base3 { - override fun foo() {} -} - -class DerivedB : Base1, Base3 { - @E3 - override fun foo() {} -} - -class DerivedC : Base1, Base2, Base3 { - @E1 - @E3 - override fun foo() {} -} diff --git a/compiler/testData/diagnostics/testsWithStdLib/experimental/overrideDifferentExperimentalities.kt b/compiler/testData/diagnostics/testsWithStdLib/experimental/overrideDifferentExperimentalities.kt index 65169fce4cf..675cafe192f 100644 --- a/compiler/testData/diagnostics/testsWithStdLib/experimental/overrideDifferentExperimentalities.kt +++ b/compiler/testData/diagnostics/testsWithStdLib/experimental/overrideDifferentExperimentalities.kt @@ -1,3 +1,4 @@ +// FIR_IDENTICAL // !OPT_IN: kotlin.RequiresOptIn @RequiresOptIn(level = RequiresOptIn.Level.WARNING)