refactor [java]: simpler modifier list parsing without Pair

Space-RevId: bdb7b5111b0b087c52afbd396b1001294238271f

GitOrigin-RevId: f871c5a1894d2e3618c9116409b41b82e682a3db
This commit is contained in:
Bas Leijdekkers
2026-01-23 15:26:39 +00:00
committed by intellij-monorepo-bot
parent fa8e49b309
commit 5d88b90720
7 changed files with 255 additions and 46 deletions
@@ -1,4 +1,4 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.java.parser.declarationParsing;
import com.intellij.java.parser.AbstractBasicJavaParsingTestCase;
@@ -102,4 +102,5 @@ public abstract class AbstractBasicClassParsingTest extends AbstractBasicJavaPar
public void testImplicitClassJavaDoc() { doTest(true); }
public void testIncompleteClass() { doTest(true); }
public void testIncorrectModifier() { doTest(true); }
}
@@ -1,4 +1,4 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.java.syntax.element
import com.intellij.java.syntax.element.JavaSyntaxTokenType
@@ -43,11 +43,6 @@ object SyntaxElementTypes {
JToken.VOLATILE_KEYWORD, JToken.DEFAULT_KEYWORD, JToken.SEALED_KEYWORD, JToken.NON_SEALED_KEYWORD, JToken.VALUE_KEYWORD
)
// While only 'final' modifier is acceptable in the (method or lambda) parameter modifier list, we still parse any modifier,
// except the soft ones, as they should be parsed as variable name in lambda parameter list.
val PARAMETER_MODIFIER_BIT_SET: SyntaxElementTypeSet =
MODIFIER_BIT_SET - setOf(JToken.SEALED_KEYWORD, JToken.NON_SEALED_KEYWORD, JToken.VALUE_KEYWORD)
val PRIMITIVE_TYPE_BIT_SET: SyntaxElementTypeSet = syntaxElementTypeSetOf(
JToken.BOOLEAN_KEYWORD, JToken.BYTE_KEYWORD, JToken.SHORT_KEYWORD, JToken.INT_KEYWORD, JToken.LONG_KEYWORD, JToken.CHAR_KEYWORD,
JToken.FLOAT_KEYWORD, JToken.DOUBLE_KEYWORD, JToken.VOID_KEYWORD
@@ -1,4 +1,4 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.java.syntax.parser
import com.intellij.java.syntax.JavaSyntaxBundle
@@ -8,7 +8,6 @@ import com.intellij.java.syntax.element.JavaSyntaxTokenType
import com.intellij.java.syntax.element.SyntaxElementTypes.CLASS_KEYWORD_BIT_SET
import com.intellij.java.syntax.element.SyntaxElementTypes.KEYWORD_BIT_SET
import com.intellij.java.syntax.element.SyntaxElementTypes.MODIFIER_BIT_SET
import com.intellij.java.syntax.element.SyntaxElementTypes.PARAMETER_MODIFIER_BIT_SET
import com.intellij.java.syntax.element.SyntaxElementTypes.PRIMITIVE_TYPE_BIT_SET
import com.intellij.platform.syntax.SyntaxElementType
import com.intellij.platform.syntax.SyntaxElementTypeSet
@@ -244,9 +243,7 @@ open class DeclarationParser(private val myParser: JavaParser) {
val declaration = builder.mark()
val declarationStart = builder.currentOffset
val modListInfo = parseModifierList(builder)
val modList = modListInfo.first
val modList = parseModifierList(builder)
if (builder.expect(JavaSyntaxTokenType.AT)) {
if (builder.tokenType === JavaSyntaxTokenType.INTERFACE_KEYWORD) {
@@ -356,7 +353,7 @@ open class DeclarationParser(private val myParser: JavaParser) {
if (!builder.expect(JavaSyntaxTokenType.IDENTIFIER)) {
if (context != Context.CODE_BLOCK ||
!modListInfo.second ||
modList.getStartOffset() != modList.getEndOffset() ||
(type.isPrimitive && builder.tokenType !== JavaSyntaxTokenType.DOT)
) {
typeParams?.precede()?.errorBefore(message("unexpected.token"), type.marker)
@@ -415,12 +412,8 @@ open class DeclarationParser(private val myParser: JavaParser) {
}
@JvmOverloads
fun parseModifierList(
builder: SyntaxTreeBuilder,
modifiers: SyntaxElementTypeSet = MODIFIER_BIT_SET
): Pair<SyntaxTreeBuilder.Marker, Boolean> {
fun parseModifierList(builder: SyntaxTreeBuilder, modifiers: SyntaxElementTypeSet = MODIFIER_BIT_SET): SyntaxTreeBuilder.Marker {
val modList = builder.mark()
var isEmpty = true
while (true) {
var tokenType = builder.tokenType ?: break
@@ -436,18 +429,15 @@ open class DeclarationParser(private val myParser: JavaParser) {
val nonSealed = builder.mark()
builder.advance(3)
nonSealed.collapse(JavaSyntaxTokenType.NON_SEALED_KEYWORD)
isEmpty = false
}
else if (modifiers.contains(tokenType)) {
builder.advanceLexer()
isEmpty = false
}
else if (tokenType === JavaSyntaxTokenType.AT) {
if (KEYWORD_BIT_SET.contains(builder.lookAhead(1))) {
break
}
parseAnnotation(builder)
isEmpty = false
}
else {
break
@@ -455,7 +445,7 @@ open class DeclarationParser(private val myParser: JavaParser) {
}
JavaParserUtil.done(modList, JavaSyntaxElementType.MODIFIER_LIST, languageLevel)
return modList to isEmpty
return modList
}
private fun parseMethodFromLeftParenth(
@@ -698,16 +688,13 @@ open class DeclarationParser(private val myParser: JavaParser) {
): SyntaxTreeBuilder.Marker? {
val param = builder.mark()
val modListInfo = parseModifierList(
builder, if (type === JavaSyntaxElementType.PARAMETER) PARAMETER_MODIFIER_BIT_SET else MODIFIER_BIT_SET)
val typeInfo: ReferenceParser.TypeInfo?
val modList: SyntaxTreeBuilder.Marker
if (typed) {
modList = parseModifierList(builder)
val flags = ReferenceParser.EAT_LAST_DOT or ReferenceParser.WILDCARD or typeFlags
typeInfo = myParser.referenceParser.parseTypeInfo(builder, flags)
if (typeInfo == null) {
if (modListInfo.second) {
if (myParser.referenceParser.parseTypeInfo(builder, flags) == null) {
if (modList.getStartOffset() == modList.getEndOffset()) {
param.rollbackTo()
return null
}
@@ -716,13 +703,9 @@ open class DeclarationParser(private val myParser: JavaParser) {
JavaParserUtil.emptyElement(builder, JavaSyntaxElementType.TYPE)
}
}
}
if (typed) {
val tokenType = builder.tokenType
if (tokenType === JavaSyntaxTokenType.THIS_KEYWORD ||
tokenType === JavaSyntaxTokenType.IDENTIFIER && builder.lookAhead(1) === JavaSyntaxTokenType.DOT
) {
tokenType === JavaSyntaxTokenType.IDENTIFIER && builder.lookAhead(1) === JavaSyntaxTokenType.DOT) {
val mark = builder.mark()
val expr = myParser.expressionParser.parse(builder)
@@ -735,6 +718,10 @@ open class DeclarationParser(private val myParser: JavaParser) {
mark.rollbackTo()
}
}
else {
modList = builder.mark() // modifier list is empty for lambda expression with untyped parameters
modList.done(JavaSyntaxElementType.MODIFIER_LIST)
}
if (builder.expect(JavaSyntaxTokenType.IDENTIFIER)) {
if (type === JavaSyntaxElementType.PARAMETER || type === JavaSyntaxElementType.RECORD_COMPONENT) {
@@ -746,13 +733,11 @@ open class DeclarationParser(private val myParser: JavaParser) {
else {
JavaParserUtil.error(builder, message("expected.identifier"))
param.drop()
return modListInfo.first
return modList
}
if (JavaParserUtil.expectOrError(builder, JavaSyntaxTokenType.EQ, "expected.eq")) {
if (myParser.expressionParser.parse(builder) == null) {
JavaParserUtil.error(builder, message("expected.expression"))
}
if (JavaParserUtil.expectOrError(builder, JavaSyntaxTokenType.EQ, "expected.eq") && myParser.expressionParser.parse(builder) == null) {
JavaParserUtil.error(builder, message("expected.expression"))
}
JavaParserUtil.done(param, JavaSyntaxElementType.RESOURCE_VARIABLE, languageLevel)
@@ -847,10 +832,7 @@ open class DeclarationParser(private val myParser: JavaParser) {
return declaration
}
private fun eatBrackets(
builder: SyntaxTreeBuilder,
errorKey: @PropertyKey(resourceBundle = JavaSyntaxBundle.BUNDLE) String?
): Boolean {
private fun eatBrackets(builder: SyntaxTreeBuilder, errorKey: @PropertyKey(resourceBundle = JavaSyntaxBundle.BUNDLE) String?): Boolean {
val tokenType = builder.tokenType
if (tokenType !== JavaSyntaxTokenType.LBRACKET && tokenType !== JavaSyntaxTokenType.AT) return true
@@ -1,4 +1,4 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.java.syntax.parser
import com.intellij.java.syntax.JavaSyntaxBundle.message
@@ -42,7 +42,8 @@ class PatternParser(private val myParser: JavaParser) {
fun preParsePattern(builder: SyntaxTreeBuilder): SyntaxTreeBuilder.Marker? {
val patternStart = builder.mark()
val hasNoModifier = myParser.declarationParser.parseModifierList(builder, PATTERN_MODIFIERS).second
val modList = myParser.declarationParser.parseModifierList(builder, PATTERN_MODIFIERS)
val hasNoModifier = modList.getStartOffset() == modList.getEndOffset()
val type = myParser.referenceParser.parseType(builder, ReferenceParser.EAT_LAST_DOT or ReferenceParser.WILDCARD)
val isPattern = type != null &&
(builder.tokenType === JavaSyntaxTokenType.IDENTIFIER || (builder.tokenType === JavaSyntaxTokenType.LPARENTH && hasNoModifier))
@@ -109,7 +110,8 @@ class PatternParser(private val myParser: JavaParser) {
private fun parseTypeOrRecordPattern(builder: SyntaxTreeBuilder, expectVar: Boolean): SyntaxTreeBuilder.Marker {
val pattern = builder.mark()
val patternVariable = builder.mark()
val hasNoModifiers = myParser.declarationParser.parseModifierList(builder, PATTERN_MODIFIERS).second
val modList = myParser.declarationParser.parseModifierList(builder, PATTERN_MODIFIERS)
val hasNoModifiers = modList.getStartOffset() == modList.getEndOffset()
var flags = ReferenceParser.EAT_LAST_DOT or ReferenceParser.WILDCARD
if (expectVar) {
@@ -0,0 +1,7 @@
class IncorrectModifier {
X x = (sealed var y) -> System.out.println()
interface X {
void x(String s);
}
}
@@ -0,0 +1,111 @@
PsiJavaFile:IncorrectModifier.java
PsiImportList
<empty list>
PsiClass:IncorrectModifier
PsiModifierList:
<empty list>
PsiKeyword:class('class')
PsiWhiteSpace(' ')
PsiIdentifier:IncorrectModifier('IncorrectModifier')
PsiTypeParameterList
<empty list>
PsiReferenceList
<empty list>
PsiReferenceList
<empty list>
PsiWhiteSpace(' ')
PsiJavaToken:LBRACE('{')
PsiWhiteSpace('\n ')
PsiField:x
PsiModifierList:
<empty list>
PsiTypeElement:X
PsiJavaCodeReferenceElement:X
PsiIdentifier:X('X')
PsiReferenceParameterList
<empty list>
PsiWhiteSpace(' ')
PsiIdentifier:x('x')
PsiWhiteSpace(' ')
PsiJavaToken:EQ('=')
PsiWhiteSpace(' ')
PsiLambdaExpression
PsiParameterList:(sealed var y)
PsiJavaToken:LPARENTH('(')
PsiParameter:y
PsiModifierList:sealed
PsiKeyword:sealed('sealed')
PsiWhiteSpace(' ')
PsiTypeElement:var
PsiKeyword:var('var')
PsiWhiteSpace(' ')
PsiIdentifier:y('y')
PsiJavaToken:RPARENTH(')')
PsiWhiteSpace(' ')
PsiJavaToken:ARROW('->')
PsiWhiteSpace(' ')
PsiMethodCallExpression:System.out.println()
PsiReferenceExpression:System.out.println
PsiReferenceExpression:System.out
PsiReferenceExpression:System
PsiReferenceParameterList
<empty list>
PsiIdentifier:System('System')
PsiJavaToken:DOT('.')
PsiReferenceParameterList
<empty list>
PsiIdentifier:out('out')
PsiJavaToken:DOT('.')
PsiReferenceParameterList
<empty list>
PsiIdentifier:println('println')
PsiExpressionList
PsiJavaToken:LPARENTH('(')
PsiJavaToken:RPARENTH(')')
PsiErrorElement:';' expected
<empty list>
PsiWhiteSpace(' \n \n ')
PsiClass:X
PsiModifierList:
<empty list>
PsiKeyword:interface('interface')
PsiWhiteSpace(' ')
PsiIdentifier:X('X')
PsiTypeParameterList
<empty list>
PsiReferenceList
<empty list>
PsiReferenceList
<empty list>
PsiWhiteSpace(' ')
PsiJavaToken:LBRACE('{')
PsiWhiteSpace('\n ')
PsiMethod:x
PsiModifierList:
<empty list>
PsiTypeParameterList
<empty list>
PsiTypeElement:void
PsiKeyword:void('void')
PsiWhiteSpace(' ')
PsiIdentifier:x('x')
PsiParameterList:(String s)
PsiJavaToken:LPARENTH('(')
PsiParameter:s
PsiModifierList:
<empty list>
PsiTypeElement:String
PsiJavaCodeReferenceElement:String
PsiIdentifier:String('String')
PsiReferenceParameterList
<empty list>
PsiWhiteSpace(' ')
PsiIdentifier:s('s')
PsiJavaToken:RPARENTH(')')
PsiReferenceList
<empty list>
PsiJavaToken:SEMICOLON(';')
PsiWhiteSpace('\n ')
PsiJavaToken:RBRACE('}')
PsiWhiteSpace('\n')
PsiJavaToken:RBRACE('}')
@@ -0,0 +1,111 @@
java.FILE
IMPORT_LIST
<empty list>
CLASS
MODIFIER_LIST
<empty list>
CLASS_KEYWORD
WHITE_SPACE
IDENTIFIER
TYPE_PARAMETER_LIST
<empty list>
EXTENDS_LIST
<empty list>
IMPLEMENTS_LIST
<empty list>
WHITE_SPACE
LBRACE
WHITE_SPACE
FIELD
MODIFIER_LIST
<empty list>
TYPE
JAVA_CODE_REFERENCE
IDENTIFIER
REFERENCE_PARAMETER_LIST
<empty list>
WHITE_SPACE
IDENTIFIER
WHITE_SPACE
EQ
WHITE_SPACE
LAMBDA_EXPRESSION
PARAMETER_LIST
LPARENTH
PARAMETER
MODIFIER_LIST
SEALED
WHITE_SPACE
TYPE
VAR
WHITE_SPACE
IDENTIFIER
RPARENTH
WHITE_SPACE
ARROW
WHITE_SPACE
METHOD_CALL_EXPRESSION
REFERENCE_EXPRESSION
REFERENCE_EXPRESSION
REFERENCE_EXPRESSION
REFERENCE_PARAMETER_LIST
<empty list>
IDENTIFIER
DOT
REFERENCE_PARAMETER_LIST
<empty list>
IDENTIFIER
DOT
REFERENCE_PARAMETER_LIST
<empty list>
IDENTIFIER
EXPRESSION_LIST
LPARENTH
RPARENTH
ERROR_ELEMENT
<empty list>
WHITE_SPACE
CLASS
MODIFIER_LIST
<empty list>
INTERFACE_KEYWORD
WHITE_SPACE
IDENTIFIER
TYPE_PARAMETER_LIST
<empty list>
EXTENDS_LIST
<empty list>
IMPLEMENTS_LIST
<empty list>
WHITE_SPACE
LBRACE
WHITE_SPACE
METHOD
MODIFIER_LIST
<empty list>
TYPE_PARAMETER_LIST
<empty list>
TYPE
VOID_KEYWORD
WHITE_SPACE
IDENTIFIER
PARAMETER_LIST
LPARENTH
PARAMETER
MODIFIER_LIST
<empty list>
TYPE
JAVA_CODE_REFERENCE
IDENTIFIER
REFERENCE_PARAMETER_LIST
<empty list>
WHITE_SPACE
IDENTIFIER
RPARENTH
THROWS_LIST
<empty list>
SEMICOLON
WHITE_SPACE
RBRACE
WHITE_SPACE
RBRACE