PY-49785 Properly recover from "case" keyword in the middle of a match statement

Not stopping at a statement break token and continuing recovery until a colon,
we considered the subsequent well-formed case clause to be a part of an error
message about a missing pattern, thus, moving the caret to its colon.

GitOrigin-RevId: f4ee0e12876960e989de3dee89925b65e3cf2339
This commit is contained in:
Mikhail Golubev
2021-07-19 16:29:15 +00:00
committed by intellij-monorepo-bot
parent edc62cbf0f
commit 20097f7a69
11 changed files with 106 additions and 1 deletions
@@ -191,7 +191,7 @@ public class StatementParsing extends Parsing implements ITokenTypeRemapper {
myBuilder.advanceLexer();
if (!getPatternParser().parseCasePattern()) {
SyntaxTreeBuilder.Marker patternError = myBuilder.mark();
while (!myBuilder.eof() && !atAnyOfTokens(PyTokenTypes.IF_KEYWORD, PyTokenTypes.COLON)) {
while (!myBuilder.eof() && !atAnyOfTokens(PyTokenTypes.IF_KEYWORD, PyTokenTypes.COLON, PyTokenTypes.STATEMENT_BREAK)) {
nextToken();
}
patternError.error(PyPsiBundle.message("PARSE.expected.pattern"));
@@ -0,0 +1,7 @@
match x:
case 1:
pass
case
case 3:
pass
case<caret>
@@ -0,0 +1,7 @@
match x:
case 1:
pass
case <caret>:
case 3:
pass
case :
@@ -0,0 +1,6 @@
match x:
case 1:
pass
case<caret>
case 3:
pass
@@ -0,0 +1,6 @@
match x:
case 1:
pass
case <caret>:
case 3:
pass
@@ -0,0 +1,6 @@
match x:
case 1:
pass
case 2<caret>
case 3:
pass
@@ -0,0 +1,7 @@
match x:
case 1:
pass
case 2:
<caret>
case 3:
pass
@@ -0,0 +1,6 @@
match x:
case True:
pass
case
case True:
pass
@@ -0,0 +1,41 @@
PyFile:PatternMatchingRecoveryNoPatternAfterCaseInIntermediateCaseClause.py
PyMatchStatement
PsiElement(Py:MATCH_KEYWORD)('match')
PsiWhiteSpace(' ')
PyReferenceExpression: x
PsiElement(Py:IDENTIFIER)('x')
PsiElement(Py:COLON)(':')
PsiWhiteSpace('\n ')
PyCaseClause
PsiElement(Py:CASE_KEYWORD)('case')
PsiWhiteSpace(' ')
PyLiteralPattern
PyBoolLiteralExpression
PsiElement(Py:TRUE_KEYWORD)('True')
PsiElement(Py:COLON)(':')
PsiWhiteSpace('\n ')
PyStatementList
PyPassStatement
PsiElement(Py:PASS_KEYWORD)('pass')
PsiWhiteSpace('\n ')
PyCaseClause
PsiElement(Py:CASE_KEYWORD)('case')
PsiErrorElement:Pattern expected
<empty list>
PsiErrorElement:':' expected
<empty list>
PsiWhiteSpace('\n ')
PyStatementList
PsiErrorElement:Indent expected
<empty list>
PyCaseClause
PsiElement(Py:CASE_KEYWORD)('case')
PsiWhiteSpace(' ')
PyLiteralPattern
PyBoolLiteralExpression
PsiElement(Py:TRUE_KEYWORD)('True')
PsiElement(Py:COLON)(':')
PsiWhiteSpace('\n ')
PyStatementList
PyPassStatement
PsiElement(Py:PASS_KEYWORD)('pass')
@@ -464,4 +464,19 @@ public class PySmartEnterTest extends PyTestCase {
public void testColonAfterCaseClauseWithoutPatternWithGuardCondition() {
doTest();
}
// PY-49785
public void testColonAfterIntermediateCaseClause() {
doTest();
}
// PY-49785
public void testColonAndIndentAfterIntermediateCaseClause() {
doTest();
}
// PY-49785
public void testColonAfterFinalCaseClauseWithPrecedingIncompleteCaseClause() {
doTest();
}
}
@@ -994,6 +994,10 @@ public class PythonParsingTest extends ParsingTestCase {
doTest(LanguageLevel.PYTHON310);
}
public void testPatternMatchingRecoveryNoPatternAfterCaseInIntermediateCaseClause() {
doTest(LanguageLevel.PYTHON310);
}
public void testPatternMatchingRecoveryIllegalStatementsInsideMatch() {
doTest(LanguageLevel.PYTHON310);
}