Allow modifier 'inner' on a class nested in enum entry class
This commit is contained in:
@@ -112,22 +112,35 @@ object ModifierCheckerCore {
|
||||
OPEN_KEYWORD to EnumSet.of(INTERFACE)
|
||||
)
|
||||
|
||||
val possibleParentTargetMap = mapOf<KtModifierKeywordToken, Set<KotlinTarget>>(
|
||||
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<KtModifierKeywordToken, TargetAllowedPredicate>(
|
||||
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<KtModifierKeywordToken, Set<KotlinTarget>>()
|
||||
private val deprecatedParentTargetMap = mapOf<KtModifierKeywordToken, Set<KotlinTarget>>()
|
||||
|
||||
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<KotlinTarget> = 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)
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// LANGUAGE_VERSION: 1.1
|
||||
// !LANGUAGE: -InnerClassInEnumEntryClass
|
||||
|
||||
enum class Enum {
|
||||
ENTRY_WITH_CLASS {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
<!WRONG_MODIFIER_CONTAINING_DECLARATION!>companion<!> object {}
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
package
|
||||
|
||||
public final enum class Enum : kotlin.Enum<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<Enum!>!
|
||||
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<Enum>
|
||||
}
|
||||
@@ -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");
|
||||
|
||||
+6
@@ -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");
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user