[K2] Disappeared OPT_IN_MARKER_CAN_ONLY_BE_USED_AS_ANNOTATION_OR_ARGUMENT_IN_OPT_IN (3)

Added checking usages of markers as qualifiers in imports.

^KT-59998
This commit is contained in:
Anastasia.Nekrasova
2023-11-06 18:27:25 +02:00
committed by Space Team
parent 97f09acc92
commit 72d69e16c4
7 changed files with 88 additions and 13 deletions
@@ -151,6 +151,7 @@ object CommonDeclarationCheckers : DeclarationCheckers() {
override val fileCheckers: Set<FirFileChecker>
get() = setOf(
FirImportsChecker,
FirOptInImportsChecker,
FirUnresolvedInMiddleOfImportChecker,
FirTopLevelPropertiesChecker,
)
@@ -0,0 +1,52 @@
/*
* Copyright 2010-2023 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.KtSourceElement
import org.jetbrains.kotlin.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.diagnostics.reportOn
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.checkers.expression.FirOptInUsageBaseChecker.isExperimentalMarker
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.fir.getContainingClassLookupTag
import org.jetbrains.kotlin.fir.resolve.providers.symbolProvider
import org.jetbrains.kotlin.fir.resolve.toSymbol
import org.jetbrains.kotlin.fir.symbols.impl.*
object FirOptInImportsChecker : FirFileChecker() {
override fun check(declaration: FirFile, context: CheckerContext, reporter: DiagnosticReporter) {
declaration.imports.forEach { import ->
if (import !is FirResolvedImport) return@forEach
val source = import.source ?: return@forEach
// 'import package.Class.Marker' is OK, but 'import package.Class.Marker.NestedClass' is not.
val resolvedParentClassId = import.resolvedParentClassId ?: return@forEach
val parentClassSymbol = context.session.symbolProvider.getClassLikeSymbolByClassId(resolvedParentClassId) ?: return@forEach
when (parentClassSymbol) {
is FirRegularClassSymbol -> parentClassSymbol.checkContainingClasses(source, context, reporter)
is FirTypeAliasSymbol -> {
val expandedClassSymbol = parentClassSymbol.fullyExpandedClass(context.session) ?: return@forEach
expandedClassSymbol.checkContainingClasses(source, context, reporter)
}
else -> return@forEach
}
}
}
private tailrec fun FirClassLikeSymbol<*>.checkContainingClasses(
source: KtSourceElement,
context: CheckerContext,
reporter: DiagnosticReporter,
) {
if (isExperimentalMarker(context.session)) {
reporter.reportOn(source, FirErrors.OPT_IN_MARKER_CAN_ONLY_BE_USED_AS_ANNOTATION_OR_ARGUMENT_IN_OPT_IN, context)
}
val containingClassSymbol = this.getContainingClassLookupTag()?.toSymbol(context.session) ?: return
containingClassSymbol.checkContainingClasses(source, context, reporter)
}
}
@@ -150,6 +150,10 @@ object FirOptInUsageBaseChecker {
fromSetter = false, dispatchReceiverType = null, fromSupertype = true
)
@OptIn(SymbolInternals::class)
fun FirClassLikeSymbol<*>.isExperimentalMarker(session: FirSession) =
this is FirRegularClassSymbol && fir.getAnnotationByClassId(OptInNames.REQUIRES_OPT_IN_CLASS_ID, session) != null
@OptIn(SymbolInternals::class)
private fun FirBasedSymbol<*>.loadExperimentalities(
context: CheckerContext,
@@ -14,32 +14,28 @@ import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.checkers.expression.FirOptInUsageBaseChecker
import org.jetbrains.kotlin.fir.analysis.checkers.expression.FirOptInUsageBaseChecker.loadExperimentalities
import org.jetbrains.kotlin.fir.analysis.checkers.expression.FirOptInUsageBaseChecker.loadExperimentalitiesFromSupertype
import org.jetbrains.kotlin.fir.analysis.checkers.expression.FirOptInUsageBaseChecker.isExperimentalMarker
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.OPT_IN_CAN_ONLY_BE_USED_AS_ANNOTATION
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.OPT_IN_MARKER_CAN_ONLY_BE_USED_AS_ANNOTATION_OR_ARGUMENT_IN_OPT_IN
import org.jetbrains.kotlin.fir.declarations.FirClass
import org.jetbrains.kotlin.fir.declarations.getAnnotationByClassId
import org.jetbrains.kotlin.fir.expressions.FirAnnotation
import org.jetbrains.kotlin.fir.getContainingClassLookupTag
import org.jetbrains.kotlin.fir.resolve.toSymbol
import org.jetbrains.kotlin.fir.symbols.SymbolInternals
import org.jetbrains.kotlin.fir.symbols.impl.FirClassLikeSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirTypeAliasSymbol
import org.jetbrains.kotlin.fir.types.*
import org.jetbrains.kotlin.resolve.checkers.OptInNames
@OptIn(SymbolInternals::class)
object FirOptInUsageTypeRefChecker : FirTypeRefChecker() {
override fun check(typeRef: FirTypeRef, context: CheckerContext, reporter: DiagnosticReporter) {
val source = typeRef.source
val delegatedTypeRef = (typeRef as? FirResolvedTypeRef)?.delegatedTypeRef
if (source?.kind !is KtRealSourceElementKind) return
val coneType = typeRef.coneTypeSafe<ConeClassLikeType>() ?: return
val symbol = typeRef.findSymbol(context.session) ?: return
val typeAliasExpandedSymbol = (symbol as? FirTypeAliasSymbol)?.resolvedExpandedTypeRef?.findSymbol(context.session)
val processedSymbol = typeAliasExpandedSymbol ?: symbol
val resolvedTypeRef = typeRef as? FirResolvedTypeRef
val qualifier = (resolvedTypeRef?.delegatedTypeRef as? FirUserTypeRef)?.qualifier
val classId = processedSymbol.classId
val lastAnnotationCall = context.callsOrAssignments.lastOrNull() as? FirAnnotation
@@ -49,8 +45,8 @@ object FirOptInUsageTypeRefChecker : FirTypeRefChecker() {
reporter.reportOn(source, OPT_IN_CAN_ONLY_BE_USED_AS_ANNOTATION, context)
processedSymbol.isExperimentalMarker(context.session) ->
reporter.reportOn(source, OPT_IN_MARKER_CAN_ONLY_BE_USED_AS_ANNOTATION_OR_ARGUMENT_IN_OPT_IN, context)
!qualifier.isNullOrEmpty() -> {
processedSymbol.checkContainingClasses(source, qualifier, context, reporter)
delegatedTypeRef is FirUserTypeRef && delegatedTypeRef.qualifier.isNotEmpty() -> {
processedSymbol.checkContainingClasses(source, delegatedTypeRef.qualifier, context, reporter)
}
}
}
@@ -84,9 +80,6 @@ object FirOptInUsageTypeRefChecker : FirTypeRefChecker() {
return coneType.lookupTag.toSymbol(session)
}
private fun FirClassLikeSymbol<*>.isExperimentalMarker(session: FirSession) =
this is FirRegularClassSymbol && fir.getAnnotationByClassId(OptInNames.REQUIRES_OPT_IN_CLASS_ID, session) != null
private tailrec fun FirClassLikeSymbol<*>.checkContainingClasses(
source: KtSourceElement,
qualifier: List<FirQualifierPart>,
@@ -66,6 +66,8 @@ fun f8(): <!OPT_IN_MARKER_CAN_ONLY_BE_USED_AS_ANNOTATION_OR_ARGUMENT_IN_OPT_IN!>
typealias Marker0 = <!OPT_IN_MARKER_CAN_ONLY_BE_USED_AS_ANNOTATION_OR_ARGUMENT_IN_OPT_IN!>A.Marker<!>
typealias Marker1 = <!OPT_IN_MARKER_CAN_ONLY_BE_USED_AS_ANNOTATION_OR_ARGUMENT_IN_OPT_IN!>Marker0<!>
fun f9(m: <!OPT_IN_MARKER_CAN_ONLY_BE_USED_AS_ANNOTATION_OR_ARGUMENT_IN_OPT_IN!>Marker0<!>) {}
@@ -83,7 +85,17 @@ fun f11(m: <!OPT_IN_MARKER_CAN_ONLY_BE_USED_AS_ANNOTATION_OR_ARGUMENT_IN_OPT_IN!
// Usages of markers in import statements should be OK, but not as qualifiers to import their nested classes
import test.A.Marker
import test.A.Marker.NestedClass
import test.A.Marker.Companion
<!OPT_IN_MARKER_CAN_ONLY_BE_USED_AS_ANNOTATION_OR_ARGUMENT_IN_OPT_IN!>import test.A.Marker.NestedClass<!>
<!OPT_IN_MARKER_CAN_ONLY_BE_USED_AS_ANNOTATION_OR_ARGUMENT_IN_OPT_IN!>import test.A.Marker.NestedClass.NestedClass2<!>
<!OPT_IN_MARKER_CAN_ONLY_BE_USED_AS_ANNOTATION_OR_ARGUMENT_IN_OPT_IN!>import test.A.Marker.Companion<!>
<!OPT_IN_MARKER_CAN_ONLY_BE_USED_AS_ANNOTATION_OR_ARGUMENT_IN_OPT_IN!>import test.A.Marker.Companion.value<!>
// FILE: usage-from-other-file-2.kt
import test.Marker0
<!OPT_IN_MARKER_CAN_ONLY_BE_USED_AS_ANNOTATION_OR_ARGUMENT_IN_OPT_IN!>import test.Marker0.NestedClass<!>
// FILE: usage-from-other-file-3.kt
import test.Marker1
<!OPT_IN_MARKER_CAN_ONLY_BE_USED_AS_ANNOTATION_OR_ARGUMENT_IN_OPT_IN!>import test.Marker1.NestedClass<!>
fun f12(m: <!OPT_IN_MARKER_CAN_ONLY_BE_USED_AS_ANNOTATION_OR_ARGUMENT_IN_OPT_IN!>test.A.Marker.NestedClass.NestedClass2<!>) {}
@@ -66,6 +66,8 @@ fun f8(): test.A.<!OPT_IN_MARKER_CAN_ONLY_BE_USED_AS_ANNOTATION_OR_ARGUMENT_IN_O
typealias Marker0 = A.<!OPT_IN_MARKER_CAN_ONLY_BE_USED_AS_ANNOTATION_OR_ARGUMENT_IN_OPT_IN!>Marker<!>
typealias Marker1 = <!OPT_IN_MARKER_CAN_ONLY_BE_USED_AS_ANNOTATION_OR_ARGUMENT_IN_OPT_IN!>Marker0<!>
fun f9(m: <!OPT_IN_MARKER_CAN_ONLY_BE_USED_AS_ANNOTATION_OR_ARGUMENT_IN_OPT_IN!>Marker0<!>) {}
@@ -84,6 +86,16 @@ fun f11(m: A.<!OPT_IN_MARKER_CAN_ONLY_BE_USED_AS_ANNOTATION_OR_ARGUMENT_IN_OPT_I
import test.A.Marker
import test.A.<!OPT_IN_MARKER_CAN_ONLY_BE_USED_AS_ANNOTATION_OR_ARGUMENT_IN_OPT_IN!>Marker<!>.NestedClass
import test.A.<!OPT_IN_MARKER_CAN_ONLY_BE_USED_AS_ANNOTATION_OR_ARGUMENT_IN_OPT_IN!>Marker<!>.NestedClass.NestedClass2
import test.A.<!OPT_IN_MARKER_CAN_ONLY_BE_USED_AS_ANNOTATION_OR_ARGUMENT_IN_OPT_IN!>Marker<!>.Companion
import test.A.<!OPT_IN_MARKER_CAN_ONLY_BE_USED_AS_ANNOTATION_OR_ARGUMENT_IN_OPT_IN!>Marker<!>.Companion.value
// FILE: usage-from-other-file-2.kt
import test.Marker0
import test.<!OPT_IN_MARKER_CAN_ONLY_BE_USED_AS_ANNOTATION_OR_ARGUMENT_IN_OPT_IN!>Marker0<!>.NestedClass
// FILE: usage-from-other-file-3.kt
import test.Marker1
import test.<!OPT_IN_MARKER_CAN_ONLY_BE_USED_AS_ANNOTATION_OR_ARGUMENT_IN_OPT_IN!>Marker1<!>.NestedClass
fun f12(m: test.A.<!OPT_IN_MARKER_CAN_ONLY_BE_USED_AS_ANNOTATION_OR_ARGUMENT_IN_OPT_IN!>Marker<!>.NestedClass.NestedClass2) {}
@@ -68,6 +68,7 @@ package test {
}
public typealias Experimental0 = kotlin.RequiresOptIn
public typealias Marker0 = test.A.Marker
public typealias Marker1 = test.Marker0
public typealias OptIn0 = kotlin.OptIn
}