Allow modifier 'inner' on a class nested in enum entry class

This commit is contained in:
Dmitry Petrov
2017-09-07 18:34:25 +03:00
parent 488a825349
commit ea91a0794d
8 changed files with 122 additions and 25 deletions
@@ -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,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 {}
}
}
@@ -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");
@@ -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