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 93efa5226e9..2f5314cb6b8 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 @@ -3457,6 +3457,12 @@ internal val KT_DIAGNOSTIC_CONVERTER = KtDiagnosticConverterBuilder.buildConvert token, ) } + add(FirErrors.EXPECT_ACTUAL_OPT_IN_ANNOTATION) { firDiagnostic -> + ExpectActualOptInAnnotationImpl( + firDiagnostic as KtPsiDiagnostic, + token, + ) + } add(FirErrors.INITIALIZER_REQUIRED_FOR_DESTRUCTURING_DECLARATION) { firDiagnostic -> InitializerRequiredForDestructuringDeclarationImpl( firDiagnostic as KtPsiDiagnostic, 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 f62277e3aa1..c43322da9d5 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 @@ -2415,6 +2415,10 @@ sealed interface KtFirDiagnostic : KtDiagnosticWithPsi { override val diagnosticClass get() = NotAMultiplatformCompilation::class } + interface ExpectActualOptInAnnotation : KtFirDiagnostic { + override val diagnosticClass get() = ExpectActualOptInAnnotation::class + } + interface InitializerRequiredForDestructuringDeclaration : KtFirDiagnostic { override val diagnosticClass get() = InitializerRequiredForDestructuringDeclaration::class } 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 11db3b1f65b..6238d949756 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 @@ -2912,6 +2912,11 @@ internal class NotAMultiplatformCompilationImpl( token: KtLifetimeToken, ) : KtAbstractFirDiagnostic(firDiagnostic, token), KtFirDiagnostic.NotAMultiplatformCompilation +internal class ExpectActualOptInAnnotationImpl( + firDiagnostic: KtPsiDiagnostic, + token: KtLifetimeToken, +) : KtAbstractFirDiagnostic(firDiagnostic, token), KtFirDiagnostic.ExpectActualOptInAnnotation + internal class InitializerRequiredForDestructuringDeclarationImpl( firDiagnostic: KtPsiDiagnostic, token: KtLifetimeToken, diff --git a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendMPPDiagnosticsWithLightTreeTestGenerated.java b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendMPPDiagnosticsWithLightTreeTestGenerated.java index 1a1a0e1ccb6..bff2a3b1ff1 100644 --- a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendMPPDiagnosticsWithLightTreeTestGenerated.java +++ b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendMPPDiagnosticsWithLightTreeTestGenerated.java @@ -55,6 +55,12 @@ public class FirOldFrontendMPPDiagnosticsWithLightTreeTestGenerated extends Abst runTest("compiler/testData/diagnostics/tests/multiplatform/expectObjectWithAbstractMember.kt"); } + @Test + @TestMetadata("expectOptInAnnotation.kt") + public void testExpectOptInAnnotation() throws Exception { + runTest("compiler/testData/diagnostics/tests/multiplatform/expectOptInAnnotation.kt"); + } + @Test @TestMetadata("expectTailrec.kt") public void testExpectTailrec() throws Exception { diff --git a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendMPPDiagnosticsWithPsiTestGenerated.java b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendMPPDiagnosticsWithPsiTestGenerated.java index fc3e526f5d0..1981a24b67e 100644 --- a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendMPPDiagnosticsWithPsiTestGenerated.java +++ b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendMPPDiagnosticsWithPsiTestGenerated.java @@ -55,6 +55,12 @@ public class FirOldFrontendMPPDiagnosticsWithPsiTestGenerated extends AbstractFi runTest("compiler/testData/diagnostics/tests/multiplatform/expectObjectWithAbstractMember.kt"); } + @Test + @TestMetadata("expectOptInAnnotation.kt") + public void testExpectOptInAnnotation() throws Exception { + runTest("compiler/testData/diagnostics/tests/multiplatform/expectOptInAnnotation.kt"); + } + @Test @TestMetadata("expectTailrec.kt") public void testExpectTailrec() 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 3bf1643d7aa..8b87332cf7a 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 @@ -1172,6 +1172,8 @@ object DIAGNOSTICS_LIST : DiagnosticList("FirErrors") { val ACTUAL_MISSING by error(PositioningStrategy.ACTUAL_DECLARATION_NAME) val NOT_A_MULTIPLATFORM_COMPILATION by error() + + val EXPECT_ACTUAL_OPT_IN_ANNOTATION by error(PositioningStrategy.EXPECT_ACTUAL_MODIFIER) } val DESTRUCTING_DECLARATION by object : DiagnosticGroup("Destructuring declaration") { diff --git a/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/model/RegularDiagnosticData.kt b/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/model/RegularDiagnosticData.kt index 12145c829fe..0c4109ac1da 100644 --- a/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/model/RegularDiagnosticData.kt +++ b/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/model/RegularDiagnosticData.kt @@ -113,6 +113,7 @@ enum class PositioningStrategy(private val strategy: String? = null) { REDUNDANT_NULLABLE, INLINE_FUN_MODIFIER, CALL_ELEMENT_WITH_DOT, + EXPECT_ACTUAL_MODIFIER, ; val expressionToCreate get() = "SourceElementPositioningStrategies.${strategy ?: name}" 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 3c99937662c..37219bf9e96 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 @@ -626,6 +626,7 @@ object FirErrors { val NO_ACTUAL_CLASS_MEMBER_FOR_EXPECTED_CLASS by error2, List, Map>, Collection>>>>>(SourceElementPositioningStrategies.ACTUAL_DECLARATION_NAME) val ACTUAL_MISSING by error0(SourceElementPositioningStrategies.ACTUAL_DECLARATION_NAME) val NOT_A_MULTIPLATFORM_COMPILATION by error0() + val EXPECT_ACTUAL_OPT_IN_ANNOTATION by error0(SourceElementPositioningStrategies.EXPECT_ACTUAL_MODIFIER) // Destructuring declaration val INITIALIZER_REQUIRED_FOR_DESTRUCTURING_DECLARATION by error0() diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirExpectActualDeclarationChecker.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirExpectActualDeclarationChecker.kt index 707162fba94..8a6689fa677 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirExpectActualDeclarationChecker.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirExpectActualDeclarationChecker.kt @@ -30,6 +30,8 @@ import org.jetbrains.kotlin.fir.symbols.impl.* import org.jetbrains.kotlin.fir.types.coneType import org.jetbrains.kotlin.fir.types.toSymbol import org.jetbrains.kotlin.lexer.KtTokens +import org.jetbrains.kotlin.name.StandardClassIds +import org.jetbrains.kotlin.resolve.checkers.OptInNames import org.jetbrains.kotlin.resolve.multiplatform.ExpectActualCompatibility import org.jetbrains.kotlin.resolve.multiplatform.ExpectActualCompatibility.* @@ -50,6 +52,7 @@ object FirExpectActualDeclarationChecker : FirBasicDeclarationChecker() { } if (declaration.isExpect) { checkExpectDeclarationModifiers(declaration, context, reporter) + checkOptInAnnotation(declaration, declaration.symbol, context, reporter) } if (declaration.isActual) { checkActualDeclarationHasExpected(declaration, context, reporter) @@ -181,6 +184,7 @@ object FirExpectActualDeclarationChecker : FirBasicDeclarationChecker() { context, reporter, ) + checkOptInAnnotation(declaration, expectedSingleCandidate, context, reporter) } } @@ -256,4 +260,19 @@ object FirExpectActualDeclarationChecker : FirBasicDeclarationChecker() { return !declaration.isAnnotationConstructor(session) && !declaration.isPrimaryConstructorOfInlineOrValueClass(session) } + + private fun checkOptInAnnotation( + declaration: FirMemberDeclaration, + expectDeclarationSymbol: FirBasedSymbol<*>, + context: CheckerContext, + reporter: DiagnosticReporter, + ) { + if (declaration is FirClass && + declaration.classKind == ClassKind.ANNOTATION_CLASS && + !expectDeclarationSymbol.hasAnnotation(StandardClassIds.Annotations.OptionalExpectation, context.session) && + declaration.hasAnnotation(OptInNames.REQUIRES_OPT_IN_CLASS_ID, context.session) + ) { + reporter.reportOn(declaration.source, FirErrors.EXPECT_ACTUAL_OPT_IN_ANNOTATION, 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 a338b250ea9..9711a4ff750 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 @@ -230,6 +230,7 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.EXPOSED_PROPERTY_ import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.DEPRECATED_IDENTITY_EQUALS import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.EXPECTED_EXTERNAL_DECLARATION import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.EXPECTED_TAILREC_FUNCTION +import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.EXPECT_ACTUAL_OPT_IN_ANNOTATION import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.EXPECT_CLASS_AS_FUNCTION import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.FORBIDDEN_BINARY_MOD import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.FORBIDDEN_IDENTITY_EQUALS @@ -1845,6 +1846,7 @@ object FirErrorsDefaultMessages : BaseDiagnosticRendererFactory() { ) map.put(ACTUAL_MISSING, "Declaration must be marked with 'actual'") map.put(NOT_A_MULTIPLATFORM_COMPILATION, "'expect' and 'actual' declarations can be used only in multiplatform projects. Learn more about Kotlin Multiplatform: https://kotl.in/multiplatform-setup") + map.put(EXPECT_ACTUAL_OPT_IN_ANNOTATION, "Opt-in annotations are prohibited to be `expect` or `actual`. Instead, declare annotation once in common sources.") // Destructuring declaration map.put(INITIALIZER_REQUIRED_FOR_DESTRUCTURING_DECLARATION, "Initializer required for destructuring declaration") diff --git a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java index 93ccf4fbdf4..f7cf9724741 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java @@ -836,6 +836,7 @@ public interface Errors { DiagnosticFactory0 OPTIONAL_DECLARATION_OUTSIDE_OF_ANNOTATION_ENTRY = DiagnosticFactory0.create(ERROR); DiagnosticFactory0 OPTIONAL_DECLARATION_USAGE_IN_NON_COMMON_SOURCE = DiagnosticFactory0.create(ERROR); DiagnosticFactory0 NOT_A_MULTIPLATFORM_COMPILATION = DiagnosticFactory0.create(ERROR); + DiagnosticFactory0 EXPECT_ACTUAL_OPT_IN_ANNOTATION = DiagnosticFactory0.create(ERROR, EXPECT_ACTUAL_MODIFIER); //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 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 a72c1bdd64d..5cf817607e4 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java @@ -392,6 +392,7 @@ public class DefaultErrorMessages { MAP.put(OPTIONAL_DECLARATION_OUTSIDE_OF_ANNOTATION_ENTRY, "Declaration annotated with '@OptionalExpectation' can only be used inside an annotation entry"); MAP.put(OPTIONAL_DECLARATION_USAGE_IN_NON_COMMON_SOURCE, "Declaration annotated with '@OptionalExpectation' can only be used in common module sources"); MAP.put(NOT_A_MULTIPLATFORM_COMPILATION, "'expect' and 'actual' declarations can be used only in multiplatform projects. Learn more about Kotlin Multiplatform: https://kotl.in/multiplatform-setup"); + MAP.put(EXPECT_ACTUAL_OPT_IN_ANNOTATION, "Opt-in annotations are prohibited to be `expect` or `actual`. Instead, declare annotation once in common sources."); MAP.put(PROJECTION_ON_NON_CLASS_TYPE_ARGUMENT, "Projections are not allowed on type arguments of functions and properties"); MAP.put(SUPERTYPE_NOT_INITIALIZED, "This type has a constructor, and thus must be initialized here"); diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/ExpectedActualDeclarationChecker.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/ExpectedActualDeclarationChecker.kt index d635a24af69..9936d75aa0d 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/ExpectedActualDeclarationChecker.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/ExpectedActualDeclarationChecker.kt @@ -64,6 +64,7 @@ class ExpectedActualDeclarationChecker( declaration, descriptor, context.trace, checkActualModifier, context.expectActualTracker ) + checkOptInAnnotation(declaration, descriptor, descriptor, context.trace) } if (descriptor.isActualOrSomeContainerIsActual()) { val allDependsOnModules = moduleStructureOracle.findAllDependsOnPaths(descriptor.module).flatMap { it.nodes }.toHashSet() @@ -334,6 +335,7 @@ class ExpectedActualDeclarationChecker( if (expectedConstructor != null && actualConstructor != null) { checkAnnotationConstructors(expectedConstructor, actualConstructor, trace, reportOn) } + checkOptInAnnotation(reportOn, descriptor, expected, trace) } } val expectSingleCandidate = compatibility.values.singleOrNull()?.singleOrNull() @@ -426,6 +428,21 @@ class ExpectedActualDeclarationChecker( return null } + private fun checkOptInAnnotation( + reportOn: KtNamedDeclaration, + descriptor: MemberDescriptor, + expectDescriptor: MemberDescriptor, + trace: BindingTrace, + ) { + if (descriptor is ClassDescriptor && + descriptor.kind == ClassKind.ANNOTATION_CLASS && + descriptor.annotations.hasAnnotation(OptInNames.REQUIRES_OPT_IN_FQ_NAME) && + !expectDescriptor.annotations.hasAnnotation(OptionalAnnotationUtil.OPTIONAL_EXPECTATION_FQ_NAME) + ) { + trace.report(Errors.EXPECT_ACTUAL_OPT_IN_ANNOTATION.on(reportOn)) + } + } + companion object { fun Map, Collection>.allStrongIncompatibilities(): Boolean = this.keys.all { it is Incompatible && it.kind == IncompatibilityKind.STRONG } diff --git a/compiler/testData/diagnostics/tests/multiplatform/expectOptInAnnotation.fir.kt b/compiler/testData/diagnostics/tests/multiplatform/expectOptInAnnotation.fir.kt new file mode 100644 index 00000000000..8bcff214851 --- /dev/null +++ b/compiler/testData/diagnostics/tests/multiplatform/expectOptInAnnotation.fir.kt @@ -0,0 +1,23 @@ +// WITH_STDLIB +// MODULE: m1-common +// FILE: common.kt +expect annotation class ActualOnly + +@RequiresOptIn +expect annotation class Both + +@OptIn(ExperimentalMultiplatform::class) +@RequiresOptIn +@OptionalExpectation +expect annotation class MyOptIn + +// MODULE: m1-jvm()()(m1-common) +// FILE: jvm.kt +@RequiresOptIn +actual annotation class ActualOnly + +@RequiresOptIn +actual annotation class Both + +@RequiresOptIn +actual annotation class MyOptIn diff --git a/compiler/testData/diagnostics/tests/multiplatform/expectOptInAnnotation.kt b/compiler/testData/diagnostics/tests/multiplatform/expectOptInAnnotation.kt new file mode 100644 index 00000000000..a8a824545f8 --- /dev/null +++ b/compiler/testData/diagnostics/tests/multiplatform/expectOptInAnnotation.kt @@ -0,0 +1,23 @@ +// WITH_STDLIB +// MODULE: m1-common +// FILE: common.kt +expect annotation class ActualOnly + +@RequiresOptIn +expect annotation class Both + +@OptIn(ExperimentalMultiplatform::class) +@RequiresOptIn +@OptionalExpectation +expect annotation class MyOptIn + +// MODULE: m1-jvm()()(m1-common) +// FILE: jvm.kt +@RequiresOptIn +actual annotation class ActualOnly + +@RequiresOptIn +actual annotation class Both + +@RequiresOptIn +actual annotation class MyOptIn 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 282ff11cf7a..3a35cd1b7a9 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 @@ -22558,6 +22558,12 @@ public class DiagnosticTestGenerated extends AbstractDiagnosticTest { runTest("compiler/testData/diagnostics/tests/multiplatform/expectObjectWithAbstractMember.kt"); } + @Test + @TestMetadata("expectOptInAnnotation.kt") + public void testExpectOptInAnnotation() throws Exception { + runTest("compiler/testData/diagnostics/tests/multiplatform/expectOptInAnnotation.kt"); + } + @Test @TestMetadata("expectTailrec.kt") public void testExpectTailrec() throws Exception { diff --git a/core/compiler.common/src/org/jetbrains/kotlin/name/StandardClassIds.kt b/core/compiler.common/src/org/jetbrains/kotlin/name/StandardClassIds.kt index 2c9920d9fed..b52562f4aa0 100644 --- a/core/compiler.common/src/org/jetbrains/kotlin/name/StandardClassIds.kt +++ b/core/compiler.common/src/org/jetbrains/kotlin/name/StandardClassIds.kt @@ -189,6 +189,8 @@ object StandardClassIds { val AccessibleLateinitPropertyLiteral = "AccessibleLateinitPropertyLiteral".internalId() + val OptionalExpectation = "OptionalExpectation".baseId() + object Java { val Deprecated = "Deprecated".javaLangId() val Repeatable = "Repeatable".javaAnnotationId()