diff --git a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDataClassConverters.kt b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDataClassConverters.kt index 60603e764a2..1646478b495 100644 --- a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDataClassConverters.kt +++ b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDataClassConverters.kt @@ -1041,6 +1041,13 @@ internal val KT_DIAGNOSTIC_CONVERTER = KtDiagnosticConverterBuilder.buildConvert token, ) } + add(FirErrors.SUBCLASS_OPT_IN_INAPPLICABLE) { firDiagnostic -> + SubclassOptInInapplicableImpl( + firDiagnostic.a, + firDiagnostic as KtPsiDiagnostic, + token, + ) + } add(FirErrors.EXPOSED_TYPEALIAS_EXPANDED_TYPE) { firDiagnostic -> ExposedTypealiasExpandedTypeImpl( firDiagnostic.a, diff --git a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnostics.kt b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnostics.kt index 7195722d2de..66d5096006d 100644 --- a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnostics.kt +++ b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnostics.kt @@ -751,6 +751,11 @@ sealed class KtFirDiagnostic : KtDiagnosticWithPsi { override val diagnosticClass get() = OptInMarkerOnOverrideWarning::class } + abstract class SubclassOptInInapplicable : KtFirDiagnostic() { + override val diagnosticClass get() = SubclassOptInInapplicable::class + abstract val target: String + } + abstract class ExposedTypealiasExpandedType : KtFirDiagnostic() { override val diagnosticClass get() = ExposedTypealiasExpandedType::class abstract val elementVisibility: EffectiveVisibility diff --git a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnosticsImpl.kt b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnosticsImpl.kt index c8dde323e62..a5fc6f1d640 100644 --- a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnosticsImpl.kt +++ b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnosticsImpl.kt @@ -902,6 +902,12 @@ internal class OptInMarkerOnOverrideWarningImpl( override val token: KtLifetimeToken, ) : KtFirDiagnostic.OptInMarkerOnOverrideWarning(), KtAbstractFirDiagnostic +internal class SubclassOptInInapplicableImpl( + override val target: String, + override val firDiagnostic: KtPsiDiagnostic, + override val token: KtLifetimeToken, +) : KtFirDiagnostic.SubclassOptInInapplicable(), KtAbstractFirDiagnostic + internal class ExposedTypealiasExpandedTypeImpl( override val elementVisibility: EffectiveVisibility, override val restrictingDeclaration: KtSymbol, 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 b5b54e548f8..ea550777305 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("subClassOptInModality.kt") + public void testSubClassOptInModality() throws Exception { + runTest("compiler/testData/diagnostics/testsWithStdLib/experimental/subClassOptInModality.kt"); + } + @Test @TestMetadata("subClassOptInRequired.kt") public void testSubClassOptInRequired() 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 afd4bb2fafe..cd8e3b2dbba 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("subClassOptInModality.kt") + public void testSubClassOptInModality() throws Exception { + runTest("compiler/testData/diagnostics/testsWithStdLib/experimental/subClassOptInModality.kt"); + } + @Test @TestMetadata("subClassOptInRequired.kt") public void testSubClassOptInRequired() 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 610dced8629..a1a44fad5ce 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("subClassOptInModality.kt") + public void testSubClassOptInModality() throws Exception { + runTest("compiler/testData/diagnostics/testsWithStdLib/experimental/subClassOptInModality.kt"); + } + @Test @TestMetadata("subClassOptInRequired.kt") public void testSubClassOptInRequired() throws Exception { diff --git a/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirDiagnosticsList.kt b/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirDiagnosticsList.kt index 59bd1a5a281..d1ee88eec3a 100644 --- a/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirDiagnosticsList.kt +++ b/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirDiagnosticsList.kt @@ -15,8 +15,6 @@ import org.jetbrains.kotlin.descriptors.ClassKind import org.jetbrains.kotlin.descriptors.EffectiveVisibility import org.jetbrains.kotlin.descriptors.Visibility import org.jetbrains.kotlin.diagnostics.WhenMissingCase -import org.jetbrains.kotlin.diagnostics.deprecationError2 -import org.jetbrains.kotlin.diagnostics.error0 import org.jetbrains.kotlin.fir.FirModuleData import org.jetbrains.kotlin.fir.PrivateForInline import org.jetbrains.kotlin.fir.checkers.generator.diagnostics.model.* @@ -328,6 +326,10 @@ object DIAGNOSTICS_LIST : DiagnosticList("FirErrors") { } val OPT_IN_MARKER_ON_OVERRIDE by error() val OPT_IN_MARKER_ON_OVERRIDE_WARNING by warning() + + val SUBCLASS_OPT_IN_INAPPLICABLE by error { + parameter("target") + } } val EXPOSED_VISIBILITY by object : DiagnosticGroup("Exposed visibility") { diff --git a/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt b/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt index 8a44041882d..2144bdf56f7 100644 --- a/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt +++ b/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt @@ -271,6 +271,7 @@ object FirErrors { val OPT_IN_MARKER_ON_WRONG_TARGET by error1() val OPT_IN_MARKER_ON_OVERRIDE by error0() val OPT_IN_MARKER_ON_OVERRIDE_WARNING by warning0() + val SUBCLASS_OPT_IN_INAPPLICABLE by error1() // Exposed visibility val EXPOSED_TYPEALIAS_EXPANDED_TYPE by error3, EffectiveVisibility>(SourceElementPositioningStrategies.DECLARATION_NAME) diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/FirOptInAnnotationCallChecker.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/FirOptInAnnotationCallChecker.kt index 61c6048f9ae..f411ee2496b 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/FirOptInAnnotationCallChecker.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/FirOptInAnnotationCallChecker.kt @@ -8,18 +8,28 @@ package org.jetbrains.kotlin.fir.analysis.checkers.expression import org.jetbrains.kotlin.KtSourceElement import org.jetbrains.kotlin.config.AnalysisFlags import org.jetbrains.kotlin.config.LanguageFeature +import org.jetbrains.kotlin.descriptors.ClassKind +import org.jetbrains.kotlin.descriptors.Modality import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext import org.jetbrains.kotlin.fir.analysis.checkers.extractClassesFromArgument import org.jetbrains.kotlin.diagnostics.DiagnosticReporter import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors import org.jetbrains.kotlin.diagnostics.reportOn +import org.jetbrains.kotlin.fir.analysis.checkers.expression.FirOptInUsageBaseChecker.loadExperimentalityForMarkerAnnotation +import org.jetbrains.kotlin.fir.analysis.checkers.extractClassFromArgument +import org.jetbrains.kotlin.fir.analysis.checkers.modality +import org.jetbrains.kotlin.fir.declarations.FirClass import org.jetbrains.kotlin.fir.declarations.findArgumentByName +import org.jetbrains.kotlin.fir.declarations.utils.isFun +import org.jetbrains.kotlin.fir.declarations.utils.isLocal import org.jetbrains.kotlin.fir.expressions.FirAnnotationCall import org.jetbrains.kotlin.fir.expressions.arguments import org.jetbrains.kotlin.fir.languageVersionSettings +import org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol import org.jetbrains.kotlin.fir.types.ConeClassLikeType import org.jetbrains.kotlin.fir.types.coneTypeSafe import org.jetbrains.kotlin.resolve.checkers.OptInNames +import org.jetbrains.kotlin.resolve.checkers.OptInNames.USE_EXPERIMENTAL_ANNOTATION_CLASS object FirOptInAnnotationCallChecker : FirAnnotationCallChecker() { override fun check(expression: FirAnnotationCall, context: CheckerContext, reporter: DiagnosticReporter) { @@ -27,6 +37,7 @@ object FirOptInAnnotationCallChecker : FirAnnotationCallChecker() { val classId = lookupTag.classId val isRequiresOptIn = classId == OptInNames.REQUIRES_OPT_IN_CLASS_ID val isOptIn = classId == OptInNames.OPT_IN_CLASS_ID + val isSubclassOptIn = classId == OptInNames.SUBCLASS_OPT_IN_REQUIRED_CLASS_ID if (isRequiresOptIn || isOptIn) { checkOptInIsEnabled(expression.source, context, reporter) if (isOptIn) { @@ -34,21 +45,36 @@ object FirOptInAnnotationCallChecker : FirAnnotationCallChecker() { if (arguments.isEmpty()) { reporter.reportOn(expression.source, FirErrors.OPT_IN_WITHOUT_ARGUMENTS, context) } else { - val annotationClasses = expression.findArgumentByName(OptInNames.USE_EXPERIMENTAL_ANNOTATION_CLASS) + val annotationClasses = expression.findArgumentByName(USE_EXPERIMENTAL_ANNOTATION_CLASS) for (classSymbol in annotationClasses?.extractClassesFromArgument().orEmpty()) { - with(FirOptInUsageBaseChecker) { - if (classSymbol.loadExperimentalityForMarkerAnnotation() == null) { - reporter.reportOn( - expression.source, - FirErrors.OPT_IN_ARGUMENT_IS_NOT_MARKER, - classSymbol.classId.asSingleFqName(), - context - ) - } - } + checkOptInArgumentIsMarker(classSymbol, expression.source, reporter, context) } } } + } else if (isSubclassOptIn) { + val declaration = context.containingDeclarations.lastOrNull() as? FirClass + if (declaration != null) { + val kind = declaration.classKind + if (kind == ClassKind.ENUM_CLASS || kind == ClassKind.OBJECT || kind == ClassKind.ANNOTATION_CLASS) { + reporter.reportOn(expression.source, FirErrors.SUBCLASS_OPT_IN_INAPPLICABLE, kind.toString(), context) + return + } + val modality = declaration.modality() + if (modality == Modality.FINAL || modality == Modality.SEALED) { + reporter.reportOn(expression.source, FirErrors.SUBCLASS_OPT_IN_INAPPLICABLE, "$modality $kind", context) + return + } + if (declaration.isFun) { + reporter.reportOn(expression.source, FirErrors.SUBCLASS_OPT_IN_INAPPLICABLE, "fun interface", context) + return + } + if (declaration.isLocal) { + reporter.reportOn(expression.source, FirErrors.SUBCLASS_OPT_IN_INAPPLICABLE, "local $kind", context) + return + } + } + val classSymbol = expression.findArgumentByName(USE_EXPERIMENTAL_ANNOTATION_CLASS)?.extractClassFromArgument() ?: return + checkOptInArgumentIsMarker(classSymbol, expression.source, reporter, context) } } @@ -65,4 +91,22 @@ object FirOptInAnnotationCallChecker : FirAnnotationCallChecker() { reporter.reportOn(element, FirErrors.OPT_IN_IS_NOT_ENABLED, context) } } + + private fun checkOptInArgumentIsMarker( + classSymbol: FirRegularClassSymbol, + source: KtSourceElement?, + reporter: DiagnosticReporter, + context: CheckerContext + ) { + with(FirOptInUsageBaseChecker) { + if (classSymbol.loadExperimentalityForMarkerAnnotation() == null) { + reporter.reportOn( + source, + FirErrors.OPT_IN_ARGUMENT_IS_NOT_MARKER, + classSymbol.classId.asSingleFqName(), + context + ) + } + } + } } diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrorsDefaultMessages.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrorsDefaultMessages.kt index 426e9d0c7f6..6b6da0a2124 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrorsDefaultMessages.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrorsDefaultMessages.kt @@ -469,6 +469,7 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.SETTER_VISIBILITY import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.SINGLETON_IN_SUPERTYPE import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.SMARTCAST_IMPOSSIBLE import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.SPREAD_OF_NULLABLE +import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.SUBCLASS_OPT_IN_INAPPLICABLE import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.SUPERCLASS_NOT_ACCESSIBLE_FROM_INTERFACE import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.SUPERTYPES_FOR_ANNOTATION_CLASS import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.SUPERTYPE_APPEARS_TWICE @@ -865,6 +866,8 @@ object FirErrorsDefaultMessages : BaseDiagnosticRendererFactory() { OPT_IN_MARKER_ON_OVERRIDE_WARNING, "Opt-in requirement marker annotation on override makes no sense without the same marker on base declaration" ) + map.put(SUBCLASS_OPT_IN_INAPPLICABLE, "@SubclassOptInRequired is inapplicable on {0}", STRING) + map.put(NOT_A_CLASS, "Not a class") map.put( WRONG_EXTENSION_FUNCTION_TYPE, diff --git a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java index e39f2898cc3..4cf6efc0ff1 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java @@ -11,7 +11,6 @@ import com.intellij.psi.impl.source.tree.LeafPsiElement; import kotlin.Pair; import org.jetbrains.annotations.NotNull; import org.jetbrains.kotlin.config.LanguageFeature; -import org.jetbrains.kotlin.config.LanguageVersion; import org.jetbrains.kotlin.config.LanguageVersionSettings; import org.jetbrains.kotlin.descriptors.*; import org.jetbrains.kotlin.diagnostics.rendering.DeclarationWithDiagnosticComponents; @@ -27,7 +26,6 @@ import org.jetbrains.kotlin.name.Name; import org.jetbrains.kotlin.psi.*; import org.jetbrains.kotlin.resolve.VarianceConflictDiagnosticData; import org.jetbrains.kotlin.resolve.calls.inference.InferenceErrorData; -import org.jetbrains.kotlin.resolve.calls.inference.model.TypeVariableTypeConstructor; import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall; import org.jetbrains.kotlin.resolve.calls.tower.WrongResolutionToClassifier; import org.jetbrains.kotlin.resolve.calls.util.BuilderLambdaLabelingInfo; @@ -43,7 +41,6 @@ import java.util.List; import java.util.Map; import java.util.Set; -import static org.jetbrains.kotlin.config.LanguageFeature.ReportTypeVarianceConflictOnQualifierArguments; import static org.jetbrains.kotlin.diagnostics.ClassicPositioningStrategies.ACTUAL_DECLARATION_NAME; import static org.jetbrains.kotlin.diagnostics.ClassicPositioningStrategies.INCOMPATIBLE_DECLARATION; import static org.jetbrains.kotlin.diagnostics.PositioningStrategies.*; @@ -339,6 +336,8 @@ public interface Errors { DiagnosticFactory0 OPT_IN_MARKER_ON_OVERRIDE = DiagnosticFactory0.create(ERROR); DiagnosticFactory0 OPT_IN_MARKER_ON_OVERRIDE_WARNING = DiagnosticFactory0.create(WARNING); + DiagnosticFactory1 SUBCLASS_OPT_IN_INAPPLICABLE = DiagnosticFactory1.create(ERROR); + DiagnosticFactory1 EXPERIMENTAL_UNSIGNED_LITERALS = DiagnosticFactory1.create(WARNING); DiagnosticFactory1 EXPERIMENTAL_UNSIGNED_LITERALS_ERROR = DiagnosticFactory1.create(ERROR); diff --git a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java index ec975df421d..de7c74e5b9d 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java @@ -180,6 +180,8 @@ public class DefaultErrorMessages { MAP.put(OPT_IN_MARKER_ON_OVERRIDE, "Opt-in requirement marker annotation on override requires the same marker on base declaration"); MAP.put(OPT_IN_MARKER_ON_OVERRIDE_WARNING, "Opt-in requirement marker annotation on override makes no sense without the same marker on base declaration"); + MAP.put(SUBCLASS_OPT_IN_INAPPLICABLE, "@SubclassOptInRequired is inapplicable on {0}", STRING); + MAP.put(EXPERIMENTAL_UNSIGNED_LITERALS, "{0}", STRING); MAP.put(EXPERIMENTAL_UNSIGNED_LITERALS_ERROR, "{0}", STRING); diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/OptInMarkerDeclarationAnnotationChecker.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/OptInMarkerDeclarationAnnotationChecker.kt index bbd29251bfc..6c65c7c5483 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/OptInMarkerDeclarationAnnotationChecker.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/OptInMarkerDeclarationAnnotationChecker.kt @@ -8,14 +8,15 @@ package org.jetbrains.kotlin.resolve.checkers import org.jetbrains.kotlin.builtins.StandardNames import org.jetbrains.kotlin.config.LanguageVersionSettings import org.jetbrains.kotlin.descriptors.ClassDescriptor +import org.jetbrains.kotlin.descriptors.ClassKind +import org.jetbrains.kotlin.descriptors.Modality import org.jetbrains.kotlin.descriptors.ModuleDescriptor import org.jetbrains.kotlin.descriptors.annotations.AnnotationUseSiteTarget import org.jetbrains.kotlin.descriptors.annotations.KotlinRetention import org.jetbrains.kotlin.descriptors.annotations.KotlinTarget import org.jetbrains.kotlin.descriptors.annotations.KotlinTarget.* import org.jetbrains.kotlin.diagnostics.Errors -import org.jetbrains.kotlin.psi.KtAnnotated -import org.jetbrains.kotlin.psi.KtAnnotationEntry +import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.resolve.AdditionalAnnotationChecker import org.jetbrains.kotlin.resolve.AnnotationChecker import org.jetbrains.kotlin.resolve.BindingContext @@ -47,6 +48,11 @@ class OptInMarkerDeclarationAnnotationChecker(private val module: ModuleDescript .safeAs()?.value.orEmpty() checkOptInUsage(annotationClasses, trace, entry) } + OptInNames.SUBCLASS_OPT_IN_REQUIRED_FQ_NAME -> { + val annotationClass = + annotation.allValueArguments[OptInNames.USE_EXPERIMENTAL_ANNOTATION_CLASS] + checkSubclassOptInUsage(annotated, listOfNotNull(annotationClass), trace, entry) + } in OptInNames.REQUIRES_OPT_IN_FQ_NAMES -> { hasOptIn = true } @@ -89,7 +95,53 @@ class OptInMarkerDeclarationAnnotationChecker(private val module: ModuleDescript trace.report(Errors.OPT_IN_WITHOUT_ARGUMENTS.on(entry)) return } + checkArgumentsAreMarkers(annotationClasses, trace, entry) + } + private fun checkSubclassOptInUsage( + annotated: KtAnnotated?, + annotationClasses: List>, + trace: BindingTrace, + entry: KtAnnotationEntry + ) { + when (annotated) { + is KtAnnotatedExpression -> { + if (annotated.baseExpression is KtObjectLiteralExpression) { + trace.report(Errors.SUBCLASS_OPT_IN_INAPPLICABLE.on(entry, "object")) + } + return + } + is KtClassOrObject -> { + val descriptor = trace[BindingContext.CLASS, annotated] + if (descriptor != null) { + val kind = descriptor.kind + if (kind == ClassKind.OBJECT || kind == ClassKind.ENUM_CLASS || kind == ClassKind.ANNOTATION_CLASS) { + trace.report(Errors.SUBCLASS_OPT_IN_INAPPLICABLE.on(entry, kind.toString())) + return + } + if (kind != ClassKind.ENUM_ENTRY) { + // ^ We don't report anything on enum entries because it's anyway inapplicable target + val modality = descriptor.modality + if (modality != Modality.ABSTRACT && modality != Modality.OPEN) { + trace.report(Errors.SUBCLASS_OPT_IN_INAPPLICABLE.on(entry, "$modality $kind")) + return + } + if (descriptor.isFun) { + trace.report(Errors.SUBCLASS_OPT_IN_INAPPLICABLE.on(entry, "fun interface")) + return + } + if (annotated.isLocal) { + trace.report(Errors.SUBCLASS_OPT_IN_INAPPLICABLE.on(entry, "local $kind")) + return + } + } + } + } + } + checkArgumentsAreMarkers(annotationClasses, trace, entry) + } + + private fun checkArgumentsAreMarkers(annotationClasses: List>, trace: BindingTrace, entry: KtAnnotationEntry) { for (annotationClass in annotationClasses) { val classDescriptor = (annotationClass as? KClassValue)?.getArgumentType(module)?.constructor?.declarationDescriptor as? ClassDescriptor diff --git a/compiler/testData/diagnostics/testsWithStdLib/experimental/subClassOptInModality.kt b/compiler/testData/diagnostics/testsWithStdLib/experimental/subClassOptInModality.kt new file mode 100644 index 00000000000..f133132c566 --- /dev/null +++ b/compiler/testData/diagnostics/testsWithStdLib/experimental/subClassOptInModality.kt @@ -0,0 +1,102 @@ +// FIR_IDENTICAL +@RequiresOptIn +annotation class Marker + +// Error! +@SubclassOptInRequired(Marker::class) +class Final + +// Error! +@SubclassOptInRequired(Marker::class) +sealed class SealedClass { + @SubclassOptInRequired(Marker::class) + object O : SealedClass() +} + +// Error! +@SubclassOptInRequired(Marker::class) +sealed interface SealedInterface + +// Error! +@SubclassOptInRequired(Marker::class) +fun interface FunInterface { + fun doIt() +} + +sealed class Normal + +// Ok! +@SubclassOptInRequired(Marker::class) +open class NormalOpen : Normal() + +// Ok! +@SubclassOptInRequired(Marker::class) +abstract class NormalAbstract : Normal() + +// Error! +@SubclassOptInRequired(Marker::class) +sealed class ErrorSealed : Normal() + +// Error! +@SubclassOptInRequired(Marker::class) +class ErrorFinal : Normal() + +// Ok! +@SubclassOptInRequired(Marker::class) +abstract class Abstract + +// Error! Should be replaced with OptIn +@SubclassOptInRequired(Marker::class) +class Derived1 : Abstract() + +// Ok! +@OptIn(Marker::class) +class Derived2 : Abstract() + +// Error! +@SubclassOptInRequired(Marker::class) +object Obj + +// Error! +@SubclassOptInRequired(Marker::class) +enum class E1 { + FIRST; +} + +enum class E2 { + // Error! + @SubclassOptInRequired(Marker::class) + SECOND; +} + +// Error! +@SubclassOptInRequired(Marker::class) +annotation class A + +// Local stuff +fun foo() { + // Error! + val v = @SubclassOptInRequired(Marker::class) object : Any() { + + } + + // Error! + @SubclassOptInRequired(Marker::class) + open class OpenLocal + + // Ok! + @OptIn(Marker::class) + class DerivedLocal : OpenLocal() + + // Error! + @SubclassOptInRequired(Marker::class) + class Local +} + +// Common rules with OptIn + +annotation class Simple + +// Error! Opt-in marker required +@SubclassOptInRequired(Simple::class) +open class Some diff --git a/compiler/testData/diagnostics/testsWithStdLib/experimental/subClassOptInModality.txt b/compiler/testData/diagnostics/testsWithStdLib/experimental/subClassOptInModality.txt new file mode 100644 index 00000000000..3218e080b5a --- /dev/null +++ b/compiler/testData/diagnostics/testsWithStdLib/experimental/subClassOptInModality.txt @@ -0,0 +1,166 @@ +package + +public fun foo(): kotlin.Unit + +@kotlin.SubclassOptInRequired(markerClass = Marker::class) public final annotation class A : kotlin.Annotation { + public 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.SubclassOptInRequired(markerClass = Marker::class) public abstract class Abstract { + public constructor Abstract() + 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 Derived1 : Abstract { + public constructor Derived1() + 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 Derived2 : Abstract { + public constructor Derived2() + 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 enum class E1 : kotlin.Enum { + enum entry FIRST + + private constructor E1() + @kotlin.internal.IntrinsicConstEvaluation public final override /*1*/ /*fake_override*/ val name: kotlin.String + public final override /*1*/ /*fake_override*/ val ordinal: kotlin.Int + protected final override /*1*/ /*fake_override*/ fun clone(): kotlin.Any + public final override /*1*/ /*fake_override*/ fun compareTo(/*0*/ other: E1): kotlin.Int + public final override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + protected/*protected and package*/ final override /*1*/ /*fake_override*/ /*isHiddenForResolutionEverywhereBesideSupercalls*/ fun finalize(): kotlin.Unit + public final override /*1*/ /*fake_override*/ /*isHiddenForResolutionEverywhereBesideSupercalls*/ fun getDeclaringClass(): java.lang.Class! + public final override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + + // Static members + public final /*synthesized*/ fun valueOf(/*0*/ value: kotlin.String): E1 + public final /*synthesized*/ fun values(): kotlin.Array +} + +public final enum class E2 : kotlin.Enum { + @kotlin.SubclassOptInRequired(markerClass = Marker::class) enum entry SECOND + + private constructor E2() + @kotlin.internal.IntrinsicConstEvaluation public final override /*1*/ /*fake_override*/ val name: kotlin.String + public final override /*1*/ /*fake_override*/ val ordinal: kotlin.Int + protected final override /*1*/ /*fake_override*/ fun clone(): kotlin.Any + public final override /*1*/ /*fake_override*/ fun compareTo(/*0*/ other: E2): kotlin.Int + public final override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + protected/*protected and package*/ final override /*1*/ /*fake_override*/ /*isHiddenForResolutionEverywhereBesideSupercalls*/ fun finalize(): kotlin.Unit + public final override /*1*/ /*fake_override*/ /*isHiddenForResolutionEverywhereBesideSupercalls*/ fun getDeclaringClass(): java.lang.Class! + public final override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + + // Static members + public final /*synthesized*/ fun valueOf(/*0*/ value: kotlin.String): E2 + public final /*synthesized*/ fun values(): kotlin.Array +} + +@kotlin.SubclassOptInRequired(markerClass = Marker::class) public final class ErrorFinal : Normal { + public constructor ErrorFinal() + 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 sealed class ErrorSealed : Normal { + protected constructor ErrorSealed() + 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 Final { + public constructor Final() + 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 fun interface FunInterface { + public abstract fun doIt(): kotlin.Unit + 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 sealed class Normal { + protected constructor Normal() + 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 abstract class NormalAbstract : Normal { + public constructor NormalAbstract() + 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 NormalOpen : Normal { + public constructor NormalOpen() + 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 object Obj { + private constructor Obj() + 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 sealed class SealedClass { + protected constructor SealedClass() + 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 object O : SealedClass { + private constructor O() + 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 sealed interface SealedInterface { + 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 Simple : kotlin.Annotation { + public constructor Simple() + 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 = Simple::class) public open class Some { + public constructor Some() + 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 5051ca57a74..e342ea2a3e9 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("subClassOptInModality.kt") + public void testSubClassOptInModality() throws Exception { + runTest("compiler/testData/diagnostics/testsWithStdLib/experimental/subClassOptInModality.kt"); + } + @Test @TestMetadata("subClassOptInRequired.kt") public void testSubClassOptInRequired() throws Exception {