]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-138857: Improve error message for `case` outside of `match` (#138858)
authorsobolevn <mail@sobolevn.me>
Fri, 24 Oct 2025 09:20:54 +0000 (12:20 +0300)
committerGitHub <noreply@github.com>
Fri, 24 Oct 2025 09:20:54 +0000 (11:20 +0200)
* gh-138857: Improve error message for `case` outside of `match`

---------

Co-authored-by: Bartosz Sławecki <bartosz@ilikepython.com>
Grammar/python.gram
Lib/test/test_syntax.py
Misc/NEWS.d/next/Core_and_Builtins/2025-09-13-01-23-25.gh-issue-138857.YQ5gdc.rst [new file with mode: 0644]
Parser/parser.c

index 1aa544d9ef42fe48eb140449cad00a49686c6831..0824b9c658b51a219f3472583361ffdb70db0f73 100644 (file)
@@ -1477,6 +1477,10 @@ invalid_match_stmt:
     | "match" subject_expr NEWLINE { CHECK_VERSION(void*, 10, "Pattern matching is", RAISE_SYNTAX_ERROR("expected ':'") ) }
     | a="match" subject=subject_expr ':' NEWLINE !INDENT {
         RAISE_INDENTATION_ERROR("expected an indented block after 'match' statement on line %d", a->lineno) }
+    | a="case" patterns guard? b=':' block {
+        RAISE_SYNTAX_ERROR_KNOWN_RANGE(
+            a, b,
+            "case statement must be inside match statement") }
 invalid_case_block:
     | "case" patterns guard? NEWLINE { RAISE_SYNTAX_ERROR("expected ':'") }
     | a="case" patterns guard? ':' NEWLINE !INDENT {
index ecb9e077137fdc06b80fe6ec62f71904b0c861a0..3ae23a6c289d3f86a7842d39916978d86481e201 100644 (file)
@@ -376,6 +376,42 @@ SyntaxError: invalid syntax
 Traceback (most recent call last):
 SyntaxError: invalid syntax
 
+# Check incorrect "case" placement with specialized error messages
+
+>>> case "pattern": ...
+Traceback (most recent call last):
+SyntaxError: case statement must be inside match statement
+
+>>> case 1 | 2: ...
+Traceback (most recent call last):
+SyntaxError: case statement must be inside match statement
+
+>>> case klass(attr=1) | {}: ...
+Traceback (most recent call last):
+SyntaxError: case statement must be inside match statement
+
+>>> case [] if x > 1: ...
+Traceback (most recent call last):
+SyntaxError: case statement must be inside match statement
+
+>>> case match: ...
+Traceback (most recent call last):
+SyntaxError: case statement must be inside match statement
+
+>>> case case: ...
+Traceback (most recent call last):
+SyntaxError: case statement must be inside match statement
+
+>>> if some:
+...     case 1: ...
+Traceback (most recent call last):
+SyntaxError: case statement must be inside match statement
+
+>>> case some:
+...     case 1: ...
+Traceback (most recent call last):
+SyntaxError: case statement must be inside match statement
+
 # But prefixes of soft keywords should
 # still raise specialized errors
 
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-09-13-01-23-25.gh-issue-138857.YQ5gdc.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-09-13-01-23-25.gh-issue-138857.YQ5gdc.rst
new file mode 100644 (file)
index 0000000..93510a9
--- /dev/null
@@ -0,0 +1,2 @@
+Improve :exc:`SyntaxError` message for ``case`` keyword placed outside
+:keyword:`match` body.
index 7a3102bb1419cf0c51b71fc06f3cf26de89143c4..145e30fffaeb1667382a4554e784526bb44262f6 100644 (file)
@@ -25154,6 +25154,7 @@ invalid_except_star_stmt_indent_rule(Parser *p)
 // invalid_match_stmt:
 //     | "match" subject_expr NEWLINE
 //     | "match" subject_expr ':' NEWLINE !INDENT
+//     | "case" patterns guard? ':' block
 static void *
 invalid_match_stmt_rule(Parser *p)
 {
@@ -25231,6 +25232,43 @@ invalid_match_stmt_rule(Parser *p)
         D(fprintf(stderr, "%*c%s invalid_match_stmt[%d-%d]: %s failed!\n", p->level, ' ',
                   p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "\"match\" subject_expr ':' NEWLINE !INDENT"));
     }
+    { // "case" patterns guard? ':' block
+        if (p->error_indicator) {
+            p->level--;
+            return NULL;
+        }
+        D(fprintf(stderr, "%*c> invalid_match_stmt[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "\"case\" patterns guard? ':' block"));
+        void *_opt_var;
+        UNUSED(_opt_var); // Silence compiler warnings
+        expr_ty a;
+        Token * b;
+        asdl_stmt_seq* block_var;
+        pattern_ty patterns_var;
+        if (
+            (a = _PyPegen_expect_soft_keyword(p, "case"))  // soft_keyword='"case"'
+            &&
+            (patterns_var = patterns_rule(p))  // patterns
+            &&
+            (_opt_var = guard_rule(p), !p->error_indicator)  // guard?
+            &&
+            (b = _PyPegen_expect_token(p, 11))  // token=':'
+            &&
+            (block_var = block_rule(p))  // block
+        )
+        {
+            D(fprintf(stderr, "%*c+ invalid_match_stmt[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "\"case\" patterns guard? ':' block"));
+            _res = RAISE_SYNTAX_ERROR_KNOWN_RANGE ( a , b , "case statement must be inside match statement" );
+            if (_res == NULL && PyErr_Occurred()) {
+                p->error_indicator = 1;
+                p->level--;
+                return NULL;
+            }
+            goto done;
+        }
+        p->mark = _mark;
+        D(fprintf(stderr, "%*c%s invalid_match_stmt[%d-%d]: %s failed!\n", p->level, ' ',
+                  p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "\"case\" patterns guard? ':' block"));
+    }
     _res = NULL;
   done:
     p->level--;