FIR: report OPT_IN_MARKER_ON_OVERRIDE diagnostic

This diagnostic works in accordance with KT-45844 and KT-49002
This commit is contained in:
Mikhail Glukhikh
2021-09-23 11:42:16 +03:00
committed by TeamCityServer
parent ad20d78a63
commit e9bff861dd
5 changed files with 40 additions and 163 deletions
@@ -5,13 +5,16 @@
package org.jetbrains.kotlin.fir.analysis.checkers.declaration
import org.jetbrains.kotlin.config.LanguageFeature
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.descriptors.Visibilities
import org.jetbrains.kotlin.descriptors.annotations.AnnotationUseSiteTarget
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.outerClassSymbol
import org.jetbrains.kotlin.fir.analysis.checkers.unsubstitutedScope
import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
@@ -24,6 +27,7 @@ import org.jetbrains.kotlin.fir.declarations.utils.isOverride
import org.jetbrains.kotlin.fir.declarations.utils.modality
import org.jetbrains.kotlin.fir.declarations.utils.visibility
import org.jetbrains.kotlin.fir.resolve.substitution.substitutorByMap
import org.jetbrains.kotlin.fir.resolve.toFirRegularClassSymbol
import org.jetbrains.kotlin.fir.resolve.toSymbol
import org.jetbrains.kotlin.fir.scopes.FirTypeScope
import org.jetbrains.kotlin.fir.scopes.getDirectOverriddenFunctions
@@ -32,10 +36,8 @@ import org.jetbrains.kotlin.fir.scopes.impl.toConeType
import org.jetbrains.kotlin.fir.symbols.SymbolInternals
import org.jetbrains.kotlin.fir.symbols.ensureResolved
import org.jetbrains.kotlin.fir.symbols.impl.*
import org.jetbrains.kotlin.fir.types.ConeKotlinErrorType
import org.jetbrains.kotlin.fir.types.ConeKotlinType
import org.jetbrains.kotlin.fir.types.coneType
import org.jetbrains.kotlin.fir.types.upperBoundIfFlexible
import org.jetbrains.kotlin.fir.types.*
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.types.AbstractTypeChecker
import org.jetbrains.kotlin.types.TypeCheckerState
@@ -318,16 +320,45 @@ object FirOverrideChecker : FirClassChecker() {
reporter: DiagnosticReporter
) {
with(FirOptInUsageBaseChecker) {
val experimentalities = mutableSetOf<Experimentality>()
val overriddenExperimentalities = mutableSetOf<Experimentality>()
val session = context.session
for (overriddenMemberSymbol in overriddenMemberSymbols) {
overriddenMemberSymbol.loadExperimentalitiesFromAnnotationTo(context.session, experimentalities)
overriddenMemberSymbol.loadExperimentalitiesFromAnnotationTo(session, overriddenExperimentalities)
}
reportNotAcceptedOverrideExperimentalities(
experimentalities, memberSymbol, context, reporter
overriddenExperimentalities, memberSymbol, context, reporter
)
for (annotation in memberSymbol.fir.annotations) {
val annotationType = annotation.annotationTypeRef.coneTypeSafe<ConeClassLikeType>()
val lookupTag = annotationType?.lookupTag ?: continue
withSuppressedDiagnostics(annotation, context) {
val useSiteTarget = annotation.useSiteTarget
if (useSiteTarget == null || useSiteTarget == AnnotationUseSiteTarget.PROPERTY) {
val experimentality = lookupTag.toFirRegularClassSymbol(session)?.loadExperimentalityForMarkerAnnotation()
if (experimentality != null && experimentality !in overriddenExperimentalities && overriddenMemberSymbols.all {
val ownerClassSymbol = it.dispatchReceiverClassOrNull()?.toFirRegularClassSymbol(session)
ownerClassSymbol == null || !ownerClassSymbol.hasAnnotationItselfOrInParent(context, lookupTag.classId)
}
) {
val factory = if (session.languageVersionSettings.supportsFeature(LanguageFeature.OptInOnOverrideForbidden)) {
FirErrors.OPT_IN_MARKER_ON_OVERRIDE
} else {
FirErrors.OPT_IN_MARKER_ON_OVERRIDE_WARNING
}
reporter.reportOn(annotation.source, factory, context)
}
}
}
}
}
}
private tailrec fun FirRegularClassSymbol.hasAnnotationItselfOrInParent(context: CheckerContext, classId: ClassId): Boolean {
if (getAnnotationByClassId(classId) != null) return true
val outerClassSymbol = outerClassSymbol(context) as? FirRegularClassSymbol ?: return false
return outerClassSymbol.hasAnnotationItselfOrInParent(context, classId)
}
private fun DiagnosticReporter.reportNothingToOverride(declaration: FirCallableSymbol<*>, context: CheckerContext) {
reportOn(declaration.source, FirErrors.NOTHING_TO_OVERRIDE, declaration, context)
}
@@ -1,12 +0,0 @@
// !OPT_IN: kotlin.RequiresOptIn
// !LANGUAGE: -OptInOnOverrideForbidden
@RequiresOptIn(level = RequiresOptIn.Level.WARNING)
@Retention(AnnotationRetention.BINARY)
annotation class E
class My {
@E
override fun hashCode() = 0
}
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
// !OPT_IN: kotlin.RequiresOptIn
// !LANGUAGE: -OptInOnOverrideForbidden
@@ -1,144 +0,0 @@
// !OPT_IN: kotlin.RequiresOptIn
// !LANGUAGE: +OptInOnOverrideForbidden
// FILE: api.kt
package api
import kotlin.annotation.AnnotationTarget.*
@RequiresOptIn(level = RequiresOptIn.Level.WARNING)
@Target(CLASS, ANNOTATION_CLASS, PROPERTY, FIELD, LOCAL_VARIABLE, VALUE_PARAMETER, CONSTRUCTOR, FUNCTION,
PROPERTY_SETTER, TYPEALIAS)
@Retention(AnnotationRetention.BINARY)
annotation class E1
@RequiresOptIn(level = RequiresOptIn.Level.WARNING)
<!OPT_IN_MARKER_WITH_WRONG_TARGET!>@Target(FILE)<!>
annotation class E2
@RequiresOptIn(level = RequiresOptIn.Level.WARNING)
<!OPT_IN_MARKER_WITH_WRONG_TARGET!>@Target(EXPRESSION)<!>
<!OPT_IN_MARKER_WITH_WRONG_RETENTION!>@Retention(AnnotationRetention.SOURCE)<!>
annotation class E3
@RequiresOptIn(level = RequiresOptIn.Level.WARNING)
<!OPT_IN_MARKER_WITH_WRONG_TARGET!>@Target(TYPE_PARAMETER)<!>
@Retention(AnnotationRetention.BINARY)
annotation class E3A
@RequiresOptIn(level = RequiresOptIn.Level.WARNING)
<!OPT_IN_MARKER_WITH_WRONG_TARGET!>@Target(TYPE)<!>
@Retention(AnnotationRetention.BINARY)
annotation class E3B
@RequiresOptIn(level = RequiresOptIn.Level.WARNING)
@Target(PROPERTY_GETTER)
@Retention(AnnotationRetention.BINARY)
annotation class E4
@RequiresOptIn(level = RequiresOptIn.Level.WARNING)
@Target(PROPERTY_SETTER)
@Retention(AnnotationRetention.BINARY)
annotation class E5
@RequiresOptIn(level = RequiresOptIn.Level.WARNING)
@Target(PROPERTY, FUNCTION, PROPERTY_SETTER, VALUE_PARAMETER, FIELD, LOCAL_VARIABLE, CLASS)
@Retention(AnnotationRetention.BINARY)
annotation class E6
var some: Int
<!OPT_IN_MARKER_ON_WRONG_TARGET!>@E4<!>
get() = 42
@E5
set(value) {}
<!OPT_IN_MARKER_ON_WRONG_TARGET!>@get:E4<!>
val another: Int = 42
class My {
@E6
override fun hashCode() = 0
}
interface Base {
val bar: Int
val baz: Int
@E6
fun foo()
fun String.withReceiver()
}
class Derived : Base {
@E6
override val bar: Int = 42
@set:E6 <!OPT_IN_MARKER_ON_WRONG_TARGET!>@setparam:E6<!>
override var baz: Int = 13
@E6
override fun foo() {}
override fun <!OPT_IN_MARKER_ON_WRONG_TARGET!>@receiver:E6<!> String.withReceiver() {}
}
@E6
interface BaseMarked {
val bar: Int
}
@E6
class Outer {
interface Nested {
val baz: Int
}
}
@OptIn(E6::class)
class DerivedOptIn : BaseMarked, Outer.Nested {
@E6
override val bar: Int = 42 // Ok
@E6
override val baz: Int = 24 // Ok
}
abstract class Another(<!OPT_IN_MARKER_ON_WRONG_TARGET!>@param:E6<!> val x: String) : Base {
<!OPT_IN_MARKER_ON_WRONG_TARGET!>@delegate:E6<!>
override val bar: Int by lazy { 42 }
fun baz(<!OPT_IN_MARKER_ON_WRONG_TARGET!>@E6<!> param: Int) {
<!OPT_IN_MARKER_ON_WRONG_TARGET!>@E6<!> val x = param
}
}
interface A {
fun f()
}
interface B {
@E6
fun f()
}
interface C1 : A, B
interface C2 : B, A
class X1 : C1 {
@E6 // Ok
override fun f() {}
}
class X2 : C2 {
@E6 // Ok
override fun f() {}
}
open class Y(val b: B): B by b
class Z(b: B) : Y(b) {
@E6
override fun f() {}
}
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
// !OPT_IN: kotlin.RequiresOptIn
// !LANGUAGE: +OptInOnOverrideForbidden
// FILE: api.kt