Skip to content

Commit ae1732d

Browse files
bpo-44180: Fix edge cases in invalid assigment rules in the parser (GH-26283)
The invalid assignment rules are very delicate since the parser can easily raise an invalid assignment when a keyword argument is provided. As they are very deep into the grammar tree, is very difficult to specify in which contexts these rules can be used and in which don't. For that, we need to use a different version of the rule that doesn't do error checking in those situations where we don't want the rule to raise (keyword arguments and generator expressions). We also need to check if we are in left-recursive rule, as those can try to eagerly advance the parser even if the parse will fail at the end of the expression. Failing to do this allows the parser to start parsing a call as a tuple and incorrectly identify a keyword argument as an invalid assignment, before it realizes that it was not a tuple after all. (cherry picked from commit c878a97) Co-authored-by: Pablo Galindo <[email protected]>
1 parent 150bc1f commit ae1732d

File tree

6 files changed

+1476
-1213
lines changed

6 files changed

+1476
-1213
lines changed

Grammar/python.gram

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -509,13 +509,13 @@ star_named_expression[expr_ty]:
509509
| '*' a=bitwise_or { _PyAST_Starred(a, Load, EXTRA) }
510510
| named_expression
511511

512-
named_expression[expr_ty]:
513-
| a=NAME ':=' ~ b=expression { _PyAST_NamedExpr(CHECK(expr_ty, _PyPegen_set_expr_context(p, a, Store)), b, EXTRA) }
514-
| invalid_named_expression
515-
| expression !':='
516512

517-
direct_named_expression[expr_ty]:
513+
assigment_expression[expr_ty]:
518514
| a=NAME ':=' ~ b=expression { _PyAST_NamedExpr(CHECK(expr_ty, _PyPegen_set_expr_context(p, a, Store)), b, EXTRA) }
515+
516+
named_expression[expr_ty]:
517+
| assigment_expression
518+
| invalid_named_expression
519519
| expression !':='
520520

521521
annotated_rhs[expr_ty]: yield_expr | star_expressions
@@ -706,7 +706,7 @@ group[expr_ty]:
706706
| '(' a=(yield_expr | named_expression) ')' { a }
707707
| invalid_group
708708
genexp[expr_ty]:
709-
| '(' a=direct_named_expression b=for_if_clauses ')' { _PyAST_GeneratorExp(a, b, EXTRA) }
709+
| '(' a=( assigment_expression | expression !':=') b=for_if_clauses ')' { _PyAST_GeneratorExp(a, b, EXTRA) }
710710
| invalid_comprehension
711711
set[expr_ty]: '{' a=star_named_expressions '}' { _PyAST_Set(a, EXTRA) }
712712
setcomp[expr_ty]:
@@ -745,27 +745,29 @@ arguments[expr_ty] (memo):
745745
| a=args [','] &')' { a }
746746
| invalid_arguments
747747
args[expr_ty]:
748-
| a[asdl_expr_seq*]=','.(starred_expression | direct_named_expression !'=')+ b=[',' k=kwargs {k}] { _PyPegen_collect_call_seqs(p, a, b, EXTRA) }
748+
| a[asdl_expr_seq*]=','.(starred_expression | ( assigment_expression | expression !':=') !'=')+ b=[',' k=kwargs {k}] {
749+
_PyPegen_collect_call_seqs(p, a, b, EXTRA) }
749750
| a=kwargs { _PyAST_Call(_PyPegen_dummy_name(p),
750751
CHECK_NULL_ALLOWED(asdl_expr_seq*, _PyPegen_seq_extract_starred_exprs(p, a)),
751752
CHECK_NULL_ALLOWED(asdl_keyword_seq*, _PyPegen_seq_delete_starred_exprs(p, a)),
752753
EXTRA) }
754+
753755
kwargs[asdl_seq*]:
754756
| a=','.kwarg_or_starred+ ',' b=','.kwarg_or_double_starred+ { _PyPegen_join_sequences(p, a, b) }
755757
| ','.kwarg_or_starred+
756758
| ','.kwarg_or_double_starred+
757759
starred_expression[expr_ty]:
758760
| '*' a=expression { _PyAST_Starred(a, Load, EXTRA) }
759761
kwarg_or_starred[KeywordOrStarred*]:
762+
| invalid_kwarg
760763
| a=NAME '=' b=expression {
761764
_PyPegen_keyword_or_starred(p, CHECK(keyword_ty, _PyAST_keyword(a->v.Name.id, b, EXTRA)), 1) }
762765
| a=starred_expression { _PyPegen_keyword_or_starred(p, a, 0) }
763-
| invalid_kwarg
764766
kwarg_or_double_starred[KeywordOrStarred*]:
767+
| invalid_kwarg
765768
| a=NAME '=' b=expression {
766769
_PyPegen_keyword_or_starred(p, CHECK(keyword_ty, _PyAST_keyword(a->v.Name.id, b, EXTRA)), 1) }
767770
| '**' a=expression { _PyPegen_keyword_or_starred(p, CHECK(keyword_ty, _PyAST_keyword(NULL, a, EXTRA)), 1) }
768-
| invalid_kwarg
769771

