Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 33 additions & 13 deletions Grammar/python.gram
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ simple_stmt[asdl_seq*]:
# will throw a SyntaxError.
small_stmt[stmt_ty] (memo):
| assignment
| e=star_expressions { _Py_Expr(e, EXTRA) }
| e=star_expressions !('=' | augassign) { _Py_Expr(e, EXTRA) }
| &'return' return_stmt
| &('import' | 'from') import_stmt
| &'raise' raise_stmt
Expand All @@ -72,6 +72,7 @@ small_stmt[stmt_ty] (memo):
| 'continue' { _Py_Continue(EXTRA) }
| &'global' global_stmt
| &'nonlocal' nonlocal_stmt
| targets_with_invalid
compound_stmt[stmt_ty]:
| &('def' | '@' | ASYNC) function_def
| &'if' if_stmt
Expand All @@ -96,7 +97,7 @@ assignment[stmt_ty]:
_Py_Assign(a, b, NEW_TYPE_COMMENT(p, tc), EXTRA) }
| a=target b=augassign c=(yield_expr | star_expressions) {
_Py_AugAssign(a, b->kind, c, EXTRA) }
| invalid_assignment
| invalid_ann_assignment

augassign[AugOperator*]:
| '+=' { _PyPegen_augoperator(p, Add) }
Expand Down Expand Up @@ -564,14 +565,15 @@ star_targets_seq[asdl_seq*]: a=','.star_target+ [','] { a }
star_target[expr_ty] (memo):
| '*' a=(!'*' star_target) {
_Py_Starred(CHECK(_PyPegen_set_expr_context(p, a, Store)), Store, EXTRA) }
| a=t_primary '.' b=NAME !t_lookahead { _Py_Attribute(a, b->v.Name.id, Store, EXTRA) }
| a=t_primary '[' b=slices ']' !t_lookahead { _Py_Subscript(a, b, Store, EXTRA) }
| a=t_primary '.' b=NAME &star_target_end { _Py_Attribute(a, b->v.Name.id, Store, EXTRA) }
| a=t_primary '[' b=slices ']' &star_target_end { _Py_Subscript(a, b, Store, EXTRA) }
| star_atom
star_atom[expr_ty]:
| a=NAME { _PyPegen_set_expr_context(p, a, Store) }
| a=NAME &star_target_end { _PyPegen_set_expr_context(p, a, Store) }
| '(' a=star_target ')' { _PyPegen_set_expr_context(p, a, Store) }
| '(' a=[star_targets_seq] ')' { _Py_Tuple(a, Store, EXTRA) }
| '[' a=[star_targets_seq] ']' { _Py_List(a, Store, EXTRA) }
star_target_end: ')' | ']' | ',' | '=' | 'in'

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I presume in is here because of for <target> in <expr>, but this would mean that you'd get a different error for

a in b = 42

than you'd get for

a < b = 42

Copy link
Member Author

@lysnikolaou lysnikolaou May 12, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually not, because when parsing the invalid target the whole expression gets parsed, which means that cannot assign to comparison is displayed for both.

You're right. What I said above was me talking about 1 in a = 42 for some reason, which would actually display the correct error message, since 1 is not a valid assignment target.

Another problem with this is that it isn't really usable in for-statements, because the in always gets parsed as the comparison operator in, which means that even something like for f() in a: pass displays the cannot assign to comparison error.


inside_paren_ann_assign_target[expr_ty]:
| ann_assign_subscript_attribute_target
Expand Down Expand Up @@ -599,8 +601,8 @@ del_target_end: ')' | ']' | ',' | ';' | NEWLINE

