PY-49990 Parse a match statement without a subject as a type declaration

GitOrigin-RevId: 5c6dc8257973dd0f22a5ef8dd5883628205689a7
This commit is contained in:
Mikhail Golubev
2021-07-29 18:55:43 +03:00
committed by intellij-monorepo-bot
parent 9664a32ba2
commit 0db66c81a5
10 changed files with 90 additions and 24 deletions

View File

@@ -147,8 +147,8 @@ public class StatementParsing extends Parsing implements ITokenTypeRemapper {
SyntaxTreeBuilder.Marker mark = myBuilder.mark();
myBuilder.remapCurrentToken(PyTokenTypes.MATCH_KEYWORD);
myBuilder.advanceLexer();
myContext.getExpressionParser().parseExpression();
if (!matchToken(PyTokenTypes.COLON)) {
boolean followedBySubject = myContext.getExpressionParser().parseExpressionOptional();
if (!followedBySubject || !matchToken(PyTokenTypes.COLON)) {
mark.rollbackTo();
myBuilder.remapCurrentToken(PyTokenTypes.IDENTIFIER);
return false;

View File

@@ -29,10 +29,6 @@ public final class PyMatchStatementFixer extends PyFixer<PyStatement> {
if (matchStatement != null) {
PsiElement colon = PyPsiUtils.getFirstChildOfType(element, PyTokenTypes.COLON);
assert colon != null;
if (matchStatement.getSubject() == null) {
processor.registerUnresolvedError(colon.getTextOffset());
return;
}
int colonEndOffset = colon.getTextRange().getEndOffset();
// It's not enough to check matchStatement.getCaseClauses().isEmpty()
boolean hasEmptyBody = colonEndOffset == matchStatement.getTextRange().getEndOffset();
@@ -46,7 +42,17 @@ public final class PyMatchStatementFixer extends PyFixer<PyStatement> {
return;
}
Couple<PsiElement> pair = findMatchKeywordAndSubject(element);
PyTypeDeclarationStatement typeDeclaration = as(element, PyTypeDeclarationStatement.class);
// "match:" case
if (typeDeclaration != null && isMatchIdentifier(typeDeclaration.getTarget())) {
PyAnnotation annotation = typeDeclaration.getAnnotation();
if (annotation != null && annotation.getValue() == null) {
processor.registerUnresolvedError(annotation.getTextRange().getStartOffset());
return;
}
}
Couple<PsiElement> pair = findMatchKeywordAndSubjectInExpressionStatement(element);
PsiElement matchKeyword = pair.getFirst();
if (matchKeyword == null) {
return;
@@ -68,7 +74,7 @@ public final class PyMatchStatementFixer extends PyFixer<PyStatement> {
}
@NotNull
private static Couple<PsiElement> findMatchKeywordAndSubject(@NotNull PyStatement statement) {
private static Couple<PsiElement> findMatchKeywordAndSubjectInExpressionStatement(@NotNull PyStatement statement) {
if (!(statement instanceof PyExpressionStatement)) return Couple.getEmpty();
// "match <caret>expr" case
PsiElement prevSibling = PyPsiUtils.getPrevNonWhitespaceSiblingOnSameLine(statement);

View File

@@ -0,0 +1,14 @@
PyFile:PatternMatchingAnnotatedAssignmentLooksLikeIncompleteMatchStatement.py
PyAssignmentStatement
PyTargetExpression: match
PsiElement(Py:IDENTIFIER)('match')
PyAnnotation
PsiElement(Py:COLON)(':')
PsiWhiteSpace(' ')
PyReferenceExpression: case
PsiElement(Py:IDENTIFIER)('case')
PsiWhiteSpace(' ')
PsiElement(Py:EQ)('=')
PsiWhiteSpace(' ')
PyNumericLiteralExpression
PsiElement(Py:INTEGER_LITERAL)('42')

View File

@@ -0,0 +1,8 @@
PyFile:PatternMatchingRecoveryMatchWithColonParsedAsVariableTypeDeclaration.py
PyTypeDeclarationStatement
PyTargetExpression: match
PsiElement(Py:IDENTIFIER)('match')
PyAnnotation
PsiElement(Py:COLON)(':')
PsiErrorElement:Expression expected
<empty list>

View File

@@ -1,18 +1,29 @@
PyFile:PatternMatchingRecoveryNoSubjectAfterMatch.py
PyMatchStatement
PsiElement(Py:MATCH_KEYWORD)('match')
PsiErrorElement:Expression expected
<empty list>
PsiElement(Py:COLON)(':')
PsiWhiteSpace('\n ')
PyCaseClause
PsiElement(Py:CASE_KEYWORD)('case')
PsiWhiteSpace(' ')
PyLiteralPattern
PyNumericLiteralExpression
PsiElement(Py:INTEGER_LITERAL)('1')
PyTypeDeclarationStatement
PyTargetExpression: match
PsiElement(Py:IDENTIFIER)('match')
PyAnnotation
PsiElement(Py:COLON)(':')
PsiWhiteSpace('\n ')
PyStatementList
PyPassStatement
PsiElement(Py:PASS_KEYWORD)('pass')
PsiErrorElement:Expression expected
<empty list>
PsiWhiteSpace('\n ')
PsiErrorElement:Unexpected indent
<empty list>
PyExpressionStatement
PyReferenceExpression: case
PsiElement(Py:IDENTIFIER)('case')
PsiErrorElement:End of statement expected
<empty list>
PsiWhiteSpace(' ')
PyTypeDeclarationStatement
PyNumericLiteralExpression
PsiElement(Py:INTEGER_LITERAL)('1')
PyAnnotation
PsiElement(Py:COLON)(':')
PsiErrorElement:Expression expected
<empty list>
PsiWhiteSpace('\n ')
PsiErrorElement:Unexpected indent
<empty list>
PyPassStatement
PsiElement(Py:PASS_KEYWORD)('pass')

View File

@@ -0,0 +1,9 @@
PyFile:PatternMatchingVariableTypeDeclarationLooksLikeIncompleteMatchStatement.py
PyTypeDeclarationStatement
PyTargetExpression: match
PsiElement(Py:IDENTIFIER)('match')
PyAnnotation
PsiElement(Py:COLON)(':')
PsiWhiteSpace(' ')
PyReferenceExpression: case
PsiElement(Py:IDENTIFIER)('case')

View File

@@ -1171,6 +1171,21 @@ public class PythonParsingTest extends ParsingTestCase {
doTest(LanguageLevel.PYTHON310);
}
// PY-49990
public void testPatternMatchingVariableTypeDeclarationLooksLikeIncompleteMatchStatement() {
doTest(LanguageLevel.PYTHON310);
}
// PY-49990
public void testPatternMatchingAnnotatedAssignmentLooksLikeIncompleteMatchStatement() {
doTest(LanguageLevel.PYTHON310);
}
// PY-49990
public void testPatternMatchingRecoveryMatchWithColonParsedAsVariableTypeDeclaration() {
doTest(LanguageLevel.PYTHON310);
}
public void doTest() {
doTest(LanguageLevel.PYTHON26);
}