From 20097f7a691704dd1677ee047ea3947c6eb67c8e Mon Sep 17 00:00:00 2001 From: Mikhail Golubev Date: Sun, 18 Jul 2021 19:12:21 +0300 Subject: [PATCH] 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 --- .../python/parsing/StatementParsing.java | 2 +- ...ClauseWithPrecedingIncompleteCaseClause.py | 7 ++++ ...WithPrecedingIncompleteCaseClause_after.py | 7 ++++ .../colonAfterIntermediateCaseClause.py | 6 +++ .../colonAfterIntermediateCaseClause_after.py | 6 +++ ...lonAndIndentAfterIntermediateCaseClause.py | 6 +++ ...IndentAfterIntermediateCaseClause_after.py | 7 ++++ ...atternAfterCaseInIntermediateCaseClause.py | 6 +++ ...tternAfterCaseInIntermediateCaseClause.txt | 41 +++++++++++++++++++ .../jetbrains/python/PySmartEnterTest.java | 15 +++++++ .../python/parsing/PythonParsingTest.java | 4 ++ 11 files changed, 106 insertions(+), 1 deletion(-) create mode 100644 python/testData/codeInsight/smartEnter/colonAfterFinalCaseClauseWithPrecedingIncompleteCaseClause.py create mode 100644 python/testData/codeInsight/smartEnter/colonAfterFinalCaseClauseWithPrecedingIncompleteCaseClause_after.py create mode 100644 python/testData/codeInsight/smartEnter/colonAfterIntermediateCaseClause.py create mode 100644 python/testData/codeInsight/smartEnter/colonAfterIntermediateCaseClause_after.py create mode 100644 python/testData/codeInsight/smartEnter/colonAndIndentAfterIntermediateCaseClause.py create mode 100644 python/testData/codeInsight/smartEnter/colonAndIndentAfterIntermediateCaseClause_after.py create mode 100644 python/testData/psi/PatternMatchingRecoveryNoPatternAfterCaseInIntermediateCaseClause.py create mode 100644 python/testData/psi/PatternMatchingRecoveryNoPatternAfterCaseInIntermediateCaseClause.txt diff --git a/python/python-psi-impl/src/com/jetbrains/python/parsing/StatementParsing.java b/python/python-psi-impl/src/com/jetbrains/python/parsing/StatementParsing.java index 23bc91f605db..f7ecb28c3014 100644 --- a/python/python-psi-impl/src/com/jetbrains/python/parsing/StatementParsing.java +++ b/python/python-psi-impl/src/com/jetbrains/python/parsing/StatementParsing.java @@ -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")); diff --git a/python/testData/codeInsight/smartEnter/colonAfterFinalCaseClauseWithPrecedingIncompleteCaseClause.py b/python/testData/codeInsight/smartEnter/colonAfterFinalCaseClauseWithPrecedingIncompleteCaseClause.py new file mode 100644 index 000000000000..ff245238744d --- /dev/null +++ b/python/testData/codeInsight/smartEnter/colonAfterFinalCaseClauseWithPrecedingIncompleteCaseClause.py @@ -0,0 +1,7 @@ +match x: + case 1: + pass + case + case 3: + pass + case \ No newline at end of file diff --git a/python/testData/codeInsight/smartEnter/colonAfterFinalCaseClauseWithPrecedingIncompleteCaseClause_after.py b/python/testData/codeInsight/smartEnter/colonAfterFinalCaseClauseWithPrecedingIncompleteCaseClause_after.py new file mode 100644 index 000000000000..8340aafac28b --- /dev/null +++ b/python/testData/codeInsight/smartEnter/colonAfterFinalCaseClauseWithPrecedingIncompleteCaseClause_after.py @@ -0,0 +1,7 @@ +match x: + case 1: + pass + case : + case 3: + pass + case : \ No newline at end of file diff --git a/python/testData/codeInsight/smartEnter/colonAfterIntermediateCaseClause.py b/python/testData/codeInsight/smartEnter/colonAfterIntermediateCaseClause.py new file mode 100644 index 000000000000..ec7050d4f2ed --- /dev/null +++ b/python/testData/codeInsight/smartEnter/colonAfterIntermediateCaseClause.py @@ -0,0 +1,6 @@ +match x: + case 1: + pass + case + case 3: + pass \ No newline at end of file diff --git a/python/testData/codeInsight/smartEnter/colonAfterIntermediateCaseClause_after.py b/python/testData/codeInsight/smartEnter/colonAfterIntermediateCaseClause_after.py new file mode 100644 index 000000000000..e4be1c81232c --- /dev/null +++ b/python/testData/codeInsight/smartEnter/colonAfterIntermediateCaseClause_after.py @@ -0,0 +1,6 @@ +match x: + case 1: + pass + case : + case 3: + pass \ No newline at end of file diff --git a/python/testData/codeInsight/smartEnter/colonAndIndentAfterIntermediateCaseClause.py b/python/testData/codeInsight/smartEnter/colonAndIndentAfterIntermediateCaseClause.py new file mode 100644 index 000000000000..883eef844ff7 --- /dev/null +++ b/python/testData/codeInsight/smartEnter/colonAndIndentAfterIntermediateCaseClause.py @@ -0,0 +1,6 @@ +match x: + case 1: + pass + case 2 + case 3: + pass \ No newline at end of file diff --git a/python/testData/codeInsight/smartEnter/colonAndIndentAfterIntermediateCaseClause_after.py b/python/testData/codeInsight/smartEnter/colonAndIndentAfterIntermediateCaseClause_after.py new file mode 100644 index 000000000000..254e9801347e --- /dev/null +++ b/python/testData/codeInsight/smartEnter/colonAndIndentAfterIntermediateCaseClause_after.py @@ -0,0 +1,7 @@ +match x: + case 1: + pass + case 2: + + case 3: + pass \ No newline at end of file diff --git a/python/testData/psi/PatternMatchingRecoveryNoPatternAfterCaseInIntermediateCaseClause.py b/python/testData/psi/PatternMatchingRecoveryNoPatternAfterCaseInIntermediateCaseClause.py new file mode 100644 index 000000000000..39df7ec45c51 --- /dev/null +++ b/python/testData/psi/PatternMatchingRecoveryNoPatternAfterCaseInIntermediateCaseClause.py @@ -0,0 +1,6 @@ +match x: + case True: + pass + case + case True: + pass \ No newline at end of file diff --git a/python/testData/psi/PatternMatchingRecoveryNoPatternAfterCaseInIntermediateCaseClause.txt b/python/testData/psi/PatternMatchingRecoveryNoPatternAfterCaseInIntermediateCaseClause.txt new file mode 100644 index 000000000000..dea77fbacf40 --- /dev/null +++ b/python/testData/psi/PatternMatchingRecoveryNoPatternAfterCaseInIntermediateCaseClause.txt @@ -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 + + PsiErrorElement:':' expected + + PsiWhiteSpace('\n ') + PyStatementList + PsiErrorElement:Indent expected + + 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') \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/PySmartEnterTest.java b/python/testSrc/com/jetbrains/python/PySmartEnterTest.java index 86d8788233b3..c0fb8d76af41 100644 --- a/python/testSrc/com/jetbrains/python/PySmartEnterTest.java +++ b/python/testSrc/com/jetbrains/python/PySmartEnterTest.java @@ -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(); + } } diff --git a/python/testSrc/com/jetbrains/python/parsing/PythonParsingTest.java b/python/testSrc/com/jetbrains/python/parsing/PythonParsingTest.java index 48520bdea5d9..0402cd76b468 100644 --- a/python/testSrc/com/jetbrains/python/parsing/PythonParsingTest.java +++ b/python/testSrc/com/jetbrains/python/parsing/PythonParsingTest.java @@ -994,6 +994,10 @@ public class PythonParsingTest extends ParsingTestCase { doTest(LanguageLevel.PYTHON310); } + public void testPatternMatchingRecoveryNoPatternAfterCaseInIntermediateCaseClause() { + doTest(LanguageLevel.PYTHON310); + } + public void testPatternMatchingRecoveryIllegalStatementsInsideMatch() { doTest(LanguageLevel.PYTHON310); }