targets[asdl_seq*]: a=','.target+ [','] { a }
target[expr_ty] (memo):
| a=t_primary '.' b=NAME !t_lookahead { _Py_Attribute(a, b->v.Name.id, Store, EXTRA) }
| a=t_primary '[' b=slices ']' !t_lookahead { _Py_Subscript(a, b, Store, EXTRA) }
| a=t_primary '.' b=NAME &target_end { _Py_Attribute(a, b->v.Name.id, Store, EXTRA) }
| a=t_primary '[' b=slices ']' &target_end { _Py_Subscript(a, b, Store, EXTRA) }
| t_atom
t_primary[expr_ty]:
| a=t_primary '.' b=NAME &t_lookahead { _Py_Attribute(a, b->v.Name.id, Load, EXTRA) }
Expand All @@ -614,11 +616,11 @@ t_primary[expr_ty]:
| a=atom &t_lookahead { a }
t_lookahead: '(' | '[' | '.'
t_atom[expr_ty]:
| a=NAME { _PyPegen_set_expr_context(p, a, Store) }
| a=NAME &target_end { _PyPegen_set_expr_context(p, a, Store) }
| '(' a=target ')' { _PyPegen_set_expr_context(p, a, Store) }
| '(' b=[targets] ')' { _Py_Tuple(b, Store, EXTRA) }
| '[' b=[targets] ']' { _Py_List(b, Store, EXTRA) }

target_end: ')' | ']' | ',' | augassign | ':'

# From here on, there are rules for invalid syntax with specialised error messages
incorrect_arguments:
Expand All @@ -627,17 +629,16 @@ incorrect_arguments:
RAISE_SYNTAX_ERROR("Generator expression must be parenthesized") }
| a=args ',' args { _PyPegen_arguments_parsing_error(p, a) }
invalid_kwarg:
| expression '=' { RAISE_SYNTAX_ERROR("expression cannot contain assignment, perhaps you meant \"==\"?") }
| expression '=' {
RAISE_SYNTAX_ERROR_NO_COL_OFFSET("expression cannot contain assignment, perhaps you meant \"==\"?") }
invalid_named_expression:
| a=expression ':=' expression {
RAISE_SYNTAX_ERROR("cannot use assignment expressions with %s", _PyPegen_get_expr_name(a)) }
invalid_assignment:
invalid_ann_assignment:
| list ':' { RAISE_SYNTAX_ERROR("only single target (not list) can be annotated") }
| tuple ':' { RAISE_SYNTAX_ERROR("only single target (not tuple) can be annotated") }
| expression ':' expression ['=' annotated_rhs] {
RAISE_SYNTAX_ERROR("illegal target for annotation") }
| a=expression ('=' | augassign) (yield_expr | star_expressions) {
RAISE_SYNTAX_ERROR_NO_COL_OFFSET("cannot assign to %s", _PyPegen_get_expr_name(a)) }
invalid_block:
| NEWLINE !INDENT { RAISE_INDENTATION_ERROR("expected an indented block") }
invalid_comprehension:
Expand All @@ -656,3 +657,22 @@ invalid_double_type_comments:
invalid_del_target:
| a=star_expression &del_target_end {
RAISE_SYNTAX_ERROR("cannot delete %s", _PyPegen_get_expr_name(a)) }

targets_with_invalid:
| target_with_invalid !','
| target_with_invalid (',' target_with_invalid)* [',']
target_with_invalid:
| '*' a=(!'*' target_with_invalid)
| a=t_primary '.' b=NAME &(star_target_end | target_end)
| a=t_primary '[' b=slices ']' &(star_target_end | target_end)
| target_atom_with_invalid
| invalid_target
target_atom_with_invalid:
| a=NAME &(star_target_end | target_end) { _PyPegen_set_expr_context(p, a, Store) }
| '(' a=target_with_invalid ')' { _PyPegen_set_expr_context(p, a, Store) }
| '(' a=[targets_with_invalid] ')' { _Py_Tuple(a, Store, EXTRA) }
| '[' a=[targets_with_invalid] ']' { _Py_List(a, Store, EXTRA) }
| invalid_target
invalid_target:
| a=star_expression &(star_target_end | target_end) {
RAISE_SYNTAX_ERROR_NO_COL_OFFSET("cannot assign to %s", _PyPegen_get_expr_name(a)) }
2 changes: 1 addition & 1 deletion Lib/test/test_peg_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -724,7 +724,7 @@ def test_incorrect_ast_generation_with_specialized_errors(self) -> None:
peg_parser.parse_string(source)
self.assertTrue(
error_text in se.exception.msg,
f"Actual error message does not match expexted for {source}"
f"Actual error message does not match expected for {source}"
)

