From 5437a0e47875146cdf5de52a49be357ea4d2a325 Mon Sep 17 00:00:00 2001 From: Denis Zharkov Date: Thu, 20 Dec 2018 10:50:36 +0300 Subject: [PATCH] Add recovery for enum declaration without 'class' keyword ^KT-13703 Fixed --- .../kotlin/parsing/KotlinParsing.java | 37 +++-- .../tests/modifiers/IllegalModifiers.kt | 4 +- .../tests/modifiers/IllegalModifiers.txt | 18 ++- .../psi/EnumWithAnnotationKeyword.txt | 25 ++-- .../testData/psi/recovery/enumWthoutClass.kt | 18 +++ .../testData/psi/recovery/enumWthoutClass.txt | 137 ++++++++++++++++++ .../kotlin/parsing/ParsingTestGenerated.java | 5 + 7 files changed, 215 insertions(+), 29 deletions(-) create mode 100644 compiler/testData/psi/recovery/enumWthoutClass.kt create mode 100644 compiler/testData/psi/recovery/enumWthoutClass.txt diff --git a/compiler/psi/src/org/jetbrains/kotlin/parsing/KotlinParsing.java b/compiler/psi/src/org/jetbrains/kotlin/parsing/KotlinParsing.java index ebd0630ae55..c06700c483f 100644 --- a/compiler/psi/src/org/jetbrains/kotlin/parsing/KotlinParsing.java +++ b/compiler/psi/src/org/jetbrains/kotlin/parsing/KotlinParsing.java @@ -441,7 +441,7 @@ public class KotlinParsing extends AbstractKotlinParsing { IElementType keywordToken = tt(); if (keywordToken == CLASS_KEYWORD || keywordToken == INTERFACE_KEYWORD) { - return parseClass(detector.isEnumDetected()); + return parseClass(detector.isEnumDetected(), true); } else if (keywordToken == FUN_KEYWORD) { return parseFunction(); @@ -455,6 +455,8 @@ public class KotlinParsing extends AbstractKotlinParsing { else if (keywordToken == OBJECT_KEYWORD) { parseObject(nameParsingModeForObject, true); return OBJECT_DECLARATION; + } else if (keywordToken == IDENTIFIER && detector.isEnumDetected() && declarationParsingMode.canBeEnumUsedAsSoftKeyword) { + return parseClass(true, false); } return null; @@ -825,15 +827,22 @@ public class KotlinParsing extends AbstractKotlinParsing { boolean object, NameParsingMode nameParsingMode, boolean optionalBody, - boolean enumClass + boolean enumClass, + boolean expectKindKeyword ) { - if (object) { - assert _at(OBJECT_KEYWORD); + if (expectKindKeyword) { + if (object) { + assert _at(OBJECT_KEYWORD); + } + else { + assert _atSet(CLASS_KEYWORD, INTERFACE_KEYWORD); + } + advance(); // CLASS_KEYWORD, INTERFACE_KEYWORD or OBJECT_KEYWORD } else { - assert _atSet(CLASS_KEYWORD, INTERFACE_KEYWORD); + assert enumClass : "Currently classifiers without class/interface/object are only allowed for enums"; + error("'class' keyword is expected after 'enum'"); } - advance(); // CLASS_KEYWORD, INTERFACE_KEYWORD or OBJECT_KEYWORD if (nameParsingMode == NameParsingMode.REQUIRED) { expect(IDENTIFIER, "Name expected", CLASS_NAME_RECOVERY_SET); @@ -918,12 +927,12 @@ public class KotlinParsing extends AbstractKotlinParsing { return object ? OBJECT_DECLARATION : CLASS; } - private IElementType parseClass(boolean enumClass) { - return parseClassOrObject(false, NameParsingMode.REQUIRED, true, enumClass); + private IElementType parseClass(boolean enumClass, boolean expectKindKeyword) { + return parseClassOrObject(false, NameParsingMode.REQUIRED, true, enumClass, expectKindKeyword); } void parseObject(NameParsingMode nameParsingMode, boolean optionalBody) { - parseClassOrObject(true, nameParsingMode, optionalBody, false); + parseClassOrObject(true, nameParsingMode, optionalBody, false, true); } /* @@ -1244,16 +1253,18 @@ public class KotlinParsing extends AbstractKotlinParsing { } enum DeclarationParsingMode { - MEMBER_OR_TOPLEVEL(false, true), - LOCAL(true, false), - SCRIPT_TOPLEVEL(true, true); + MEMBER_OR_TOPLEVEL(false, true, true), + LOCAL(true, false, false), + SCRIPT_TOPLEVEL(true, true, false); public final boolean destructuringAllowed; public final boolean accessorsAllowed; + public final boolean canBeEnumUsedAsSoftKeyword; - DeclarationParsingMode(boolean destructuringAllowed, boolean accessorsAllowed) { + DeclarationParsingMode(boolean destructuringAllowed, boolean accessorsAllowed, boolean canBeEnumUsedAsSoftKeyword) { this.destructuringAllowed = destructuringAllowed; this.accessorsAllowed = accessorsAllowed; + this.canBeEnumUsedAsSoftKeyword = canBeEnumUsedAsSoftKeyword; } } diff --git a/compiler/testData/diagnostics/tests/modifiers/IllegalModifiers.kt b/compiler/testData/diagnostics/tests/modifiers/IllegalModifiers.kt index bffac287aa9..5318cfdfede 100644 --- a/compiler/testData/diagnostics/tests/modifiers/IllegalModifiers.kt +++ b/compiler/testData/diagnostics/tests/modifiers/IllegalModifiers.kt @@ -119,7 +119,7 @@ class IllegalModifiers7() { // Secondary constructors class IllegalModifiers8 { abstract - enum + enum open inner annotation @@ -129,7 +129,7 @@ class IllegalModifiers8 { final vararg reified - const + const constructor() {} constructor(private enum abstract x: Int) {} diff --git a/compiler/testData/diagnostics/tests/modifiers/IllegalModifiers.txt b/compiler/testData/diagnostics/tests/modifiers/IllegalModifiers.txt index 2915bfd788c..7c5153ea00c 100644 --- a/compiler/testData/diagnostics/tests/modifiers/IllegalModifiers.txt +++ b/compiler/testData/diagnostics/tests/modifiers/IllegalModifiers.txt @@ -108,11 +108,27 @@ package illegal_modifiers { } public final class IllegalModifiers8 { - public constructor IllegalModifiers8() public constructor IllegalModifiers8(/*0*/ x: kotlin.Int) 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 abstract enum class constructor : kotlin.Enum { + private constructor constructor() + 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: illegal_modifiers.IllegalModifiers8.constructor): 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): illegal_modifiers.IllegalModifiers8.constructor + public final /*synthesized*/ fun values(): kotlin.Array + } } public final class IllegalModifiers9 { diff --git a/compiler/testData/psi/EnumWithAnnotationKeyword.txt b/compiler/testData/psi/EnumWithAnnotationKeyword.txt index 84aa9116be8..577e705ca69 100644 --- a/compiler/testData/psi/EnumWithAnnotationKeyword.txt +++ b/compiler/testData/psi/EnumWithAnnotationKeyword.txt @@ -23,21 +23,20 @@ KtFile: EnumWithAnnotationKeyword.kt PsiWhiteSpace('\n') PsiElement(RBRACE)('}') PsiWhiteSpace('\n\n') - MODIFIER_LIST - PsiElement(enum)('enum') - PsiWhiteSpace(' ') - PsiElement(annotation)('annotation') - PsiWhiteSpace(' ') - PsiErrorElement:Expecting a top level declaration - PsiElement(IDENTIFIER)('E1') - PsiWhiteSpace(' ') - FUN - PsiErrorElement:Expecting a top level declaration + CLASS + MODIFIER_LIST + PsiElement(enum)('enum') + PsiWhiteSpace(' ') + PsiElement(annotation)('annotation') + PsiErrorElement:'class' keyword is expected after 'enum' - BLOCK + PsiWhiteSpace(' ') + PsiElement(IDENTIFIER)('E1') + PsiWhiteSpace(' ') + CLASS_BODY PsiElement(LBRACE)('{') PsiWhiteSpace('\n ') - REFERENCE_EXPRESSION + ENUM_ENTRY PsiElement(IDENTIFIER)('D') PsiWhiteSpace('\n') - PsiElement(RBRACE)('}') \ No newline at end of file + PsiElement(RBRACE)('}') diff --git a/compiler/testData/psi/recovery/enumWthoutClass.kt b/compiler/testData/psi/recovery/enumWthoutClass.kt new file mode 100644 index 00000000000..0c4a6218139 --- /dev/null +++ b/compiler/testData/psi/recovery/enumWthoutClass.kt @@ -0,0 +1,18 @@ +enum A { X, Y } + +private enum B { + X, Y +} + +internal Q { + X, Y +} + +fun foo() { + // No recovery here + enum A { X, Y } + + private enum B { + X, Y + } +} diff --git a/compiler/testData/psi/recovery/enumWthoutClass.txt b/compiler/testData/psi/recovery/enumWthoutClass.txt new file mode 100644 index 00000000000..e256c50a81e --- /dev/null +++ b/compiler/testData/psi/recovery/enumWthoutClass.txt @@ -0,0 +1,137 @@ +KtFile: enumWthoutClass.kt + PACKAGE_DIRECTIVE + + IMPORT_LIST + + CLASS + MODIFIER_LIST + PsiElement(enum)('enum') + PsiErrorElement:'class' keyword is expected after 'enum' + + PsiWhiteSpace(' ') + PsiElement(IDENTIFIER)('A') + PsiWhiteSpace(' ') + CLASS_BODY + PsiElement(LBRACE)('{') + PsiWhiteSpace(' ') + ENUM_ENTRY + PsiElement(IDENTIFIER)('X') + PsiElement(COMMA)(',') + PsiWhiteSpace(' ') + ENUM_ENTRY + PsiElement(IDENTIFIER)('Y') + PsiWhiteSpace(' ') + PsiElement(RBRACE)('}') + PsiWhiteSpace('\n\n') + CLASS + MODIFIER_LIST + PsiElement(private)('private') + PsiWhiteSpace(' ') + PsiElement(enum)('enum') + PsiErrorElement:'class' keyword is expected after 'enum' + + PsiWhiteSpace(' ') + PsiElement(IDENTIFIER)('B') + PsiWhiteSpace(' ') + CLASS_BODY + PsiElement(LBRACE)('{') + PsiWhiteSpace('\n ') + ENUM_ENTRY + PsiElement(IDENTIFIER)('X') + PsiElement(COMMA)(',') + PsiWhiteSpace(' ') + ENUM_ENTRY + PsiElement(IDENTIFIER)('Y') + PsiWhiteSpace('\n') + PsiElement(RBRACE)('}') + PsiWhiteSpace('\n\n') + MODIFIER_LIST + PsiElement(internal)('internal') + PsiWhiteSpace(' ') + PsiErrorElement:Expecting a top level declaration + PsiElement(IDENTIFIER)('Q') + PsiWhiteSpace(' ') + FUN + PsiErrorElement:Expecting a top level declaration + + BLOCK + PsiElement(LBRACE)('{') + PsiWhiteSpace('\n ') + REFERENCE_EXPRESSION + PsiElement(IDENTIFIER)('X') + PsiErrorElement:Unexpected tokens (use ';' to separate expressions on the same line) + PsiElement(COMMA)(',') + PsiWhiteSpace(' ') + PsiElement(IDENTIFIER)('Y') + PsiWhiteSpace('\n') + PsiElement(RBRACE)('}') + PsiWhiteSpace('\n\n') + FUN + PsiElement(fun)('fun') + PsiWhiteSpace(' ') + PsiElement(IDENTIFIER)('foo') + VALUE_PARAMETER_LIST + PsiElement(LPAR)('(') + PsiElement(RPAR)(')') + PsiWhiteSpace(' ') + BLOCK + PsiElement(LBRACE)('{') + PsiWhiteSpace('\n ') + PsiComment(EOL_COMMENT)('// No recovery here') + PsiWhiteSpace('\n ') + BINARY_EXPRESSION + REFERENCE_EXPRESSION + PsiElement(IDENTIFIER)('enum') + PsiWhiteSpace(' ') + OPERATION_REFERENCE + PsiElement(IDENTIFIER)('A') + PsiWhiteSpace(' ') + LAMBDA_EXPRESSION + FUNCTION_LITERAL + PsiElement(LBRACE)('{') + PsiWhiteSpace(' ') + VALUE_PARAMETER_LIST + VALUE_PARAMETER + PsiElement(IDENTIFIER)('X') + PsiElement(COMMA)(',') + PsiWhiteSpace(' ') + VALUE_PARAMETER + PsiElement(IDENTIFIER)('Y') + PsiErrorElement:Expecting '->' or ',' + + PsiWhiteSpace(' ') + BLOCK + + PsiElement(RBRACE)('}') + PsiWhiteSpace('\n\n ') + BINARY_EXPRESSION + REFERENCE_EXPRESSION + PsiElement(IDENTIFIER)('private') + PsiWhiteSpace(' ') + OPERATION_REFERENCE + PsiElement(IDENTIFIER)('enum') + PsiWhiteSpace(' ') + CALL_EXPRESSION + REFERENCE_EXPRESSION + PsiElement(IDENTIFIER)('B') + PsiWhiteSpace(' ') + LAMBDA_ARGUMENT + LAMBDA_EXPRESSION + FUNCTION_LITERAL + PsiElement(LBRACE)('{') + PsiWhiteSpace('\n ') + VALUE_PARAMETER_LIST + VALUE_PARAMETER + PsiElement(IDENTIFIER)('X') + PsiElement(COMMA)(',') + PsiWhiteSpace(' ') + VALUE_PARAMETER + PsiElement(IDENTIFIER)('Y') + PsiErrorElement:Expecting '->' or ',' + + PsiWhiteSpace('\n ') + BLOCK + + PsiElement(RBRACE)('}') + PsiWhiteSpace('\n') + PsiElement(RBRACE)('}') \ No newline at end of file diff --git a/compiler/tests/org/jetbrains/kotlin/parsing/ParsingTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/parsing/ParsingTestGenerated.java index d7faf543dcc..a5bb676d7d8 100644 --- a/compiler/tests/org/jetbrains/kotlin/parsing/ParsingTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/parsing/ParsingTestGenerated.java @@ -1933,6 +1933,11 @@ public class ParsingTestGenerated extends AbstractParsingTest { runTest("compiler/testData/psi/recovery/DoWhileWithoutLPar.kt"); } + @TestMetadata("enumWthoutClass.kt") + public void testEnumWthoutClass() throws Exception { + runTest("compiler/testData/psi/recovery/enumWthoutClass.kt"); + } + @TestMetadata("ForEmptyParentheses.kt") public void testForEmptyParentheses() throws Exception { runTest("compiler/testData/psi/recovery/ForEmptyParentheses.kt");