FIR: report OPT_IN_OVERRIDE(_ERROR) diagnostics
This commit is contained in:
committed by
TeamCityServer
parent
4052befe88
commit
ac3b738d9b
+2
-2
@@ -280,11 +280,11 @@ object DIAGNOSTICS_LIST : DiagnosticList("FirErrors") {
|
||||
parameter<FqName>("optInMarkerFqName")
|
||||
parameter<String>("message")
|
||||
}
|
||||
val OPT_IN_OVERRIDE by warning<PsiElement> {
|
||||
val OPT_IN_OVERRIDE by warning<PsiElement>(PositioningStrategy.DECLARATION_NAME) {
|
||||
parameter<FqName>("optInMarkerFqName")
|
||||
parameter<String>("message")
|
||||
}
|
||||
val OPT_IN_OVERRIDE_ERROR by error<PsiElement> {
|
||||
val OPT_IN_OVERRIDE_ERROR by error<PsiElement>(PositioningStrategy.DECLARATION_NAME) {
|
||||
parameter<FqName>("optInMarkerFqName")
|
||||
parameter<String>("message")
|
||||
}
|
||||
|
||||
+2
-2
@@ -242,8 +242,8 @@ object FirErrors {
|
||||
// OptIn
|
||||
val OPT_IN_USAGE by warning2<PsiElement, FqName, String>(SourceElementPositioningStrategies.REFERENCE_BY_QUALIFIED)
|
||||
val OPT_IN_USAGE_ERROR by error2<PsiElement, FqName, String>(SourceElementPositioningStrategies.REFERENCE_BY_QUALIFIED)
|
||||
val OPT_IN_OVERRIDE by warning2<PsiElement, FqName, String>()
|
||||
val OPT_IN_OVERRIDE_ERROR by error2<PsiElement, FqName, String>()
|
||||
val OPT_IN_OVERRIDE by warning2<PsiElement, FqName, String>(SourceElementPositioningStrategies.DECLARATION_NAME)
|
||||
val OPT_IN_OVERRIDE_ERROR by error2<PsiElement, FqName, String>(SourceElementPositioningStrategies.DECLARATION_NAME)
|
||||
val OPT_IN_IS_NOT_ENABLED by warning0<KtAnnotationEntry>(SourceElementPositioningStrategies.REFERENCED_NAME_BY_QUALIFIED)
|
||||
val OPT_IN_CAN_ONLY_BE_USED_AS_ANNOTATION by error0<PsiElement>()
|
||||
val OPT_IN_MARKER_CAN_ONLY_BE_USED_AS_ANNOTATION_OR_ARGUMENT_IN_OPT_IN by error0<PsiElement>()
|
||||
|
||||
+27
-1
@@ -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<FirCallableSymbol<*>>,
|
||||
context: CheckerContext,
|
||||
reporter: DiagnosticReporter
|
||||
) {
|
||||
with(FirOptInUsageBaseChecker) {
|
||||
val experimentalities = mutableSetOf<Experimentality>()
|
||||
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)
|
||||
}
|
||||
|
||||
+52
-17
@@ -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<Experimentality>) {
|
||||
ensureResolved(FirResolvePhase.STATUS)
|
||||
@OptIn(SymbolInternals::class)
|
||||
(fir as? FirAnnotatedDeclaration)?.loadExperimentalitiesFromAnnotationTo(session, result)
|
||||
}
|
||||
|
||||
private fun FirAnnotatedDeclaration.loadExperimentalitiesFromAnnotationTo(
|
||||
session: FirSession,
|
||||
result: MutableCollection<Experimentality>,
|
||||
fromSetter: Boolean = false
|
||||
) {
|
||||
for (annotation in annotations) {
|
||||
val annotationType = annotation.annotationTypeRef.coneTypeSafe<ConeClassLikeType>()
|
||||
if (annotation.useSiteTarget != AnnotationUseSiteTarget.PROPERTY_SETTER || fromSetter) {
|
||||
result.addIfNotNull(
|
||||
annotationType?.lookupTag?.toFirRegularClassSymbol(
|
||||
session
|
||||
)?.loadExperimentalityForMarkerAnnotation()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun loadExperimentalitiesFromTypeArguments(
|
||||
context: CheckerContext,
|
||||
typeArguments: List<FirTypeProjection>
|
||||
@@ -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<ConeClassLikeType>()
|
||||
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<Experimentality>,
|
||||
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
|
||||
|
||||
@@ -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.<!OPT_IN_USAGE_ERROR!>foo<!>()
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
// FIR_IDENTICAL
|
||||
// !OPT_IN: kotlin.RequiresOptIn
|
||||
// FILE: api.kt
|
||||
|
||||
|
||||
@@ -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() {}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
// FIR_IDENTICAL
|
||||
// !OPT_IN: kotlin.RequiresOptIn
|
||||
// FILE: api.kt
|
||||
|
||||
|
||||
compiler/testData/diagnostics/testsWithStdLib/experimental/overrideDifferentExperimentalities.fir.kt
Vendored
-38
@@ -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() {}
|
||||
}
|
||||
Vendored
+1
@@ -1,3 +1,4 @@
|
||||
// FIR_IDENTICAL
|
||||
// !OPT_IN: kotlin.RequiresOptIn
|
||||
|
||||
@RequiresOptIn(level = RequiresOptIn.Level.WARNING)
|
||||
|
||||
Reference in New Issue
Block a user