diff --git a/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/DiagnosticCompilerTestFE10TestdataTestGenerated.java b/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/DiagnosticCompilerTestFE10TestdataTestGenerated.java index c62d64e42eb..0bed2cb6904 100644 --- a/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/DiagnosticCompilerTestFE10TestdataTestGenerated.java +++ b/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/DiagnosticCompilerTestFE10TestdataTestGenerated.java @@ -21232,6 +21232,12 @@ public class DiagnosticCompilerTestFE10TestdataTestGenerated extends AbstractDia runTest("compiler/testData/diagnostics/tests/modifiers/protected.kt"); } + @Test + @TestMetadata("protectedInExpectActual.kt") + public void testProtectedInExpectActual() throws Exception { + runTest("compiler/testData/diagnostics/tests/modifiers/protectedInExpectActual.kt"); + } + @Test @TestMetadata("redundantTargets.kt") public void testRedundantTargets() throws Exception { diff --git a/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/LLFirPreresolvedReversedDiagnosticCompilerFE10TestDataTestGenerated.java b/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/LLFirPreresolvedReversedDiagnosticCompilerFE10TestDataTestGenerated.java index ee7220f0b6c..e37a6ddc356 100644 --- a/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/LLFirPreresolvedReversedDiagnosticCompilerFE10TestDataTestGenerated.java +++ b/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/LLFirPreresolvedReversedDiagnosticCompilerFE10TestDataTestGenerated.java @@ -21232,6 +21232,12 @@ public class LLFirPreresolvedReversedDiagnosticCompilerFE10TestDataTestGenerated runTest("compiler/testData/diagnostics/tests/modifiers/protected.kt"); } + @Test + @TestMetadata("protectedInExpectActual.kt") + public void testProtectedInExpectActual() throws Exception { + runTest("compiler/testData/diagnostics/tests/modifiers/protectedInExpectActual.kt"); + } + @Test @TestMetadata("redundantTargets.kt") public void testRedundantTargets() throws Exception { diff --git a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirLightTreeOldFrontendDiagnosticsTestGenerated.java b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirLightTreeOldFrontendDiagnosticsTestGenerated.java index 0ebbf9fe009..a101d9dd7a7 100644 --- a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirLightTreeOldFrontendDiagnosticsTestGenerated.java +++ b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirLightTreeOldFrontendDiagnosticsTestGenerated.java @@ -21232,6 +21232,12 @@ public class FirLightTreeOldFrontendDiagnosticsTestGenerated extends AbstractFir runTest("compiler/testData/diagnostics/tests/modifiers/protected.kt"); } + @Test + @TestMetadata("protectedInExpectActual.kt") + public void testProtectedInExpectActual() throws Exception { + runTest("compiler/testData/diagnostics/tests/modifiers/protectedInExpectActual.kt"); + } + @Test @TestMetadata("redundantTargets.kt") public void testRedundantTargets() throws Exception { diff --git a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirPsiOldFrontendDiagnosticsTestGenerated.java b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirPsiOldFrontendDiagnosticsTestGenerated.java index e710bc2e31d..a01a726fb99 100644 --- a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirPsiOldFrontendDiagnosticsTestGenerated.java +++ b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirPsiOldFrontendDiagnosticsTestGenerated.java @@ -21238,6 +21238,12 @@ public class FirPsiOldFrontendDiagnosticsTestGenerated extends AbstractFirPsiDia runTest("compiler/testData/diagnostics/tests/modifiers/protected.kt"); } + @Test + @TestMetadata("protectedInExpectActual.kt") + public void testProtectedInExpectActual() throws Exception { + runTest("compiler/testData/diagnostics/tests/modifiers/protectedInExpectActual.kt"); + } + @Test @TestMetadata("redundantTargets.kt") public void testRedundantTargets() throws Exception { diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirModifierChecker.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirModifierChecker.kt index e0b4850710b..f0bd185cfcc 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirModifierChecker.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirModifierChecker.kt @@ -22,9 +22,7 @@ import org.jetbrains.kotlin.diagnostics.KtDiagnosticFactory2 import org.jetbrains.kotlin.diagnostics.reportOn import org.jetbrains.kotlin.fir.declarations.* import org.jetbrains.kotlin.fir.declarations.impl.FirPrimaryConstructor -import org.jetbrains.kotlin.fir.declarations.utils.isCompanion -import org.jetbrains.kotlin.fir.declarations.utils.isInner -import org.jetbrains.kotlin.fir.declarations.utils.isLocal +import org.jetbrains.kotlin.fir.declarations.utils.* import org.jetbrains.kotlin.fir.languageVersionSettings import org.jetbrains.kotlin.lexer.KtModifierKeywordToken import org.jetbrains.kotlin.lexer.KtTokens @@ -85,7 +83,7 @@ object FirModifierChecker : FirBasicDeclarationChecker() { val modifier = secondModifier.token when { !checkTarget(modifierSource, modifier, actualTargets, parent, context, reporter) -> reportedNodes += secondModifier - !checkParent(modifierSource, modifier, actualParents, context, reporter) -> reportedNodes += secondModifier + !checkParent(modifierSource, modifier, actualParents, parent, context, reporter) -> reportedNodes += secondModifier } } } @@ -236,6 +234,7 @@ object FirModifierChecker : FirBasicDeclarationChecker() { modifierSource: KtSourceElement, modifierToken: KtModifierKeywordToken, actualParents: List, + parent: FirDeclaration?, context: CheckerContext, reporter: DiagnosticReporter ): Boolean { @@ -251,6 +250,15 @@ object FirModifierChecker : FirBasicDeclarationChecker() { return true } + if (modifierToken == KtTokens.PROTECTED_KEYWORD && isFinalExpectOrActualClass(parent)) { + reporter.reportOn( + modifierSource, + FirErrors.WRONG_MODIFIER_CONTAINING_DECLARATION, + modifierToken, + "final expect or actual class", + context, + ) + } val possibleParentPredicate = possibleParentTargetPredicateMap[modifierToken] ?: return true if (actualParents.any { possibleParentPredicate.isAllowed(it, context.session.languageVersionSettings) }) return true @@ -268,4 +276,8 @@ object FirModifierChecker : FirBasicDeclarationChecker() { private fun List.firstOrThis(): String { return firstOrNull()?.description ?: "this" } + + private fun isFinalExpectOrActualClass(d: FirDeclaration?): Boolean { + return d is FirClass && d.isFinal && (d.isExpect || d.isActual) + } } diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/ModifiersChecker.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/ModifiersChecker.kt index 391bea5dc55..965cfe8801d 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/ModifiersChecker.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/ModifiersChecker.kt @@ -180,6 +180,15 @@ object ModifierCheckerCore { ) return true } + if (modifier == PROTECTED_KEYWORD && isFinalExpectOrActualClass(parentDescriptor)) { + trace.report( + Errors.WRONG_MODIFIER_CONTAINING_DECLARATION.on( + node.psi, + modifier, + "final expect or actual class" + ) + ) + } val possibleParentPredicate = possibleParentTargetPredicateMap[modifier] ?: return true if (actualParents.any { possibleParentPredicate.isAllowed(it, languageVersionSettings) }) return true trace.report( @@ -237,4 +246,8 @@ object ModifierCheckerCore { return true } + + private fun isFinalExpectOrActualClass(d: DeclarationDescriptor?): Boolean { + return d is ClassDescriptor && d.isFinalOrEnum && (d.isExpect || d.isActual) + } } \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/modifiers/protectedInExpectActual.kt b/compiler/testData/diagnostics/tests/modifiers/protectedInExpectActual.kt new file mode 100644 index 00000000000..f8529688ce4 --- /dev/null +++ b/compiler/testData/diagnostics/tests/modifiers/protectedInExpectActual.kt @@ -0,0 +1,48 @@ +// LANGUAGE: +MultiPlatformProjects +// !DIAGNOSTICS: -NO_ACTUAL_FOR_EXPECT, -ACTUAL_WITHOUT_EXPECT +// FIR_IDENTICAL +class SimpleClass { + protected fun foo() = Unit +} + +expect class ExpClass { + protected fun foo() + + protected val bar: Int +} + +actual class ActClass { + actual protected fun foo() = Unit + + actual protected val bar: Int = 42 +} + +expect open class ExpOpenClass { + protected fun foo() +} + +actual open class ActOpenClass { + actual protected fun foo() = Unit +} + +enum class SimpleEnum { + ENTRY; + + protected fun foo() = Unit +} + +expect enum class ExpEnumClass { + ENTRY; + + protected fun foo() + + protected val bar: Int +} + +actual enum class ActEnumClass { + ENTRY; + + actual protected fun foo() = Unit + + protected val bar: Int = 42 +} 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 283d79e1c74..e8c310e0fbe 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 @@ -21238,6 +21238,12 @@ public class DiagnosticTestGenerated extends AbstractDiagnosticTest { runTest("compiler/testData/diagnostics/tests/modifiers/protected.kt"); } + @Test + @TestMetadata("protectedInExpectActual.kt") + public void testProtectedInExpectActual() throws Exception { + runTest("compiler/testData/diagnostics/tests/modifiers/protectedInExpectActual.kt"); + } + @Test @TestMetadata("redundantTargets.kt") public void testRedundantTargets() throws Exception {