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 6a24fe1cf0e..1d75fda3f72 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 @@ -6,6 +6,7 @@ package org.jetbrains.kotlin.fir.analysis.checkers.type import org.jetbrains.kotlin.KtRealSourceElementKind +import org.jetbrains.kotlin.KtSourceElement import org.jetbrains.kotlin.diagnostics.DiagnosticReporter import org.jetbrains.kotlin.diagnostics.reportOn import org.jetbrains.kotlin.fir.FirSession @@ -18,18 +19,17 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.OPT_IN_MARKER_CAN 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.ConeClassLikeType -import org.jetbrains.kotlin.fir.types.FirTypeRef -import org.jetbrains.kotlin.fir.types.coneTypeSafe +import org.jetbrains.kotlin.fir.types.* import org.jetbrains.kotlin.resolve.checkers.OptInNames +@OptIn(SymbolInternals::class) object FirOptInUsageTypeRefChecker : FirTypeRefChecker() { - @OptIn(SymbolInternals::class) override fun check(typeRef: FirTypeRef, context: CheckerContext, reporter: DiagnosticReporter) { val source = typeRef.source if (source?.kind !is KtRealSourceElementKind) return @@ -38,23 +38,34 @@ object FirOptInUsageTypeRefChecker : FirTypeRefChecker() { 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 if (lastAnnotationCall == null || lastAnnotationCall.annotationTypeRef !== typeRef) { - if (classId == OptInNames.REQUIRES_OPT_IN_CLASS_ID || classId == OptInNames.OPT_IN_CLASS_ID) { - reporter.reportOn(source, OPT_IN_CAN_ONLY_BE_USED_AS_ANNOTATION, context) - } else if (processedSymbol is FirRegularClassSymbol && - processedSymbol.fir.getAnnotationByClassId(OptInNames.REQUIRES_OPT_IN_CLASS_ID, context.session) != null - ) { - reporter.reportOn(source, OPT_IN_MARKER_CAN_ONLY_BE_USED_AS_ANNOTATION_OR_ARGUMENT_IN_OPT_IN, context) + when { + classId == OptInNames.REQUIRES_OPT_IN_CLASS_ID || classId == OptInNames.OPT_IN_CLASS_ID -> + 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) + } } } - val isSupertypeRef = typeRef in (context.containingDeclarations.lastOrNull() as? FirClass)?.superTypeRefs.orEmpty() with(FirOptInUsageBaseChecker) { val experimentalities = mutableSetOf() experimentalities.addAll(symbol.loadClassifierExperimentalities(context, isSupertypeRef)) - if (typeAliasExpandedSymbol != null) experimentalities.addAll(typeAliasExpandedSymbol.loadClassifierExperimentalities(context, isSupertypeRef)) + if (typeAliasExpandedSymbol != null) { + experimentalities.addAll( + typeAliasExpandedSymbol.loadClassifierExperimentalities( + context, + isSupertypeRef + ) + ) + } experimentalities.addAll(loadExperimentalitiesFromConeArguments(context, coneType.typeArguments.toList())) reportNotAcceptedExperimentalities(experimentalities, typeRef, context, reporter) } @@ -72,4 +83,20 @@ object FirOptInUsageTypeRefChecker : FirTypeRefChecker() { val coneType = coneTypeSafe() ?: return null 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, + context: CheckerContext, + reporter: DiagnosticReporter, + ) { + val containingClassSymbol = this.getContainingClassLookupTag()?.toSymbol(context.session) ?: return + if (qualifier.any { it.name == containingClassSymbol.name } && containingClassSymbol.isExperimentalMarker(context.session)) { + reporter.reportOn(source, OPT_IN_MARKER_CAN_ONLY_BE_USED_AS_ANNOTATION_OR_ARGUMENT_IN_OPT_IN, context) + } + containingClassSymbol.checkContainingClasses(source, qualifier, context, reporter) + } } diff --git a/compiler/testData/diagnostics/testsWithStdLib/experimental/usageNotAsAnnotation.fir.kt b/compiler/testData/diagnostics/testsWithStdLib/experimental/usageNotAsAnnotation.fir.kt index 03b97f3d347..25b07ab2545 100644 --- a/compiler/testData/diagnostics/testsWithStdLib/experimental/usageNotAsAnnotation.fir.kt +++ b/compiler/testData/diagnostics/testsWithStdLib/experimental/usageNotAsAnnotation.fir.kt @@ -45,34 +45,45 @@ fun f5() {} // Usages of markers as types should be errors -@RequiresOptIn -annotation class Marker { - class NestedClass +object A { + @RequiresOptIn + annotation class Marker { + class NestedClass() { + class NestedClass2 - companion object { - const val value = 42 + fun f12(m: NestedClass2){} + } + + companion object { + const val value = 42 + } } } -fun f6(m: Marker) {} -fun f7(): List<Marker>? = null -fun f8(): test.Marker? = null +fun f6(m: A.Marker) {} +fun f7(): List<A.Marker>? = null +fun f8(): test.A.Marker? = null -typealias Marker0 = Marker +typealias Marker0 = A.Marker fun f9(m: Marker0) {} // Usages of markers as qualifiers are errors as well (we can lift this restriction for select cases) -fun f10(m: Marker.NestedClass) { - Marker.value +fun f10(m: A.Marker.NestedClass) { + A.Marker.value } +fun f11(m: A.Marker.NestedClass.NestedClass2) {} + + // FILE: usage-from-other-file.kt // Usages of markers in import statements should be OK, but not as qualifiers to import their nested classes -import test.Marker -import test.Marker.NestedClass -import test.Marker.Companion +import test.A.Marker +import test.A.Marker.NestedClass +import test.A.Marker.Companion + +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 a17cd457c3f..eb73cf2891d 100644 --- a/compiler/testData/diagnostics/testsWithStdLib/experimental/usageNotAsAnnotation.kt +++ b/compiler/testData/diagnostics/testsWithStdLib/experimental/usageNotAsAnnotation.kt @@ -45,34 +45,45 @@ fun f5() {} // Usages of markers as types should be errors -@RequiresOptIn -annotation class Marker { - class NestedClass +object A { + @RequiresOptIn + annotation class Marker { + class NestedClass() { + class NestedClass2 - companion object { - const val value = 42 + fun f12(m: NestedClass2){} + } + + companion object { + const val value = 42 + } } } -fun f6(m: Marker) {} -fun f7(): List<Marker>? = null -fun f8(): test.Marker? = null +fun f6(m: A.Marker) {} +fun f7(): ListMarker>? = null +fun f8(): test.A.Marker? = null -typealias Marker0 = Marker +typealias Marker0 = A.Marker fun f9(m: Marker0) {} // Usages of markers as qualifiers are errors as well (we can lift this restriction for select cases) -fun f10(m: Marker.NestedClass) { - Marker.value +fun f10(m: A.Marker.NestedClass) { + A.Marker.value } +fun f11(m: A.Marker.NestedClass.NestedClass2) {} + + // FILE: usage-from-other-file.kt // Usages of markers in import statements should be OK, but not as qualifiers to import their nested classes -import test.Marker -import test.Marker.NestedClass -import test.Marker.Companion +import test.A.Marker +import test.A.Marker.NestedClass +import test.A.Marker.Companion + +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 a0aa934274f..4dbc699e1a7 100644 --- a/compiler/testData/diagnostics/testsWithStdLib/experimental/usageNotAsAnnotation.txt +++ b/compiler/testData/diagnostics/testsWithStdLib/experimental/usageNotAsAnnotation.txt @@ -1,16 +1,56 @@ package +public fun f12(/*0*/ m: test.A.Marker.NestedClass.NestedClass2): kotlin.Unit + package test { public fun f1(/*0*/ e: kotlin.RequiresOptIn): kotlin.Unit - public fun f10(/*0*/ m: test.Marker.NestedClass): kotlin.Unit + public fun f10(/*0*/ m: test.A.Marker.NestedClass): kotlin.Unit + public fun f11(/*0*/ m: test.A.Marker.NestedClass.NestedClass2): kotlin.Unit public fun f2(/*0*/ u: kotlin.OptIn?): kotlin.Unit public fun f3(/*0*/ e: test.Experimental0 /* = kotlin.RequiresOptIn */): kotlin.Unit public fun f4(/*0*/ u: test.OptIn0 /* = kotlin.OptIn */): kotlin.Unit @test.VarargKClasses(k = {kotlin.RequiresOptIn::class, kotlin.OptIn::class, kotlin.RequiresOptIn::class, kotlin.OptIn::class}) public fun f5(): kotlin.Unit - public fun f6(/*0*/ m: test.Marker): kotlin.Unit - public fun f7(): kotlin.collections.List? - public fun f8(): test.Marker? - public fun f9(/*0*/ m: test.Marker0 /* = test.Marker */): kotlin.Unit + public fun f6(/*0*/ m: test.A.Marker): kotlin.Unit + public fun f7(): kotlin.collections.List? + public fun f8(): test.A.Marker? + public fun f9(/*0*/ m: test.Marker0 /* = test.A.Marker */): kotlin.Unit + + public object A { + private constructor A() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + + @kotlin.RequiresOptIn public final annotation class Marker : kotlin.Annotation { + public constructor Marker() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + + public companion object Companion { + private constructor Companion() + public const final val value: kotlin.Int = 42 + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + } + + public final class NestedClass { + public constructor NestedClass() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public final fun f12(/*0*/ m: test.A.Marker.NestedClass.NestedClass2): kotlin.Unit + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + + public final class NestedClass2 { + public constructor NestedClass2() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + } + } + } + } @kotlin.RequiresOptIn(level = Level.ERROR) @kotlin.annotation.Retention(value = AnnotationRetention.BINARY) public final annotation class M : kotlin.Annotation { public constructor M() @@ -19,28 +59,6 @@ package test { public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String } - @kotlin.RequiresOptIn public final annotation class Marker : kotlin.Annotation { - public constructor Marker() - public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean - public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int - public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String - - public companion object Companion { - private constructor Companion() - public const final val value: kotlin.Int = 42 - public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean - public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int - public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String - } - - public final class NestedClass { - public constructor NestedClass() - public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean - public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int - public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String - } - } - public final annotation class VarargKClasses : kotlin.Annotation { public constructor VarargKClasses(/*0*/ vararg k: kotlin.reflect.KClass<*> /*kotlin.Array>*/) public final val k: kotlin.Array> @@ -49,6 +67,7 @@ package test { public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String } public typealias Experimental0 = kotlin.RequiresOptIn - public typealias Marker0 = test.Marker + public typealias Marker0 = test.A.Marker public typealias OptIn0 = kotlin.OptIn } +