Java Parser & stubs: recognize local record if it has type parameter, do not remap tokens until sure

GitOrigin-RevId: aab508cd6f840047ab950c0b428c1a1f18e0aa1c
This commit is contained in:
Roman.Ivanov
2020-02-05 06:09:40 +00:00
committed by intellij-monorepo-bot
parent 2ea07c424a
commit 573faaae2c
8 changed files with 174 additions and 17 deletions
@@ -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);
@@ -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);
}
@@ -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}''
@@ -0,0 +1,5 @@
class A {
void foo() {
record R<T>{};
}
}
@@ -0,0 +1,62 @@
PsiJavaFile:LocalRecordWithTypeParams.java
PsiImportList
<empty list>
PsiClass:A
PsiModifierList:
<empty list>
PsiKeyword:class('class')
PsiWhiteSpace(' ')
PsiIdentifier:A('A')
PsiTypeParameterList
<empty list>
PsiReferenceList
<empty list>
PsiReferenceList
<empty list>
PsiWhiteSpace(' ')
PsiJavaToken:LBRACE('{')
PsiWhiteSpace('\n ')
PsiMethod:foo
PsiModifierList:
<empty list>
PsiTypeParameterList
<empty list>
PsiTypeElement:void
PsiKeyword:void('void')
PsiWhiteSpace(' ')
PsiIdentifier:foo('foo')
PsiParameterList:()
PsiJavaToken:LPARENTH('(')
PsiJavaToken:RPARENTH(')')
PsiReferenceList
<empty list>
PsiWhiteSpace(' ')
PsiCodeBlock
PsiJavaToken:LBRACE('{')
PsiWhiteSpace('\n ')
PsiDeclarationStatement
PsiClass:R
PsiModifierList:
<empty list>
PsiKeyword:record('record')
PsiWhiteSpace(' ')
PsiIdentifier:R('R')
PsiTypeParameterList
PsiJavaToken:LT('<')
PsiTypeParameter:T
PsiIdentifier:T('T')
PsiReferenceList
<empty list>
PsiJavaToken:GT('>')
PsiReferenceList
<empty list>
PsiReferenceList
<empty list>
PsiJavaToken:LBRACE('{')
PsiJavaToken:RBRACE('}')
PsiEmptyStatement
PsiJavaToken:SEMICOLON(';')
PsiWhiteSpace('\n ')
PsiJavaToken:RBRACE('}')
PsiWhiteSpace('\n')
PsiJavaToken:RBRACE('}')
@@ -37,12 +37,10 @@ PsiJavaFile:LocalRecordWithoutParens.java
PsiDeclarationStatement
PsiModifierList:
<empty list>
PsiKeyword:record('record')
PsiIdentifier:record('record')
PsiWhiteSpace(' ')
PsiIdentifier:R('R')
PsiTypeParameterList
<empty list>
PsiErrorElement:'(' expected
PsiErrorElement:'<' or '(' expected
<empty list>
PsiWhiteSpace(' ')
PsiBlockStatement
@@ -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); }
@@ -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<A>" +
" }\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<String>(){}\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() {