@unittest.expectedFailure
Expand Down
58 changes: 27 additions & 31 deletions Lib/test/test_syntax.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,30 +100,29 @@
This test just checks a couple of cases rather than enumerating all of
them.

# All of the following also produce different error messages with pegen
# >>> (a, "b", c) = (1, 2, 3)
# Traceback (most recent call last):
# SyntaxError: cannot assign to literal
>>> (a, "b", c) = (1, 2, 3)
Traceback (most recent call last):
SyntaxError: cannot assign to literal

# >>> (a, True, c) = (1, 2, 3)
# Traceback (most recent call last):
# SyntaxError: cannot assign to True
>>> (a, True, c) = (1, 2, 3)
Traceback (most recent call last):
SyntaxError: cannot assign to True

>>> (a, __debug__, c) = (1, 2, 3)
Traceback (most recent call last):
SyntaxError: cannot assign to __debug__

# >>> (a, *True, c) = (1, 2, 3)
# Traceback (most recent call last):
# SyntaxError: cannot assign to True
>>> (a, *True, c) = (1, 2, 3)
Traceback (most recent call last):
SyntaxError: cannot assign to True

>>> (a, *__debug__, c) = (1, 2, 3)
Traceback (most recent call last):
SyntaxError: cannot assign to __debug__

# >>> [a, b, c + 1] = [1, 2, 3]
# Traceback (most recent call last):
# SyntaxError: cannot assign to operator
>>> [a, b, c + 1] = [1, 2, 3]
Traceback (most recent call last):
SyntaxError: cannot assign to operator

>>> a if 1 else b = 1
Traceback (most recent call last):
Expand Down Expand Up @@ -300,28 +299,25 @@
... 290, 291, 292, 293, 294, 295, 296, 297, 298, 299) # doctest: +ELLIPSIS
(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, ..., 297, 298, 299)

# >>> f(lambda x: x[0] = 3)
# Traceback (most recent call last):
# SyntaxError: expression cannot contain assignment, perhaps you meant "=="?
>>> f(lambda x: x[0] = 3)
Traceback (most recent call last):
SyntaxError: expression cannot contain assignment, perhaps you meant "=="?

The grammar accepts any test (basically, any expression) in the
keyword slot of a call site. Test a few different options.

# >>> f(x()=2)
# Traceback (most recent call last):
# SyntaxError: expression cannot contain assignment, perhaps you meant "=="?
# >>> f(a or b=1)
# Traceback (most recent call last):
# SyntaxError: expression cannot contain assignment, perhaps you meant "=="?
# >>> f(x.y=1)
# Traceback (most recent call last):
# SyntaxError: expression cannot contain assignment, perhaps you meant "=="?
# >>> f((x)=2)
# Traceback (most recent call last):
# SyntaxError: expression cannot contain assignment, perhaps you meant "=="?
# >>> f(True=2)
# Traceback (most recent call last):
# SyntaxError: cannot assign to True
>>> f(x()=2)
Traceback (most recent call last):
SyntaxError: expression cannot contain assignment, perhaps you meant "=="?
>>> f(a or b=1)
Traceback (most recent call last):
SyntaxError: expression cannot contain assignment, perhaps you meant "=="?
>>> f(x.y=1)
Traceback (most recent call last):
SyntaxError: expression cannot contain assignment, perhaps you meant "=="?
>>> f((x)=2)
Traceback (most recent call last):
SyntaxError: expression cannot contain assignment, perhaps you meant "=="?
>>> f(__debug__=1)
Traceback (most recent call last):
SyntaxError: cannot assign to __debug__
Expand Down
Loading