diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/ModifiersChecker.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/ModifiersChecker.kt index 97cce886e34..14f8c35d3d8 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/ModifiersChecker.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/ModifiersChecker.kt @@ -112,22 +112,35 @@ object ModifierCheckerCore { OPEN_KEYWORD to EnumSet.of(INTERFACE) ) - val possibleParentTargetMap = mapOf>( - INNER_KEYWORD to EnumSet.of(CLASS_ONLY, LOCAL_CLASS, ENUM_CLASS), - OVERRIDE_KEYWORD to EnumSet.of(CLASS_ONLY, LOCAL_CLASS, OBJECT, OBJECT_LITERAL, - INTERFACE, ENUM_CLASS, ENUM_ENTRY), - PROTECTED_KEYWORD to EnumSet.of(CLASS_ONLY, LOCAL_CLASS, ENUM_CLASS, COMPANION_OBJECT), - INTERNAL_KEYWORD to EnumSet.of(CLASS_ONLY, LOCAL_CLASS, OBJECT, OBJECT_LITERAL, - ENUM_CLASS, ENUM_ENTRY, FILE), - PRIVATE_KEYWORD to EnumSet.of(CLASS_ONLY, LOCAL_CLASS, OBJECT, OBJECT_LITERAL, - INTERFACE, ENUM_CLASS, ENUM_ENTRY, FILE), - COMPANION_KEYWORD to EnumSet.of(CLASS_ONLY, ENUM_CLASS, INTERFACE), - FINAL_KEYWORD to EnumSet.of(CLASS_ONLY, LOCAL_CLASS, OBJECT, OBJECT_LITERAL, - ENUM_CLASS, ENUM_ENTRY, ANNOTATION_CLASS, FILE), - VARARG_KEYWORD to EnumSet.of(CONSTRUCTOR, FUNCTION, CLASS) + private val possibleParentTargetPredicateMap = mapOf( + INNER_KEYWORD to or(always(CLASS_ONLY, LOCAL_CLASS, ENUM_CLASS), + ifSupported(LanguageFeature.InnerClassInEnumEntryClass, ENUM_ENTRY)), + OVERRIDE_KEYWORD to always(CLASS_ONLY, LOCAL_CLASS, OBJECT, OBJECT_LITERAL, INTERFACE, ENUM_CLASS, ENUM_ENTRY), + PROTECTED_KEYWORD to always(CLASS_ONLY, LOCAL_CLASS, ENUM_CLASS, COMPANION_OBJECT), + INTERNAL_KEYWORD to always(CLASS_ONLY, LOCAL_CLASS, OBJECT, OBJECT_LITERAL, ENUM_CLASS, ENUM_ENTRY, FILE), + PRIVATE_KEYWORD to always(CLASS_ONLY, LOCAL_CLASS, OBJECT, OBJECT_LITERAL, INTERFACE, ENUM_CLASS, ENUM_ENTRY, FILE), + COMPANION_KEYWORD to always(CLASS_ONLY, ENUM_CLASS, INTERFACE), + FINAL_KEYWORD to always(CLASS_ONLY, LOCAL_CLASS, OBJECT, OBJECT_LITERAL, ENUM_CLASS, ENUM_ENTRY, ANNOTATION_CLASS, FILE), + VARARG_KEYWORD to always(CONSTRUCTOR, FUNCTION, CLASS) ) - val deprecatedParentTargetMap = mapOf>() + private val deprecatedParentTargetMap = mapOf>() + + fun isPossibleParentTarget( + modifier: KtModifierKeywordToken, + parentTarget: KotlinTarget, + languageVersionSettings: LanguageVersionSettings + ): Boolean { + deprecatedParentTargetMap[modifier]?.let { + if (parentTarget in it) return false + } + + possibleParentTargetPredicateMap[modifier]?.let { + return it.isAllowed(parentTarget, languageVersionSettings) + } + + return true + } // First modifier in pair should be also first in declaration private val mutualCompatibility = buildCompatibilityMap() @@ -305,7 +318,7 @@ object ModifierCheckerCore { // Should return false if error is reported, true otherwise - private fun checkParent(trace: BindingTrace, node: ASTNode, parentDescriptor: DeclarationDescriptor?): Boolean { + private fun checkParent(trace: BindingTrace, node: ASTNode, parentDescriptor: DeclarationDescriptor?, languageVersionSettings: LanguageVersionSettings): Boolean { val modifier = node.elementType as KtModifierKeywordToken val actualParents: List = when (parentDescriptor) { is ClassDescriptor -> KotlinTarget.classActualTargets(parentDescriptor) @@ -319,9 +332,8 @@ object ModifierCheckerCore { trace.report(Errors.DEPRECATED_MODIFIER_CONTAINING_DECLARATION.on(node.psi, modifier, actualParents.firstOrNull()?.description ?: "this scope")) return true } - val possibleParents = possibleParentTargetMap[modifier] ?: return true - if (possibleParents == KotlinTarget.ALL_TARGET_SET) return true - if (actualParents.any { it in possibleParents }) return true + val possibleParentPredicate = possibleParentTargetPredicateMap[modifier] ?: return true + if (actualParents.any { possibleParentPredicate.isAllowed(it, languageVersionSettings) }) return true trace.report(Errors.WRONG_MODIFIER_CONTAINING_DECLARATION.on(node.psi, modifier, actualParents.firstOrNull()?.description ?: "this scope")) return false } @@ -349,7 +361,7 @@ object ModifierCheckerCore { if (second !in incorrectNodes) { when { !checkTarget(trace, second, actualTargets) -> incorrectNodes += second - !checkParent(trace, second, parentDescriptor) -> incorrectNodes += second + !checkParent(trace, second, parentDescriptor, languageVersionSettings) -> incorrectNodes += second !checkLanguageLevelSupport(trace, second, languageVersionSettings, actualTargets) -> incorrectNodes += second } } @@ -375,3 +387,29 @@ object ModifierCheckerCore { checkModifierList(list, trace, descriptor?.containingDeclaration, actualTargets, languageVersionSettings) } } + + +private interface TargetAllowedPredicate { + fun isAllowed(target: KotlinTarget, languageVersionSettings: LanguageVersionSettings): Boolean +} + + +private fun always(target: KotlinTarget, vararg targets: KotlinTarget) = object : TargetAllowedPredicate { + private val targetSet = EnumSet.of(target, *targets) + + override fun isAllowed(target: KotlinTarget, languageVersionSettings: LanguageVersionSettings) = + target in targetSet +} + +private fun ifSupported(languageFeature: LanguageFeature, target: KotlinTarget, vararg targets: KotlinTarget) = object : TargetAllowedPredicate { + private val targetSet = EnumSet.of(target, *targets) + + override fun isAllowed(target: KotlinTarget, languageVersionSettings: LanguageVersionSettings) = + languageVersionSettings.supportsFeature(languageFeature) && target in targetSet +} + +private fun or(p1: TargetAllowedPredicate, p2: TargetAllowedPredicate) = object : TargetAllowedPredicate { + override fun isAllowed(target: KotlinTarget, languageVersionSettings: LanguageVersionSettings) = + p1.isAllowed(target, languageVersionSettings) || + p2.isAllowed(target, languageVersionSettings) +} diff --git a/compiler/testData/diagnostics/tests/inner/innerClassInEnumEntryClass_lv11.kt b/compiler/testData/diagnostics/tests/inner/innerClassInEnumEntryClass_lv11.kt index ee2c69e7c12..a2570f57c71 100644 --- a/compiler/testData/diagnostics/tests/inner/innerClassInEnumEntryClass_lv11.kt +++ b/compiler/testData/diagnostics/tests/inner/innerClassInEnumEntryClass_lv11.kt @@ -1,4 +1,4 @@ -// LANGUAGE_VERSION: 1.1 +// !LANGUAGE: -InnerClassInEnumEntryClass enum class Enum { ENTRY_WITH_CLASS { diff --git a/compiler/testData/diagnostics/tests/inner/innerClassInEnumEntryClass_lv12.kt b/compiler/testData/diagnostics/tests/inner/innerClassInEnumEntryClass_lv12.kt new file mode 100644 index 00000000000..e9210739943 --- /dev/null +++ b/compiler/testData/diagnostics/tests/inner/innerClassInEnumEntryClass_lv12.kt @@ -0,0 +1,19 @@ +// !LANGUAGE: +InnerClassInEnumEntryClass + +enum class Enum { + ENTRY_WITH_CLASS { + inner class TestInner + + class TestNested + + interface TestInterface + + object TestObject + + enum class TestEnumClass { + OTHER_ENTRY + } + + companion object {} + } +} diff --git a/compiler/testData/diagnostics/tests/inner/innerClassInEnumEntryClass_lv12.txt b/compiler/testData/diagnostics/tests/inner/innerClassInEnumEntryClass_lv12.txt new file mode 100644 index 00000000000..e357e6ce0df --- /dev/null +++ b/compiler/testData/diagnostics/tests/inner/innerClassInEnumEntryClass_lv12.txt @@ -0,0 +1,20 @@ +package + +public final enum class Enum : kotlin.Enum { + enum entry ENTRY_WITH_CLASS + + private constructor Enum() + 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: Enum): 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): Enum + public final /*synthesized*/ fun values(): kotlin.Array +} diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java index 9c6d2b1ffd0..66af3c619c9 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java @@ -11866,6 +11866,12 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest { doTest(fileName); } + @TestMetadata("innerClassInEnumEntryClass_lv12.kt") + public void testInnerClassInEnumEntryClass_lv12() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/inner/innerClassInEnumEntryClass_lv12.kt"); + doTest(fileName); + } + @TestMetadata("InnerClassNameClash.kt") public void testInnerClassNameClash() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/inner/InnerClassNameClash.kt"); diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/javac/DiagnosticsUsingJavacTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/javac/DiagnosticsUsingJavacTestGenerated.java index 48a3e923971..4bb59bb5761 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/javac/DiagnosticsUsingJavacTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/javac/DiagnosticsUsingJavacTestGenerated.java @@ -11866,6 +11866,12 @@ public class DiagnosticsUsingJavacTestGenerated extends AbstractDiagnosticsUsing doTest(fileName); } + @TestMetadata("innerClassInEnumEntryClass_lv12.kt") + public void testInnerClassInEnumEntryClass_lv12() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/inner/innerClassInEnumEntryClass_lv12.kt"); + doTest(fileName); + } + @TestMetadata("InnerClassNameClash.kt") public void testInnerClassNameClash() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/inner/InnerClassNameClash.kt"); diff --git a/compiler/util/src/org/jetbrains/kotlin/config/LanguageVersionSettings.kt b/compiler/util/src/org/jetbrains/kotlin/config/LanguageVersionSettings.kt index a8c3a74a9d2..fc2e69e55b5 100644 --- a/compiler/util/src/org/jetbrains/kotlin/config/LanguageVersionSettings.kt +++ b/compiler/util/src/org/jetbrains/kotlin/config/LanguageVersionSettings.kt @@ -59,6 +59,7 @@ enum class LanguageFeature( CapturedInClosureSmartCasts(KOTLIN_1_2), LateinitTopLevelProperties(KOTLIN_1_2), LateinitLocalVariables(KOTLIN_1_2), + InnerClassInEnumEntryClass(KOTLIN_1_2), CallableReferencesToClassMembersWithEmptyLHS(KOTLIN_1_2), RestrictionOfValReassignmentViaBackingField(KOTLIN_1_3), diff --git a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/KeywordCompletion.kt b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/KeywordCompletion.kt index 93087696610..bb277c23230 100644 --- a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/KeywordCompletion.kt +++ b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/KeywordCompletion.kt @@ -388,11 +388,7 @@ object KeywordCompletion { else -> return true } - val modifierParents = ModifierCheckerCore.possibleParentTargetMap[keywordTokenType] - if (modifierParents != null && parentTarget !in modifierParents) return false - - val deprecatedParents = ModifierCheckerCore.deprecatedParentTargetMap[keywordTokenType] - if (deprecatedParents != null && parentTarget in deprecatedParents) return false + if (!ModifierCheckerCore.isPossibleParentTarget(keywordTokenType, parentTarget, languageVersionSettings)) return false return true } @@ -451,6 +447,17 @@ object KeywordCompletion { } } + private fun isModifierParentSupportedAtLanguageLevel( + keyword: KtKeywordToken, + target: KotlinTarget, + languageVersionSettings: LanguageVersionSettings + ): Boolean { + if (keyword == KtTokens.INNER_KEYWORD && target == ENUM_ENTRY) { + return languageVersionSettings.supportsFeature(LanguageFeature.InnerClassInEnumEntryClass) + } + return true + } + // builds text within scope (or from the start of the file) before position element excluding almost all declarations private fun buildReducedContextBefore(builder: StringBuilder, position: PsiElement, scope: PsiElement?) { if (position == scope) return