From 573faaae2c68ab4008304481244a0a35a4876c73 Mon Sep 17 00:00:00 2001 From: "Roman.Ivanov" Date: Wed, 5 Feb 2020 10:50:37 +0700 Subject: [PATCH] Java Parser & stubs: recognize local record if it has type parameter, do not remap tokens until sure GitOrigin-RevId: aab508cd6f840047ab950c0b428c1a1f18e0aa1c --- .../lang/java/parser/DeclarationParser.java | 25 +++--- .../psi/impl/source/JavaLightStubBuilder.java | 8 +- .../src/messages/JavaErrorBundle.properties | 1 + .../class/LocalRecordWithTypeParams.java | 5 ++ .../class/LocalRecordWithTypeParams.txt | 62 ++++++++++++++ .../class/LocalRecordWithoutParens.txt | 6 +- .../declarationParsing/ClassParsingTest.java | 1 + .../java/psi/JavaStubBuilderTest.java | 83 ++++++++++++++++++- 8 files changed, 174 insertions(+), 17 deletions(-) create mode 100644 java/java-tests/testData/psi/parser-full/declarationParsing/class/LocalRecordWithTypeParams.java create mode 100644 java/java-tests/testData/psi/parser-full/declarationParsing/class/LocalRecordWithTypeParams.txt diff --git a/java/java-psi-impl/src/com/intellij/lang/java/parser/DeclarationParser.java b/java/java-psi-impl/src/com/intellij/lang/java/parser/DeclarationParser.java index bfe5a5ee6926..162c2c8964be 100644 --- a/java/java-psi-impl/src/com/intellij/lang/java/parser/DeclarationParser.java +++ b/java/java-psi-impl/src/com/intellij/lang/java/parser/DeclarationParser.java @@ -64,15 +64,25 @@ public class DeclarationParser { @Nullable private PsiBuilder.Marker parseClassFromKeyword(PsiBuilder builder, PsiBuilder.Marker declaration, boolean isAnnotation, Context context) { IElementType keywordTokenType = builder.getTokenType(); - boolean isRecord = isRecordToken(builder, keywordTokenType); + final boolean isRecord = isRecordToken(builder, keywordTokenType); if (isRecord) { - if (builder.lookAhead(1) == JavaTokenType.IDENTIFIER) { - builder.remapCurrentToken(JavaTokenType.RECORD_KEYWORD); - keywordTokenType = JavaTokenType.RECORD_KEYWORD; - } else { + if (builder.lookAhead(1) != JavaTokenType.IDENTIFIER) { declaration.drop(); return null; } + final IElementType afterIdent = builder.lookAhead(2); + // No parser recovery for local records without < or ( to support for light stubs + // (look at com.intellij.psi.impl.source.JavaLightStubBuilder.CodeBlockVisitor.visit) + if (context == Context.CODE_BLOCK && afterIdent != JavaTokenType.LPARENTH && afterIdent != JavaTokenType.LT) { + // skipping record kw and identifier + builder.advanceLexer(); + builder.advanceLexer(); + error(builder, JavaErrorBundle.message("expected.lt.or.lparen")); + declaration.drop(); + return null; + } + builder.remapCurrentToken(JavaTokenType.RECORD_KEYWORD); + keywordTokenType = JavaTokenType.RECORD_KEYWORD; } assert ElementType.CLASS_KEYWORD_BIT_SET.contains(keywordTokenType) : keywordTokenType; builder.advanceLexer(); @@ -90,11 +100,6 @@ public class DeclarationParser { if (builder.getTokenType() == JavaTokenType.LPARENTH) { parseElementList(builder, ListType.RECORD_COMPONENTS); } - else if (isRecord && context == Context.CODE_BLOCK) { - error(builder, JavaErrorBundle.message("expected.lparen")); - declaration.drop(); - return null; - } refParser.parseReferenceList(builder, JavaTokenType.EXTENDS_KEYWORD, JavaElementType.EXTENDS_LIST, JavaTokenType.COMMA); refParser.parseReferenceList(builder, JavaTokenType.IMPLEMENTS_KEYWORD, JavaElementType.IMPLEMENTS_LIST, JavaTokenType.COMMA); diff --git a/java/java-psi-impl/src/com/intellij/psi/impl/source/JavaLightStubBuilder.java b/java/java-psi-impl/src/com/intellij/psi/impl/source/JavaLightStubBuilder.java index 17546a758cb0..114a8dd335d6 100644 --- a/java/java-psi-impl/src/com/intellij/psi/impl/source/JavaLightStubBuilder.java +++ b/java/java-psi-impl/src/com/intellij/psi/impl/source/JavaLightStubBuilder.java @@ -157,8 +157,12 @@ public class JavaLightStubBuilder extends LightStubBuilder { return (result = false); } // if record is inside lazy parseable element, tokens are not remapped and record token is still identifier - // This token combination may be "record RecordName (" - else if (preLast == JavaTokenType.IDENTIFIER && last == JavaTokenType.IDENTIFIER && type == JavaTokenType.LPARENTH) { + // This token combination may be "record RecordName (" or "record RecordName<..." + // Local records without < or ( won't be parsed + else if (preLast == JavaTokenType.IDENTIFIER && + last == JavaTokenType.IDENTIFIER && + (type == JavaTokenType.LPARENTH || type == JavaTokenType.LT) + ) { return (result = false); } diff --git a/java/java-psi-impl/src/messages/JavaErrorBundle.properties b/java/java-psi-impl/src/messages/JavaErrorBundle.properties index 00df0d0646ad..741d18308736 100644 --- a/java/java-psi-impl/src/messages/JavaErrorBundle.properties +++ b/java/java-psi-impl/src/messages/JavaErrorBundle.properties @@ -394,6 +394,7 @@ expected.module.declaration=Module declaration expected expected.module.statement=Module directive expected expected.switch.label='case', 'default' or '}' expected expected.switch.rule=Expression, block or throw statement expected +expected.lt.or.lparen='<' or '(' expected error.cannot.resolve.class=Cannot resolve class ''{0}'' error.cannot.resolve.class.or.package=Cannot resolve class or package ''{0}'' diff --git a/java/java-tests/testData/psi/parser-full/declarationParsing/class/LocalRecordWithTypeParams.java b/java/java-tests/testData/psi/parser-full/declarationParsing/class/LocalRecordWithTypeParams.java new file mode 100644 index 000000000000..4bd51ae0ef9c --- /dev/null +++ b/java/java-tests/testData/psi/parser-full/declarationParsing/class/LocalRecordWithTypeParams.java @@ -0,0 +1,5 @@ +class A { + void foo() { + record R{}; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/psi/parser-full/declarationParsing/class/LocalRecordWithTypeParams.txt b/java/java-tests/testData/psi/parser-full/declarationParsing/class/LocalRecordWithTypeParams.txt new file mode 100644 index 000000000000..0a9ebf5c0509 --- /dev/null +++ b/java/java-tests/testData/psi/parser-full/declarationParsing/class/LocalRecordWithTypeParams.txt @@ -0,0 +1,62 @@ +PsiJavaFile:LocalRecordWithTypeParams.java + PsiImportList + + PsiClass:A + PsiModifierList: + + PsiKeyword:class('class') + PsiWhiteSpace(' ') + PsiIdentifier:A('A') + PsiTypeParameterList + + PsiReferenceList + + PsiReferenceList + + PsiWhiteSpace(' ') + PsiJavaToken:LBRACE('{') + PsiWhiteSpace('\n ') + PsiMethod:foo + PsiModifierList: + + PsiTypeParameterList + + PsiTypeElement:void + PsiKeyword:void('void') + PsiWhiteSpace(' ') + PsiIdentifier:foo('foo') + PsiParameterList:() + PsiJavaToken:LPARENTH('(') + PsiJavaToken:RPARENTH(')') + PsiReferenceList + + PsiWhiteSpace(' ') + PsiCodeBlock + PsiJavaToken:LBRACE('{') + PsiWhiteSpace('\n ') + PsiDeclarationStatement + PsiClass:R + PsiModifierList: + + PsiKeyword:record('record') + PsiWhiteSpace(' ') + PsiIdentifier:R('R') + PsiTypeParameterList + PsiJavaToken:LT('<') + PsiTypeParameter:T + PsiIdentifier:T('T') + PsiReferenceList + + PsiJavaToken:GT('>') + PsiReferenceList + + PsiReferenceList + + PsiJavaToken:LBRACE('{') + PsiJavaToken:RBRACE('}') + PsiEmptyStatement + PsiJavaToken:SEMICOLON(';') + PsiWhiteSpace('\n ') + PsiJavaToken:RBRACE('}') + PsiWhiteSpace('\n') + PsiJavaToken:RBRACE('}') \ No newline at end of file diff --git a/java/java-tests/testData/psi/parser-full/declarationParsing/class/LocalRecordWithoutParens.txt b/java/java-tests/testData/psi/parser-full/declarationParsing/class/LocalRecordWithoutParens.txt index d53751d9c6ce..5a908f5d19f8 100644 --- a/java/java-tests/testData/psi/parser-full/declarationParsing/class/LocalRecordWithoutParens.txt +++ b/java/java-tests/testData/psi/parser-full/declarationParsing/class/LocalRecordWithoutParens.txt @@ -37,12 +37,10 @@ PsiJavaFile:LocalRecordWithoutParens.java PsiDeclarationStatement PsiModifierList: - PsiKeyword:record('record') + PsiIdentifier:record('record') PsiWhiteSpace(' ') PsiIdentifier:R('R') - PsiTypeParameterList - - PsiErrorElement:'(' expected + PsiErrorElement:'<' or '(' expected PsiWhiteSpace(' ') PsiBlockStatement diff --git a/java/java-tests/testSrc/com/intellij/java/parser/declarationParsing/ClassParsingTest.java b/java/java-tests/testSrc/com/intellij/java/parser/declarationParsing/ClassParsingTest.java index 03841aecc717..809d473868f3 100644 --- a/java/java-tests/testSrc/com/intellij/java/parser/declarationParsing/ClassParsingTest.java +++ b/java/java-tests/testSrc/com/intellij/java/parser/declarationParsing/ClassParsingTest.java @@ -53,6 +53,7 @@ public class ClassParsingTest extends JavaParsingTestCase { public void testRecordWithModifiers() { doTest(true); } public void testRecordInCodeBlock() { doTest(true); } public void testLocalRecord() { doTest(true); } + public void testLocalRecordWithTypeParams() { doTest(true); } public void testLocalRecordWithoutParens() { doTest(true); } public void testCompactConstructor0() { doTest(true); } public void testCompactConstructor1() { doTest(true); } diff --git a/java/java-tests/testSrc/com/intellij/java/psi/JavaStubBuilderTest.java b/java/java-tests/testSrc/com/intellij/java/psi/JavaStubBuilderTest.java index e77dbd15b28c..5dc1d5f475e4 100644 --- a/java/java-tests/testSrc/com/intellij/java/psi/JavaStubBuilderTest.java +++ b/java/java-tests/testSrc/com/intellij/java/psi/JavaStubBuilderTest.java @@ -590,8 +590,89 @@ public class JavaStubBuilderTest extends LightIdeaTestCase { " MODIFIER_LIST:PsiModifierListStub[mask=0]\n" + " TYPE_PARAMETER_LIST:PsiTypeParameterListStub\n" + " PARAMETER_LIST:PsiParameterListStub\n" + + " THROWS_LIST:PsiRefListStub[THROWS_LIST:]\n"); + } + + public void testLocalRecordLikeIncompleteCode() { + doTest("class A {\n" + + " void foo(){\n" + + " record turn getTitle();\n" + + " }\n" + + "}", + + "PsiJavaFileStub []\n" + + " IMPORT_LIST:PsiImportListStub\n" + + " CLASS:PsiClassStub[name=A fqn=A]\n" + + " MODIFIER_LIST:PsiModifierListStub[mask=0]\n" + + " TYPE_PARAMETER_LIST:PsiTypeParameterListStub\n" + + " EXTENDS_LIST:PsiRefListStub[EXTENDS_LIST:]\n" + + " IMPLEMENTS_LIST:PsiRefListStub[IMPLEMENTS_LIST:]\n" + + " METHOD:PsiMethodStub[foo:void]\n" + + " MODIFIER_LIST:PsiModifierListStub[mask=0]\n" + + " TYPE_PARAMETER_LIST:PsiTypeParameterListStub\n" + + " PARAMETER_LIST:PsiParameterListStub\n" + + " THROWS_LIST:PsiRefListStub[THROWS_LIST:]\n"); + } + + + public void testLocalRecordLikeIncompleteCodeWithTypeParameters() { + doTest("class A {\n" + + " void foo(){\n" + + " record turn" + + " }\n" + + "}", + + "PsiJavaFileStub []\n" + + " IMPORT_LIST:PsiImportListStub\n" + + " CLASS:PsiClassStub[name=A fqn=A]\n" + + " MODIFIER_LIST:PsiModifierListStub[mask=0]\n" + + " TYPE_PARAMETER_LIST:PsiTypeParameterListStub\n" + + " EXTENDS_LIST:PsiRefListStub[EXTENDS_LIST:]\n" + + " IMPLEMENTS_LIST:PsiRefListStub[IMPLEMENTS_LIST:]\n" + + " METHOD:PsiMethodStub[foo:void]\n" + + " MODIFIER_LIST:PsiModifierListStub[mask=0]\n" + + " TYPE_PARAMETER_LIST:PsiTypeParameterListStub\n" + + " PARAMETER_LIST:PsiParameterListStub\n" + " THROWS_LIST:PsiRefListStub[THROW" + - "S_LIST:]\n"); + "S_LIST:]\n" + + " CLASS:PsiClassStub[record name=turn fqn=null]\n" + + " MODIFIER_LIST:PsiModifierListStub[mask=0]\n" + + " TYPE_PARAMETER_LIST:PsiTypeParameterListStub\n" + + " TYPE_PARAMETER:PsiTypeParameter[A]\n" + + " EXTENDS_BOUND_LIST:PsiRefListStub[EXTENDS_BOUNDS_LIST:]\n" + + " EXTENDS_LIST:PsiRefListStub[EXTENDS_LIST:]\n" + + " IMPLEMENTS_LIST:PsiRefListStub[IMPLEMENTS_LIST:]\n"); + } + + public void testLocalRecordWithTypeParameters() { + doTest("class A {\n" + + " void foo(){\n" + + " record R(){}\n" + + " " + + "}\n" + + "}", + + "PsiJavaFileStub []\n" + + " IMPORT_LIST:PsiImportListStub\n" + + " CLASS:PsiClassStub[name=A fqn=A]\n" + + " MODIFIER_LIST:PsiModifierListStub[mask=0]\n" + + " TYPE_PARAMETER_LIST:PsiTypeParameterListStub\n" + + " EXTENDS_LIST:PsiRefListStub[EXTENDS_LIST:]\n" + + " IMPLEMENTS_LIST:PsiRefListStub[IMPLEMENTS_LIST:]\n" + + " METHOD:PsiMethodStub[foo:void]\n" + + " MODIFIER_LIST:PsiModifierListStub[mask=0]\n" + + " TYPE_PARAMETER_LIST:PsiTypeParameterListStub\n" + + " PARAMETER_LIST:PsiParameterListStub\n" + + " THROWS_LIST:PsiRefListStub[THROW" + + "S_LIST:]\n" + + " CLASS:PsiClassStub[record name=R fqn=null]\n" + + " MODIFIER_LIST:PsiModifierListStub[mask=0]\n" + + " TYPE_PARAMETER_LIST:PsiTypeParameterListStub\n" + + " TYPE_PARAMETER:PsiTypeParameter[String]\n" + + " EXTENDS_BOUND_LIST:PsiRefListStub[EXTENDS_BOUNDS_LIST:]\n" + + " RECORD_HEADER:PsiRecordHeaderStub\n" + + " EXTENDS_LIST:PsiRefListStub[EXTENDS_LIST:]\n" + + " IMPLEMENTS_LIST:PsiRefListStub[IMPLEMENTS_LIST:]\n"); } public void testSOEProof() {