diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/CommonDeclarationCheckers.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/CommonDeclarationCheckers.kt index 1808d04707e..89bc0efa7bb 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/CommonDeclarationCheckers.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/CommonDeclarationCheckers.kt @@ -151,6 +151,7 @@ object CommonDeclarationCheckers : DeclarationCheckers() { override val fileCheckers: Set get() = setOf( FirImportsChecker, + FirOptInImportsChecker, FirUnresolvedInMiddleOfImportChecker, FirTopLevelPropertiesChecker, ) diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirOptInImportsChecker.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirOptInImportsChecker.kt new file mode 100644 index 00000000000..82f1cb5258f --- /dev/null +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirOptInImportsChecker.kt @@ -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) + } + +} 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 035118144fb..a1cc8b7736d 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 @@ -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, diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/type/FirOptInUsageTypeRefChecker.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/type/FirOptInUsageTypeRefChecker.kt index 1d75fda3f72..5788274d24a 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/type/FirOptInUsageTypeRefChecker.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/type/FirOptInUsageTypeRefChecker.kt @@ -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() ?: 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, diff --git a/compiler/testData/diagnostics/testsWithStdLib/experimental/usageNotAsAnnotation.fir.kt b/compiler/testData/diagnostics/testsWithStdLib/experimental/usageNotAsAnnotation.fir.kt index 25b07ab2545..4d23a781b58 100644 --- a/compiler/testData/diagnostics/testsWithStdLib/experimental/usageNotAsAnnotation.fir.kt +++ b/compiler/testData/diagnostics/testsWithStdLib/experimental/usageNotAsAnnotation.fir.kt @@ -66,6 +66,8 @@ fun f8(): typealias Marker0 = A.Marker +typealias Marker1 = Marker0 + fun f9(m: Marker0) {} @@ -83,7 +85,17 @@ fun f11(m: import test.A.Marker.NestedClass +import test.A.Marker.NestedClass.NestedClass2 +import test.A.Marker.Companion +import test.A.Marker.Companion.value + +// FILE: usage-from-other-file-2.kt +import test.Marker0 +import test.Marker0.NestedClass + +// FILE: usage-from-other-file-3.kt +import test.Marker1 +import test.Marker1.NestedClass fun f12(m: test.A.Marker.NestedClass.NestedClass2) {} diff --git a/compiler/testData/diagnostics/testsWithStdLib/experimental/usageNotAsAnnotation.kt b/compiler/testData/diagnostics/testsWithStdLib/experimental/usageNotAsAnnotation.kt index eb73cf2891d..33f9fd44003 100644 --- a/compiler/testData/diagnostics/testsWithStdLib/experimental/usageNotAsAnnotation.kt +++ b/compiler/testData/diagnostics/testsWithStdLib/experimental/usageNotAsAnnotation.kt @@ -66,6 +66,8 @@ fun f8(): test.A.Marker +typealias Marker1 = Marker0 + fun f9(m: Marker0) {} @@ -84,6 +86,16 @@ fun f11(m: A.Marker.NestedClass +import test.A.Marker.NestedClass.NestedClass2 import test.A.Marker.Companion +import test.A.Marker.Companion.value + +// FILE: usage-from-other-file-2.kt +import test.Marker0 +import test.Marker0.NestedClass + +// FILE: usage-from-other-file-3.kt +import test.Marker1 +import test.Marker1.NestedClass fun f12(m: test.A.Marker.NestedClass.NestedClass2) {} diff --git a/compiler/testData/diagnostics/testsWithStdLib/experimental/usageNotAsAnnotation.txt b/compiler/testData/diagnostics/testsWithStdLib/experimental/usageNotAsAnnotation.txt index 4dbc699e1a7..e9f36c3cb54 100644 --- a/compiler/testData/diagnostics/testsWithStdLib/experimental/usageNotAsAnnotation.txt +++ b/compiler/testData/diagnostics/testsWithStdLib/experimental/usageNotAsAnnotation.txt @@ -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 }