Specialize diagnostic message for nested classes not allowed

This commit is contained in:
Dmitry Petrov
2017-09-07 14:06:55 +03:00
parent dd9ffd10b6
commit 488a825349
17 changed files with 237 additions and 36 deletions
@@ -930,8 +930,8 @@ public interface Errors {
DiagnosticFactory2<KtExpression, ClassDescriptor, String> NESTED_CLASS_SHOULD_BE_QUALIFIED = DiagnosticFactory2.create(ERROR);
DiagnosticFactory1<PsiElement, ClassDescriptor> INACCESSIBLE_OUTER_CLASS_EXPRESSION = DiagnosticFactory1.create(ERROR);
DiagnosticFactory0<KtClass> NESTED_CLASS_NOT_ALLOWED = DiagnosticFactory0.create(ERROR, DECLARATION_NAME);
DiagnosticFactory0<KtObjectDeclaration> NESTED_OBJECT_NOT_ALLOWED = DiagnosticFactory0.create(ERROR, DECLARATION_NAME);
DiagnosticFactory1<KtClassOrObject, String> NESTED_CLASS_NOT_ALLOWED = DiagnosticFactory1.create(ERROR, DECLARATION_NAME);
DiagnosticFactory1<KtClassOrObject, String> NESTED_CLASS_NOT_ALLOWED_SINCE_1_3 = DiagnosticFactory1.create(WARNING, DECLARATION_NAME);
//Inline and inlinable parameters
DiagnosticFactory2<KtElement, DeclarationDescriptor, DeclarationDescriptor> NON_PUBLIC_CALL_FROM_PUBLIC_INLINE = DiagnosticFactory2.create(ERROR, CALL_ELEMENT);
@@ -423,8 +423,8 @@ public class DefaultErrorMessages {
MAP.put(NESTED_CLASS_SHOULD_BE_QUALIFIED, "Nested {0} should be qualified as ''{1}''", RENDER_CLASS_OR_OBJECT_NAME, TO_STRING);
MAP.put(INACCESSIBLE_OUTER_CLASS_EXPRESSION, "Expression is inaccessible from a nested class ''{0}''", NAME);
MAP.put(NESTED_CLASS_NOT_ALLOWED, "Nested class is not allowed here, use 'inner' keyword to make the class inner");
MAP.put(NESTED_OBJECT_NOT_ALLOWED, "Objects inside inner classes are prohibited");
MAP.put(NESTED_CLASS_NOT_ALLOWED, "{0} is not allowed here", STRING);
MAP.put(NESTED_CLASS_NOT_ALLOWED_SINCE_1_3, "{0} will not be allowed here in Kotlin 1.3 and later, please migrate your code", STRING);
MAP.put(HAS_NEXT_MISSING, "hasNext() cannot be called on iterator() of type ''{0}''", RENDER_TYPE);
MAP.put(HAS_NEXT_FUNCTION_AMBIGUITY, "hasNext() is ambiguous for iterator() of type ''{0}''", RENDER_TYPE);
@@ -40,12 +40,33 @@ import static org.jetbrains.kotlin.diagnostics.Errors.*;
import static org.jetbrains.kotlin.lexer.KtTokens.*;
public class ModifiersChecker {
private static boolean isIllegalNestedClass(@NotNull DeclarationDescriptor descriptor) {
if (!(descriptor instanceof ClassDescriptor)) return false;
DeclarationDescriptor containingDeclaration = descriptor.getContainingDeclaration();
if (!(containingDeclaration instanceof ClassDescriptor)) return false;
ClassDescriptor containingClass = (ClassDescriptor) containingDeclaration;
return containingClass.isInner() || DescriptorUtils.isLocal(containingClass);
private enum DetailedClassKind {
ENUM_CLASS("Enum class"),
ENUM_ENTRY("Enum entry"),
ANNOTATION_CLASS("Annotation class"),
INTERFACE("Interface"),
COMPANION_OBJECT("Companion object"),
ANONYMOUS_OBJECT("Anonymous object"),
OBJECT("Object"),
CLASS("Class");
public final String withCapitalFirstLetter;
DetailedClassKind(String withCapitalFirstLetter) {
this.withCapitalFirstLetter = withCapitalFirstLetter;
}
@NotNull
public static DetailedClassKind getClassKind(@NotNull ClassDescriptor descriptor) {
if (DescriptorUtils.isEnumEntry(descriptor)) return ENUM_ENTRY;
if (DescriptorUtils.isEnumClass(descriptor)) return ENUM_CLASS;
if (DescriptorUtils.isAnnotationClass(descriptor)) return ANNOTATION_CLASS;
if (DescriptorUtils.isInterface(descriptor)) return INTERFACE;
if (DescriptorUtils.isCompanionObject(descriptor)) return COMPANION_OBJECT;
if (DescriptorUtils.isAnonymousObject(descriptor)) return ANONYMOUS_OBJECT;
if (DescriptorUtils.isObject(descriptor)) return OBJECT;
return CLASS;
}
}
@NotNull
@@ -157,18 +178,32 @@ public class ModifiersChecker {
public void checkModifiersForDeclaration(@NotNull KtDeclaration modifierListOwner, @NotNull MemberDescriptor descriptor) {
checkNestedClassAllowed(modifierListOwner, descriptor);
checkObjectInsideInnerClass(modifierListOwner, descriptor);
checkTypeParametersModifiers(modifierListOwner);
checkModifierListCommon(modifierListOwner, descriptor);
checkIllegalHeader(modifierListOwner, descriptor);
}
private void checkObjectInsideInnerClass(@NotNull KtDeclaration modifierListOwner, @NotNull MemberDescriptor descriptor) {
if (modifierListOwner instanceof KtObjectDeclaration) {
KtObjectDeclaration ktObject = (KtObjectDeclaration) modifierListOwner;
if (!ktObject.isLocal() && isIllegalNestedClass(descriptor)) {
trace.report(NESTED_OBJECT_NOT_ALLOWED.on(ktObject));
}
private void checkNestedClassAllowed(@NotNull KtDeclaration declaration, @NotNull DeclarationDescriptor descriptor) {
if (!(declaration instanceof KtClassOrObject)) return;
KtClassOrObject ktClassOrObject = (KtClassOrObject) declaration;
if (!(descriptor instanceof ClassDescriptor)) return;
ClassDescriptor classDescriptor = (ClassDescriptor) descriptor;
DeclarationDescriptor containingDeclaration = descriptor.getContainingDeclaration();
if (!(containingDeclaration instanceof ClassDescriptor)) return;
ClassDescriptor containingClass = (ClassDescriptor) containingDeclaration;
DetailedClassKind kind = DetailedClassKind.getClassKind(classDescriptor);
if (kind == DetailedClassKind.ANONYMOUS_OBJECT || kind == DetailedClassKind.ENUM_ENTRY) return;
// Local enums / objects / companion objects are handled in different checks
if ((kind == DetailedClassKind.ENUM_CLASS || kind == DetailedClassKind.OBJECT || kind == DetailedClassKind.COMPANION_OBJECT) &&
DescriptorUtils.isLocal(classDescriptor)) {
return;
}
if (!classDescriptor.isInner() && (containingClass.isInner() || DescriptorUtils.isLocal(containingClass))) {
trace.report(NESTED_CLASS_NOT_ALLOWED.on(ktClassOrObject, kind.withCapitalFirstLetter));
}
}
@@ -206,17 +241,6 @@ public class ModifiersChecker {
}
}
private void checkNestedClassAllowed(@NotNull KtModifierListOwner modifierListOwner, @NotNull DeclarationDescriptor descriptor) {
if (modifierListOwner.hasModifier(INNER_KEYWORD)) return;
if (modifierListOwner instanceof KtClass && !(modifierListOwner instanceof KtEnumEntry)) {
KtClass aClass = (KtClass) modifierListOwner;
boolean localEnumError = aClass.isLocal() && aClass.isEnum();
if (!localEnumError && isIllegalNestedClass(descriptor)) {
trace.report(NESTED_CLASS_NOT_ALLOWED.on(aClass));
}
}
}
@NotNull
public Map<KtModifierKeywordToken, PsiElement> getTokensCorrespondingToModifiers(
@NotNull KtModifierList modifierList,
@@ -2,7 +2,7 @@
class A {
inner class B {
companion <!NESTED_OBJECT_NOT_ALLOWED!>object<!> { }
companion <!NESTED_CLASS_NOT_ALLOWED(Companion object)!>object<!> { }
}
}
@@ -1,10 +1,10 @@
class A {
inner class I {
companion <!NESTED_OBJECT_NOT_ALLOWED!>object A<!>
companion <!NESTED_CLASS_NOT_ALLOWED(Companion object)!>object A<!>
<!MANY_COMPANION_OBJECTS!>companion<!> <!NESTED_OBJECT_NOT_ALLOWED!>object B<!>
<!MANY_COMPANION_OBJECTS!>companion<!> <!NESTED_CLASS_NOT_ALLOWED(Companion object)!>object B<!>
<!MANY_COMPANION_OBJECTS!>companion<!> <!NESTED_OBJECT_NOT_ALLOWED!>object C<!>
<!MANY_COMPANION_OBJECTS!>companion<!> <!NESTED_CLASS_NOT_ALLOWED(Companion object)!>object C<!>
}
}
@@ -0,0 +1,5 @@
class Outer {
inner class Inner {
annotation <!NESTED_CLASS_NOT_ALLOWED(Annotation class)!>class TestNestedAnnotation<!>
}
}
@@ -0,0 +1,22 @@
package
public final class Outer {
public constructor Outer()
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
public final inner class Inner {
public constructor Inner()
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
public final annotation class TestNestedAnnotation : kotlin.Annotation {
public constructor TestNestedAnnotation()
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
}
}
@@ -0,0 +1,5 @@
class Outer {
inner class Inner {
<!NESTED_CLASS_NOT_ALLOWED(Enum class)!>enum class TestNestedEnum<!>
}
}
@@ -0,0 +1,32 @@
package
public final class Outer {
public constructor Outer()
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
public final inner class Inner {
public constructor Inner()
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
public final enum class TestNestedEnum : kotlin.Enum<Outer.Inner.TestNestedEnum> {
private constructor TestNestedEnum()
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: Outer.Inner.TestNestedEnum): 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<Outer.Inner.TestNestedEnum!>!
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): Outer.Inner.TestNestedEnum
public final /*synthesized*/ fun values(): kotlin.Array<Outer.Inner.TestNestedEnum>
}
}
}
@@ -0,0 +1,19 @@
// LANGUAGE_VERSION: 1.1
enum class Enum {
ENTRY_WITH_CLASS {
<!WRONG_MODIFIER_CONTAINING_DECLARATION!>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>
}
@@ -0,0 +1,5 @@
class Outer {
inner class Inner {
<!NESTED_CLASS_NOT_ALLOWED(Interface)!>interface TestNestedInterface<!>
}
}
@@ -0,0 +1,21 @@
package
public final class Outer {
public constructor Outer()
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
public final inner class Inner {
public constructor Inner()
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
public interface TestNestedInterface {
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
}
}
+3 -3
View File
@@ -1,12 +1,12 @@
// SKIP_TXT
class Outer {
inner class Inner1 {
<!NESTED_OBJECT_NOT_ALLOWED!>object Obj1<!>
<!NESTED_CLASS_NOT_ALLOWED(Object)!>object Obj1<!>
companion <!NESTED_OBJECT_NOT_ALLOWED!>object Obj2<!>
companion <!NESTED_CLASS_NOT_ALLOWED(Companion object)!>object Obj2<!>
inner class Inner2 {
<!NESTED_OBJECT_NOT_ALLOWED!>object Obj3<!>
<!NESTED_CLASS_NOT_ALLOWED(Object)!>object Obj3<!>
}
}
}
@@ -61,7 +61,7 @@ enum class MyEnum {
class Outer {
inner class Inner {
<!NESTED_OBJECT_NOT_ALLOWED!>object C<!> {
<!NESTED_CLASS_NOT_ALLOWED(Object)!>object C<!> {
const val a = 18
}
}
@@ -11806,6 +11806,12 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/diagnostics/tests/inner"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true);
}
@TestMetadata("annotationInInnerClass.kt")
public void testAnnotationInInnerClass() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/inner/annotationInInnerClass.kt");
doTest(fileName);
}
@TestMetadata("classesInClassObjectHeader.kt")
public void testClassesInClassObjectHeader() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/inner/classesInClassObjectHeader.kt");
@@ -11830,6 +11836,12 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest {
doTest(fileName);
}
@TestMetadata("enumInInnerClass.kt")
public void testEnumInInnerClass() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/inner/enumInInnerClass.kt");
doTest(fileName);
}
@TestMetadata("extensionFun.kt")
public void testExtensionFun() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/inner/extensionFun.kt");
@@ -11848,6 +11860,12 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest {
doTest(fileName);
}
@TestMetadata("innerClassInEnumEntryClass_lv11.kt")
public void testInnerClassInEnumEntryClass_lv11() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/inner/innerClassInEnumEntryClass_lv11.kt");
doTest(fileName);
}
@TestMetadata("InnerClassNameClash.kt")
public void testInnerClassNameClash() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/inner/InnerClassNameClash.kt");
@@ -11890,6 +11908,12 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest {
doTest(fileName);
}
@TestMetadata("interfaceInInnerClass.kt")
public void testInterfaceInInnerClass() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/inner/interfaceInInnerClass.kt");
doTest(fileName);
}
@TestMetadata("kt5854.kt")
public void testKt5854() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/inner/kt5854.kt");
@@ -11806,6 +11806,12 @@ public class DiagnosticsUsingJavacTestGenerated extends AbstractDiagnosticsUsing
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/diagnostics/tests/inner"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true);
}
@TestMetadata("annotationInInnerClass.kt")
public void testAnnotationInInnerClass() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/inner/annotationInInnerClass.kt");
doTest(fileName);
}
@TestMetadata("classesInClassObjectHeader.kt")
public void testClassesInClassObjectHeader() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/inner/classesInClassObjectHeader.kt");
@@ -11830,6 +11836,12 @@ public class DiagnosticsUsingJavacTestGenerated extends AbstractDiagnosticsUsing
doTest(fileName);
}
@TestMetadata("enumInInnerClass.kt")
public void testEnumInInnerClass() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/inner/enumInInnerClass.kt");
doTest(fileName);
}
@TestMetadata("extensionFun.kt")
public void testExtensionFun() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/inner/extensionFun.kt");
@@ -11848,6 +11860,12 @@ public class DiagnosticsUsingJavacTestGenerated extends AbstractDiagnosticsUsing
doTest(fileName);
}
@TestMetadata("innerClassInEnumEntryClass_lv11.kt")
public void testInnerClassInEnumEntryClass_lv11() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/inner/innerClassInEnumEntryClass_lv11.kt");
doTest(fileName);
}
@TestMetadata("InnerClassNameClash.kt")
public void testInnerClassNameClash() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/inner/InnerClassNameClash.kt");
@@ -11890,6 +11908,12 @@ public class DiagnosticsUsingJavacTestGenerated extends AbstractDiagnosticsUsing
doTest(fileName);
}
@TestMetadata("interfaceInInnerClass.kt")
public void testInterfaceInInnerClass() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/inner/interfaceInInnerClass.kt");
doTest(fileName);
}
@TestMetadata("kt5854.kt")
public void testKt5854() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/inner/kt5854.kt");