diff --git a/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/DiagnosisCompilerTestFE10TestdataTestGenerated.java b/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/DiagnosisCompilerTestFE10TestdataTestGenerated.java index 89d95a614cc..b5b54e548f8 100644 --- a/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/DiagnosisCompilerTestFE10TestdataTestGenerated.java +++ b/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/DiagnosisCompilerTestFE10TestdataTestGenerated.java @@ -37548,6 +37548,12 @@ public class DiagnosisCompilerTestFE10TestdataTestGenerated extends AbstractDiag runTest("compiler/testData/diagnostics/testsWithStdLib/experimental/overrideInAnonymousObject.kt"); } + @Test + @TestMetadata("subClassOptInRequired.kt") + public void testSubClassOptInRequired() throws Exception { + runTest("compiler/testData/diagnostics/testsWithStdLib/experimental/subClassOptInRequired.kt"); + } + @Test @TestMetadata("topLevel.kt") public void testTopLevel() throws Exception { diff --git a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsTestGenerated.java b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsTestGenerated.java index f15c93bdf3c..afd4bb2fafe 100644 --- a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsTestGenerated.java +++ b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsTestGenerated.java @@ -37548,6 +37548,12 @@ public class FirOldFrontendDiagnosticsTestGenerated extends AbstractFirDiagnosti runTest("compiler/testData/diagnostics/testsWithStdLib/experimental/overrideInAnonymousObject.kt"); } + @Test + @TestMetadata("subClassOptInRequired.kt") + public void testSubClassOptInRequired() throws Exception { + runTest("compiler/testData/diagnostics/testsWithStdLib/experimental/subClassOptInRequired.kt"); + } + @Test @TestMetadata("topLevel.kt") public void testTopLevel() throws Exception { diff --git a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsWithLightTreeTestGenerated.java b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsWithLightTreeTestGenerated.java index b5df4f232bf..610dced8629 100644 --- a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsWithLightTreeTestGenerated.java +++ b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsWithLightTreeTestGenerated.java @@ -37548,6 +37548,12 @@ public class FirOldFrontendDiagnosticsWithLightTreeTestGenerated extends Abstrac runTest("compiler/testData/diagnostics/testsWithStdLib/experimental/overrideInAnonymousObject.kt"); } + @Test + @TestMetadata("subClassOptInRequired.kt") + public void testSubClassOptInRequired() throws Exception { + runTest("compiler/testData/diagnostics/testsWithStdLib/experimental/subClassOptInRequired.kt"); + } + @Test @TestMetadata("topLevel.kt") public void testTopLevel() throws Exception { diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/OptInUsageChecker.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/OptInUsageChecker.kt index 634e9efcb0d..1fc8a0d5f61 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/OptInUsageChecker.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/OptInUsageChecker.kt @@ -22,6 +22,7 @@ import org.jetbrains.kotlin.config.AnalysisFlags import org.jetbrains.kotlin.config.LanguageFeature import org.jetbrains.kotlin.config.LanguageVersionSettings import org.jetbrains.kotlin.descriptors.* +import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor import org.jetbrains.kotlin.diagnostics.DiagnosticFactory2 import org.jetbrains.kotlin.diagnostics.Errors import org.jetbrains.kotlin.diagnostics.reportDiagnosticOnce @@ -41,6 +42,7 @@ import org.jetbrains.kotlin.resolve.checkers.OptInNames.OPT_IN_FQ_NAME import org.jetbrains.kotlin.resolve.checkers.OptInNames.OPT_IN_FQ_NAMES import org.jetbrains.kotlin.resolve.checkers.OptInNames.REQUIRES_OPT_IN_FQ_NAME import org.jetbrains.kotlin.resolve.checkers.OptInNames.REQUIRES_OPT_IN_FQ_NAMES +import org.jetbrains.kotlin.resolve.checkers.OptInNames.SUBCLASS_OPT_IN_REQUIRED_FQ_NAME import org.jetbrains.kotlin.resolve.checkers.OptInNames.USE_EXPERIMENTAL_ANNOTATION_CLASS import org.jetbrains.kotlin.resolve.checkers.OptInNames.WAS_EXPERIMENTAL_FQ_NAME import org.jetbrains.kotlin.resolve.constants.ArrayValue @@ -84,7 +86,9 @@ class OptInUsageChecker(project: Project) : CallChecker { override fun check(resolvedCall: ResolvedCall<*>, reportOn: PsiElement, context: CallCheckerContext) { val resultingDescriptor = resolvedCall.resultingDescriptor - val optIns = resultingDescriptor.loadOptIns(moduleAnnotationsResolver, context.languageVersionSettings) + val bindingContext = context.trace.bindingContext + val languageVersionSettings = context.languageVersionSettings + val optIns = resultingDescriptor.loadOptIns(moduleAnnotationsResolver, bindingContext, languageVersionSettings) if (resultingDescriptor is FunctionDescriptor && resultingDescriptor.kind == CallableMemberDescriptor.Kind.SYNTHESIZED ) { @@ -92,7 +96,7 @@ class OptInUsageChecker(project: Project) : CallChecker { if (propertyDescriptor != null) { reportNotAllowedOptIns( optIns + propertyDescriptor.loadOptIns( - moduleAnnotationsResolver, context.languageVersionSettings + moduleAnnotationsResolver, bindingContext, languageVersionSettings ), reportOn, context ) return @@ -101,9 +105,9 @@ class OptInUsageChecker(project: Project) : CallChecker { if (resultingDescriptor is SamConstructorDescriptor) { // KT-47708 special case (warning only) val methodDescriptor = resultingDescriptor.getSingleAbstractMethod() - val samOptIns = methodDescriptor.loadOptIns(moduleAnnotationsResolver, context.languageVersionSettings) + val samOptIns = methodDescriptor.loadOptIns(moduleAnnotationsResolver, bindingContext, languageVersionSettings) .map { (fqName, _, message) -> - OptInDescription(fqName, OptInDescription.Severity.WARNING, message) + OptInDescription(fqName, OptInDescription.Severity.WARNING, message, subclassesOnly = false) } reportNotAllowedOptIns(samOptIns, reportOn, context) } @@ -162,8 +166,8 @@ class OptInUsageChecker(project: Project) : CallChecker { trace: BindingTrace, diagnostics: OptInReporterMultiplexer ) { - for ((annotationFqName, severity, message) in descriptions) { - if (!element.isOptInAllowed(annotationFqName, languageVersionSettings, trace.bindingContext)) { + for ((annotationFqName, severity, message, subclassesOnly) in descriptions) { + if (!element.isOptInAllowed(annotationFqName, languageVersionSettings, trace.bindingContext, subclassesOnly)) { val diagnostic = when (severity) { OptInDescription.Severity.WARNING -> diagnostics.warning OptInDescription.Severity.ERROR -> diagnostics.error @@ -176,10 +180,12 @@ class OptInUsageChecker(project: Project) : CallChecker { fun DeclarationDescriptor.loadOptIns( moduleAnnotationsResolver: ModuleAnnotationsResolver, + context: BindingContext, languageVersionSettings: LanguageVersionSettings, visited: MutableSet = mutableSetOf(), useFutureError: Boolean = false, useMarkersFromContainer: Boolean = true, + fromSupertype: Boolean = false, ): Set { if (!visited.add(this)) return emptySet() val result = SmartSet.create() @@ -188,6 +194,7 @@ class OptInUsageChecker(project: Project) : CallChecker { result.addAll( overridden.loadOptIns( moduleAnnotationsResolver, + context, languageVersionSettings, visited, useFutureError = !languageVersionSettings.supportsFeature(LanguageFeature.OptInContagiousSignatures), @@ -197,7 +204,7 @@ class OptInUsageChecker(project: Project) : CallChecker { if (useMarkersFromContainer) { (containingDeclaration as? ClassDescriptor)?.let { result.addAll( - it.loadOptIns(moduleAnnotationsResolver, languageVersionSettings, visited, useFutureError) + it.loadOptIns(moduleAnnotationsResolver, context, languageVersionSettings, visited, useFutureError) ) } } @@ -206,23 +213,26 @@ class OptInUsageChecker(project: Project) : CallChecker { } for (annotation in annotations) { - result.addIfNotNull(annotation.annotationClass?.loadOptInForMarkerAnnotation(useFutureError)) + result.addIfNotNull( + annotation.annotationClass?.loadOptInForMarkerAnnotation(useFutureError) + ?: if (fromSupertype) annotation.loadSubclassOptInRequired(context) else null + ) } if (this is CallableDescriptor && this !is ClassConstructorDescriptor) { result.addAll( - returnType.loadOptIns(moduleAnnotationsResolver, languageVersionSettings, visited) + returnType.loadOptIns(moduleAnnotationsResolver, context, languageVersionSettings, visited) ) result.addAll( extensionReceiverParameter?.type.loadOptIns( - moduleAnnotationsResolver, languageVersionSettings, visited + moduleAnnotationsResolver, context, languageVersionSettings, visited ) ) if (this is FunctionDescriptor) { valueParameters.forEach { result.addAll( it.type.loadOptIns( - moduleAnnotationsResolver, languageVersionSettings, visited + moduleAnnotationsResolver, context, languageVersionSettings, visited ) ) } @@ -230,7 +240,7 @@ class OptInUsageChecker(project: Project) : CallChecker { } if (this is TypeAliasDescriptor) { - result.addAll(expandedType.loadOptIns(moduleAnnotationsResolver, languageVersionSettings, visited)) + result.addAll(expandedType.loadOptIns(moduleAnnotationsResolver, context, languageVersionSettings, visited)) } if (annotations.any { it.fqName == WAS_EXPERIMENTAL_FQ_NAME }) { @@ -242,7 +252,7 @@ class OptInUsageChecker(project: Project) : CallChecker { val container = containingDeclaration if (useMarkersFromContainer && container is ClassDescriptor && this !is ConstructorDescriptor) { - result.addAll(container.loadOptIns(moduleAnnotationsResolver, languageVersionSettings, visited, useFutureError)) + result.addAll(container.loadOptIns(moduleAnnotationsResolver, context, languageVersionSettings, visited, useFutureError)) } return result @@ -250,27 +260,31 @@ class OptInUsageChecker(project: Project) : CallChecker { private fun KotlinType?.loadOptIns( moduleAnnotationsResolver: ModuleAnnotationsResolver, + context: BindingContext, languageVersionSettings: LanguageVersionSettings, visitedClassifiers: MutableSet ): Set = when { this?.isError != false -> emptySet() this is AbbreviatedType -> abbreviation.constructor.declarationDescriptor?.loadOptIns( - moduleAnnotationsResolver, languageVersionSettings, visitedClassifiers, + moduleAnnotationsResolver, context, languageVersionSettings, visitedClassifiers, useFutureError = !languageVersionSettings.supportsFeature(LanguageFeature.OptInContagiousSignatures) ).orEmpty() + expandedType.loadOptIns( - moduleAnnotationsResolver, languageVersionSettings, visitedClassifiers + moduleAnnotationsResolver, context, languageVersionSettings, visitedClassifiers ) else -> constructor.declarationDescriptor?.loadOptIns( - moduleAnnotationsResolver, languageVersionSettings, visitedClassifiers, + moduleAnnotationsResolver, context, languageVersionSettings, visitedClassifiers, useFutureError = !languageVersionSettings.supportsFeature(LanguageFeature.OptInContagiousSignatures) ).orEmpty() + arguments.flatMap { if (it.isStarProjection) emptySet() - else it.type.loadOptIns(moduleAnnotationsResolver, languageVersionSettings, visitedClassifiers) + else it.type.loadOptIns(moduleAnnotationsResolver, context, languageVersionSettings, visitedClassifiers) } } - internal fun ClassDescriptor.loadOptInForMarkerAnnotation(useFutureError: Boolean = false): OptInDescription? { + internal fun ClassDescriptor.loadOptInForMarkerAnnotation( + useFutureError: Boolean = false, + subclassesOnly: Boolean = false + ): OptInDescription? { val optInAnnotationDescriptor = annotations.findAnnotation(REQUIRES_OPT_IN_FQ_NAME) ?: annotations.findAnnotation(OLD_EXPERIMENTAL_FQ_NAME) @@ -289,11 +303,21 @@ class OptInUsageChecker(project: Project) : CallChecker { val message = (arguments[MESSAGE] as? StringValue)?.value - return OptInDescription(fqNameSafe, severity, message) + return OptInDescription(fqNameSafe, severity, message, subclassesOnly) } - private fun PsiElement.isOptInAllowed(annotationFqName: FqName, context: CheckerContext): Boolean = - isOptInAllowed(annotationFqName, context.languageVersionSettings, context.trace.bindingContext) + private fun AnnotationDescriptor.loadSubclassOptInRequired(context: BindingContext?): OptInDescription? { + if (this.fqName != SUBCLASS_OPT_IN_REQUIRED_FQ_NAME) return null + val markerClass = allValueArguments[USE_EXPERIMENTAL_ANNOTATION_CLASS] + if (markerClass !is KClassValue) return null + val value = markerClass.value + if (value !is KClassValue.Value.NormalClass) return null + val markerDescriptor = context?.get(BindingContext.FQNAME_TO_CLASS_DESCRIPTOR, value.classId.asSingleFqName().toUnsafe()) + return markerDescriptor?.loadOptInForMarkerAnnotation(subclassesOnly = true) + } + + private fun PsiElement.isOptInAllowed(annotationFqName: FqName, context: CheckerContext, subclassesOnly: Boolean): Boolean = + isOptInAllowed(annotationFqName, context.languageVersionSettings, context.trace.bindingContext, subclassesOnly) /** * Checks whether there's an element lexically above in the tree, annotated with `@UseExperimental(X::class)`, or a declaration @@ -302,13 +326,17 @@ class OptInUsageChecker(project: Project) : CallChecker { fun PsiElement.isOptInAllowed( annotationFqName: FqName, languageVersionSettings: LanguageVersionSettings, - bindingContext: BindingContext - ): Boolean = - annotationFqName.asString() in languageVersionSettings.getFlag(AnalysisFlags.optIn) || - anyParentMatches { element -> - element.isDeclarationAnnotatedWith(annotationFqName, bindingContext) || - element.isElementAnnotatedWithOptIn(annotationFqName, bindingContext) - } + bindingContext: BindingContext, + subclassesOnly: Boolean + ): Boolean { + if (annotationFqName.asString() in languageVersionSettings.getFlag(AnalysisFlags.optIn)) return true + val isSubclass = subclassesOnly && getParentOfType(strict = true) != null + return anyParentMatches { element -> + element.isDeclarationAnnotatedWith(annotationFqName, bindingContext) || + element.isElementAnnotatedWithOptIn(annotationFqName, bindingContext) || + isSubclass && element.isElementAnnotatedWithSubclassOptInRequired(annotationFqName, bindingContext) + } + } private fun PsiElement.isDeclarationAnnotatedWith(annotationFqName: FqName, bindingContext: BindingContext): Boolean { if (this !is KtDeclaration) return false @@ -332,6 +360,22 @@ class OptInUsageChecker(project: Project) : CallChecker { } } + private fun PsiElement.isElementAnnotatedWithSubclassOptInRequired( + annotationFqName: FqName, + bindingContext: BindingContext + ): Boolean { + return this is KtAnnotated && annotationEntries.any { entry -> + val descriptor = bindingContext.get(BindingContext.ANNOTATION, entry) + if (descriptor != null && descriptor.fqName == SUBCLASS_OPT_IN_REQUIRED_FQ_NAME) { + val annotationClass = descriptor.allValueArguments[USE_EXPERIMENTAL_ANNOTATION_CLASS] + annotationClass is KClassValue && annotationClass.value.let { value -> + value is KClassValue.Value.NormalClass && + value.classId.asSingleFqName() == annotationFqName && value.arrayDimensions == 0 + } + } else false + } + } + private inline fun PsiElement.anyParentMatches(predicate: (element: PsiElement) -> Boolean): Boolean { var element = this while (true) { @@ -406,9 +450,10 @@ class OptInUsageChecker(project: Project) : CallChecker { is TypeAliasDescriptor -> targetDescriptor.classDescriptor else -> null } + val bindingContext = context.trace.bindingContext if (targetClass != null && targetClass.loadOptInForMarkerAnnotation() != null) { if (!element.isUsageAsAnnotationOrImport() && - !element.isUsageAsOptInArgument(context.trace.bindingContext) + !element.isUsageAsOptInArgument(bindingContext) ) { context.trace.report( Errors.OPT_IN_MARKER_CAN_ONLY_BE_USED_AS_ANNOTATION_OR_ARGUMENT_IN_OPT_IN.on(element) @@ -417,7 +462,12 @@ class OptInUsageChecker(project: Project) : CallChecker { } if (element.getParentOfType(false) == null) { - val descriptions = targetDescriptor.loadOptIns(moduleAnnotationsResolver, context.languageVersionSettings) + val descriptions = targetDescriptor.loadOptIns( + moduleAnnotationsResolver, + bindingContext, + context.languageVersionSettings, + fromSupertype = element.getParentOfType(strict = true) != null + ) reportNotAllowedOptIns(descriptions, element, context) } } @@ -456,7 +506,8 @@ class OptInUsageChecker(project: Project) : CallChecker { parent.parent.parent is KtValueArgumentList && parent.parent.parent.parent.let { entry -> entry is KtAnnotationEntry && bindingContext.get(BindingContext.ANNOTATION, entry)?.let { annotation -> - annotation.fqName in OPT_IN_FQ_NAMES || annotation.fqName == WAS_EXPERIMENTAL_FQ_NAME + annotation.fqName in OPT_IN_FQ_NAMES || annotation.fqName == WAS_EXPERIMENTAL_FQ_NAME || + annotation.fqName == SUBCLASS_OPT_IN_REQUIRED_FQ_NAME } == true } } @@ -469,12 +520,12 @@ class OptInUsageChecker(project: Project) : CallChecker { if (descriptor !is CallableMemberDescriptor) return val optInOverriddenDescriptorMap = descriptor.overriddenDescriptors.flatMap { overriddenMember -> - overriddenMember.loadOptIns(moduleAnnotationsResolver, context.languageVersionSettings) + overriddenMember.loadOptIns(moduleAnnotationsResolver, context.trace.bindingContext, context.languageVersionSettings) .map { description -> description to overriddenMember } }.toMap() for ((description, overriddenMember) in optInOverriddenDescriptorMap) { - if (!declaration.isOptInAllowed(description.annotationFqName, context)) { + if (!declaration.isOptInAllowed(description.annotationFqName, context, description.subclassesOnly)) { val reportOn = (declaration as? KtNamedDeclaration)?.nameIdentifier ?: declaration val (diagnostic, defaultMessageVerb) = when (description.severity) { diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/constants/evaluate/ConstantExpressionEvaluator.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/constants/evaluate/ConstantExpressionEvaluator.kt index 3512fc040e6..31ac3ff50f6 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/constants/evaluate/ConstantExpressionEvaluator.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/constants/evaluate/ConstantExpressionEvaluator.kt @@ -324,7 +324,7 @@ class ConstantExpressionEvaluator( with(OptInUsageChecker) { val descriptor = constantType.constructor.declarationDescriptor ?: return - val optInDescriptions = descriptor.loadOptIns(moduleAnnotationsResolver, languageVersionSettings) + val optInDescriptions = descriptor.loadOptIns(moduleAnnotationsResolver, trace.bindingContext, languageVersionSettings) reportNotAllowedOptIns( optInDescriptions, expression, languageVersionSettings, trace, EXPERIMENTAL_UNSIGNED_LITERALS_DIAGNOSTICS diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/deprecation/DeprecationResolver.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/deprecation/DeprecationResolver.kt index 0f892808bd3..c1381e756cc 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/deprecation/DeprecationResolver.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/deprecation/DeprecationResolver.kt @@ -117,7 +117,9 @@ class DeprecationResolver( return if (callElement != null && bindingContext != null) { with(OptInUsageChecker) { sinceKotlinAccessibility.markerClasses.any { classDescriptor -> - !callElement.isOptInAllowed(classDescriptor.fqNameSafe, languageVersionSettings, bindingContext) + !callElement.isOptInAllowed( + classDescriptor.fqNameSafe, languageVersionSettings, bindingContext, subclassesOnly = false + ) } } } else { diff --git a/compiler/testData/diagnostics/testsWithStdLib/experimental/subClassOptInRequired.fir.kt b/compiler/testData/diagnostics/testsWithStdLib/experimental/subClassOptInRequired.fir.kt new file mode 100644 index 00000000000..507d0664f39 --- /dev/null +++ b/compiler/testData/diagnostics/testsWithStdLib/experimental/subClassOptInRequired.fir.kt @@ -0,0 +1,35 @@ +@RequiresOptIn +annotation class Marker + +@SubclassOptInRequired(Marker::class) +open class Base + +@Marker +class DerivedFirst : Base() + +@OptIn(Marker::class) +class DerivedSecond : Base() + +@SubclassOptInRequired(Marker::class) +open class DerivedThird : Base() + +open class DerivedFourth : Base() + +class GrandDerivedThird : DerivedThird() + +// Question: should we have an error also here? +class GrandDerivedFourth : DerivedFourth() + +@Marker +open class Marked + +@SubclassOptInRequired(Marker::class) +class DerivedMarked : Marked() + +fun test() { + val b = Base() + val d1 = DerivedFirst() + val d2 = DerivedSecond() + val d3 = DerivedThird() + val d4 = DerivedFourth() +} diff --git a/compiler/testData/diagnostics/testsWithStdLib/experimental/subClassOptInRequired.kt b/compiler/testData/diagnostics/testsWithStdLib/experimental/subClassOptInRequired.kt new file mode 100644 index 00000000000..50856ce85e6 --- /dev/null +++ b/compiler/testData/diagnostics/testsWithStdLib/experimental/subClassOptInRequired.kt @@ -0,0 +1,35 @@ +@RequiresOptIn +annotation class Marker + +@SubclassOptInRequired(Marker::class) +open class Base + +@Marker +class DerivedFirst : Base() + +@OptIn(Marker::class) +class DerivedSecond : Base() + +@SubclassOptInRequired(Marker::class) +open class DerivedThird : Base() + +open class DerivedFourth : Base() + +class GrandDerivedThird : DerivedThird() + +// Question: should we have an error also here? +class GrandDerivedFourth : DerivedFourth() + +@Marker +open class Marked + +@SubclassOptInRequired(Marker::class) +class DerivedMarked : Marked() + +fun test() { + val b = Base() + val d1 = DerivedFirst() + val d2 = DerivedSecond() + val d3 = DerivedThird() + val d4 = DerivedFourth() +} diff --git a/compiler/testData/diagnostics/testsWithStdLib/experimental/subClassOptInRequired.txt b/compiler/testData/diagnostics/testsWithStdLib/experimental/subClassOptInRequired.txt new file mode 100644 index 00000000000..9230f125b4e --- /dev/null +++ b/compiler/testData/diagnostics/testsWithStdLib/experimental/subClassOptInRequired.txt @@ -0,0 +1,73 @@ +package + +public fun test(): kotlin.Unit + +@kotlin.SubclassOptInRequired(markerClass = Marker::class) public open class Base { + public constructor Base() + 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 +} + +@Marker public final class DerivedFirst : Base { + public constructor DerivedFirst() + 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 open class DerivedFourth : Base { + public constructor DerivedFourth() + 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.SubclassOptInRequired(markerClass = Marker::class) public final class DerivedMarked : Marked { + public constructor DerivedMarked() + 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.OptIn(markerClass = {Marker::class}) public final class DerivedSecond : Base { + public constructor DerivedSecond() + 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.SubclassOptInRequired(markerClass = Marker::class) public open class DerivedThird : Base { + public constructor DerivedThird() + 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 GrandDerivedFourth : DerivedFourth { + public constructor GrandDerivedFourth() + 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 GrandDerivedThird : DerivedThird { + public constructor GrandDerivedThird() + 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 +} + +@Marker public open class Marked { + public constructor Marked() + 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 +} diff --git a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/DiagnosticTestGenerated.java b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/DiagnosticTestGenerated.java index 18e4f007241..5051ca57a74 100644 --- a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/DiagnosticTestGenerated.java +++ b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/DiagnosticTestGenerated.java @@ -37638,6 +37638,12 @@ public class DiagnosticTestGenerated extends AbstractDiagnosticTest { runTest("compiler/testData/diagnostics/testsWithStdLib/experimental/overrideInAnonymousObject.kt"); } + @Test + @TestMetadata("subClassOptInRequired.kt") + public void testSubClassOptInRequired() throws Exception { + runTest("compiler/testData/diagnostics/testsWithStdLib/experimental/subClassOptInRequired.kt"); + } + @Test @TestMetadata("topLevel.kt") public void testTopLevel() throws Exception { diff --git a/core/compiler.common/src/org/jetbrains/kotlin/resolve/checkers/OptInDescription.kt b/core/compiler.common/src/org/jetbrains/kotlin/resolve/checkers/OptInDescription.kt index a6f40567c9f..6b50527ff3c 100644 --- a/core/compiler.common/src/org/jetbrains/kotlin/resolve/checkers/OptInDescription.kt +++ b/core/compiler.common/src/org/jetbrains/kotlin/resolve/checkers/OptInDescription.kt @@ -8,7 +8,12 @@ package org.jetbrains.kotlin.resolve.checkers import org.jetbrains.kotlin.descriptors.annotations.KotlinTarget import org.jetbrains.kotlin.name.FqName -data class OptInDescription(val annotationFqName: FqName, val severity: Severity, val message: String?) { +data class OptInDescription( + val annotationFqName: FqName, + val severity: Severity, + val message: String?, + val subclassesOnly: Boolean, +) { enum class Severity { WARNING, ERROR, FUTURE_ERROR } companion object {