diff --git a/Grammar/python.gram b/Grammar/python.gram index b9ecd2273a5cae..88b6b200e254d8 100644 --- a/Grammar/python.gram +++ b/Grammar/python.gram @@ -1476,6 +1476,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 { diff --git a/Lib/test/test_syntax.py b/Lib/test/test_syntax.py index e334f48179ec84..9a0b62644156f5 100644 --- a/Lib/test/test_syntax.py +++ b/Lib/test/test_syntax.py @@ -382,6 +382,42 @@ 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 index 00000000000000..93510a9ceaf3c8 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-09-13-01-23-25.gh-issue-138857.YQ5gdc.rst @@ -0,0 +1,2 @@ +Improve :exc:`SyntaxError` message for ``case`` keyword placed outside +:keyword:`match` body. diff --git a/Parser/parser.c b/Parser/parser.c index 8242c4dfabba35..e47ebf94bad88b 100644 --- a/Parser/parser.c +++ b/Parser/parser.c @@ -25130,6 +25130,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) { @@ -25207,6 +25208,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--;