FIR: report OPT_IN_OVERRIDE(_ERROR) diagnostics

This commit is contained in:
Mikhail Glukhikh
2021-09-22 18:57:40 +03:00
committed by TeamCityServer
parent 4052befe88
commit ac3b738d9b
10 changed files with 86 additions and 132 deletions
@@ -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")
}
@@ -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>()
@@ -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)
}
@@ -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
@@ -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() {}
}
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
// !OPT_IN: kotlin.RequiresOptIn
@RequiresOptIn(level = RequiresOptIn.Level.WARNING)