770772
# NOTE: star_targets may contain *bitwise_or, targets may not.
771773
star_targets[expr_ty]:
@@ -838,29 +840,37 @@ invalid_arguments:
838840
| a=args ',' '*' { RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "iterable argument unpacking follows keyword argument unpacking") }
839841
| a=expression b=for_if_clauses ',' [args | expression for_if_clauses] {
840842
RAISE_SYNTAX_ERROR_KNOWN_RANGE(a, PyPegen_last_item(b, comprehension_ty)->target, "Generator expression must be parenthesized") }
843+
| a=NAME b='=' expression for_if_clauses {
844+
RAISE_SYNTAX_ERROR_KNOWN_RANGE(a, b, "invalid syntax. Maybe you meant '==' or ':=' instead of '='?")}
841845
| a=args for_if_clauses { _PyPegen_nonparen_genexp_in_call(p, a) }
842846
| args ',' a=expression b=for_if_clauses {
843847
RAISE_SYNTAX_ERROR_KNOWN_RANGE(a, asdl_seq_GET(b, b->size-1)->target, "Generator expression must be parenthesized") }
844848
| a=args ',' args { _PyPegen_arguments_parsing_error(p, a) }
845849
invalid_kwarg:
850+
| a=NAME b='=' expression for_if_clauses {
851+
RAISE_SYNTAX_ERROR_KNOWN_RANGE(a, b, "invalid syntax. Maybe you meant '==' or ':=' instead of '='?")}
846852
| !(NAME '=') a=expression b='=' {
847853
RAISE_SYNTAX_ERROR_KNOWN_RANGE(
848854
a, b, "expression cannot contain assignment, perhaps you meant \"==\"?") }
849855

856+
expression_without_invalid[expr_ty]:
857+
| a=disjunction 'if' b=disjunction 'else' c=expression { _PyAST_IfExp(b, a, c, EXTRA) }
858+
| disjunction
859+
| lambdef
850860
invalid_expression:
851861
# !(NAME STRING) is not matched so we don't show this error with some invalid string prefixes like: kf"dsfsdf"
852862
# Soft keywords need to also be ignored because they can be parsed as NAME NAME
853-
| !(NAME STRING | SOFT_KEYWORD) a=disjunction b=expression {
863+
| !(NAME STRING | SOFT_KEYWORD) a=disjunction b=expression_without_invalid {
854864
RAISE_SYNTAX_ERROR_KNOWN_RANGE(a, b, "invalid syntax. Perhaps you forgot a comma?") }
855865

856866
invalid_named_expression:
857867
| a=expression ':=' expression {
858868
RAISE_SYNTAX_ERROR_KNOWN_LOCATION(
859869
a, "cannot use assignment expressions with %s", _PyPegen_get_expr_name(a)) }
860-
| a=NAME '=' b=bitwise_or !('='|':='|',') {
861-
RAISE_SYNTAX_ERROR_KNOWN_RANGE(a, b, "invalid syntax. Maybe you meant '==' or ':=' instead of '='?") }
862-
| !(list|tuple|genexp|'True'|'None'|'False') a=bitwise_or b='=' bitwise_or !('='|':='|',') {
863-
RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "cannot assign to %s here. Maybe you meant '==' instead of '='?",
870+
| a=NAME '=' b=bitwise_or !('='|':=') {
871+
p->in_raw_rule ? NULL : RAISE_SYNTAX_ERROR_KNOWN_RANGE(a, b, "invalid syntax. Maybe you meant '==' or ':=' instead of '='?") }
872+
| !(list|tuple|genexp|'True'|'None'|'False') a=bitwise_or b='=' bitwise_or !('='|':=') {
873+
p->in_raw_rule ? NULL : RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "cannot assign to %s here. Maybe you meant '==' instead of '='?",
864874
_PyPegen_get_expr_name(a)) }
865875

866876
invalid_assignment:

Lib/test/test_syntax.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1128,6 +1128,26 @@
11281128
Traceback (most recent call last):
11291129
SyntaxError: cannot assign to f-string expression here. Maybe you meant '==' instead of '='?
11301130
1131+
>>> (x, y, z=3, d, e)
1132+
Traceback (most recent call last):
1133+
SyntaxError: invalid syntax. Maybe you meant '==' or ':=' instead of '='?
1134+
1135+
>>> [x, y, z=3, d, e]
1136+
Traceback (most recent call last):
1137+
SyntaxError: invalid syntax. Maybe you meant '==' or ':=' instead of '='?
1138+
1139+
>>> [z=3]
1140+
Traceback (most recent call last):
1141+
SyntaxError: invalid syntax. Maybe you meant '==' or ':=' instead of '='?
1142+
1143+
>>> {x, y, z=3, d, e}
1144+
Traceback (most recent call last):
1145+
SyntaxError: invalid syntax. Maybe you meant '==' or ':=' instead of '='?
1146+
1147+
>>> {z=3}
1148+
Traceback (most recent call last):
1149+
SyntaxError: invalid syntax. Maybe you meant '==' or ':=' instead of '='?
1150+
11311151
>>> from t import x,
11321152
Traceback (most recent call last):
11331153
SyntaxError: trailing comma not allowed without surrounding parentheses

0 commit comments

Comments
 (0)