Skip to content

Commit a8d390e

Browse files
committed
attempt at better error messages for * and ** misuse
1 parent b4c6fc7 commit a8d390e

4 files changed

Lines changed: 2386 additions & 1762 deletions

File tree

Grammar/python.gram

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -706,10 +706,13 @@ expressions[expr_ty]:
706706
expression[expr_ty] (memo):
707707
| invalid_expression
708708
| invalid_legacy_expression
709-
| a=disjunction 'if' b=disjunction 'else' c=expression { _PyAST_IfExp(b, a, c, EXTRA) }
709+
| if_expression
710710
| disjunction
711711
| lambdef
712712

713+
if_expression[expr_ty]:
714+
| a=disjunction 'if' b=disjunction 'else' c=expression { _PyAST_IfExp(b, a, c, EXTRA) }
715+
713716
yield_expr[expr_ty]:
714717
| 'yield' 'from' a=expression { _PyAST_YieldFrom(a, EXTRA) }
715718
| 'yield' a=[star_expressions] { _PyAST_Yield(a, EXTRA) }
@@ -727,9 +730,16 @@ star_expression[expr_ty] (memo):
727730
star_named_expressions[asdl_expr_seq*]: a[asdl_expr_seq*]=','.star_named_expression+ [','] { a }
728731

729732
star_named_expression[expr_ty]:
733+
| invalid_starred_expression_unpacking
730734
| '*' a=bitwise_or { _PyAST_Starred(a, Load, EXTRA) }
731735
| named_expression
732736

737+
star_named_expressions_listset[asdl_expr_seq*]: a[asdl_expr_seq*]=','.star_named_expression_listset+ [','] { a }
738+
739+
star_named_expression_listset[expr_ty]:
740+
| invalid_starred_expression_unpacking_listset
741+
| star_named_expression
742+
733743
assignment_expression[expr_ty]:
734744
| a=NAME ':=' ~ b=expression {
735745
CHECK_VERSION(expr_ty, 8, "Assignment expressions are",
@@ -878,8 +888,8 @@ atom[expr_ty]:
878888
| &(STRING|FSTRING_START|TSTRING_START) strings
879889
| NUMBER
880890
| &'(' (tuple | group | genexp)
881-
| &'[' (list | listcomp)
882-
| &'{' (dict | set | dictcomp | setcomp)
891+
| &'[' (listcomp | list)
892+
| &'{' (dictcomp | setcomp | dict | set)
883893
| '...' { _PyAST_Constant(Py_Ellipsis, NULL, EXTRA) }
884894

885895
group[expr_ty]:
@@ -990,13 +1000,13 @@ string[expr_ty]: s[Token*]=STRING { _PyPegen_constant_from_string(p, s) }
9901000
strings[expr_ty] (memo): a[asdl_expr_seq*]=(fstring|string|tstring)+ { _PyPegen_concatenate_strings(p, a, EXTRA) }
9911001

9921002
list[expr_ty]:
993-
| '[' a=[star_named_expressions] ']' { _PyAST_List(a, Load, EXTRA) }
1003+
| '[' a=[star_named_expressions_listset] ']' { _PyAST_List(a, Load, EXTRA) }
9941004

9951005
tuple[expr_ty]:
996-
| '(' a=[y=star_named_expression ',' z=[star_named_expressions] { _PyPegen_seq_insert_in_front(p, y, z) } ] ')' {
1006+
| '(' a=[y=star_named_expression_listset ',' z=[star_named_expressions_listset] { _PyPegen_seq_insert_in_front(p, y, z) } ] ')' {
9971007
_PyAST_Tuple(a, Load, EXTRA) }
9981008

999-
set[expr_ty]: '{' a=star_named_expressions '}' { _PyAST_Set(a, EXTRA) }
1009+
set[expr_ty]: '{' a=star_named_expressions_listset '}' { _PyAST_Set(a, EXTRA) }
10001010

10011011
# Dicts
10021012
# -----
@@ -1046,6 +1056,7 @@ genexp[expr_ty]:
10461056
dictcomp[expr_ty]:
10471057
| '{' a=kvpair b=for_if_clauses '}' { _PyAST_DictComp(a->key, a->value, b, EXTRA) }
10481058
| '{' '**' a=bitwise_or b=for_if_clauses '}' { _PyAST_DictComp(a, NULL, b, EXTRA) }
1059+
| invalid_dictcomp
10491060

10501061
# FUNCTION CALL ARGUMENTS
10511062
# =======================
@@ -1246,6 +1257,10 @@ invalid_expression:
12461257
_PyPegen_check_legacy_stmt(p, a) ? NULL : p->tokens[p->mark-1]->level == 0 ? NULL :
12471258
RAISE_SYNTAX_ERROR_KNOWN_RANGE(a, b, "invalid syntax. Perhaps you forgot a comma?") }
12481259
| a=disjunction 'if' b=disjunction !('else'|':') { RAISE_SYNTAX_ERROR_KNOWN_RANGE(a, b, "expected 'else' after 'if' expression") }
1260+
| disjunction 'if' b=disjunction 'else' a='*' {
1261+
RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "cannot use unpacking operator on part of a conditional expression") }
1262+
| disjunction 'if' b=disjunction 'else' a='**' {
1263+
RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "cannot use dict unpacking operator on part of a conditional expression") }
12491264
| a=disjunction 'if' b=disjunction 'else' !expression {
12501265
RAISE_SYNTAX_ERROR_ON_NEXT_TOKEN("expected expression after 'else', but statement is given") }
12511266
| a[stmt_ty]=(pass_stmt|break_stmt|continue_stmt) 'if' b=disjunction 'else' c=simple_stmt {
@@ -1300,15 +1315,18 @@ invalid_del_stmt:
13001315
invalid_block:
13011316
| NEWLINE !INDENT { RAISE_INDENTATION_ERROR("expected an indented block") }
13021317
invalid_comprehension:
1303-
| '[' a='**' b=expression for_if_clauses {
1304-
RAISE_SYNTAX_ERROR_KNOWN_RANGE(a, b, "dict unpacking cannot be used in list comprehension") }
1305-
| '(' a='**' b=expression for_if_clauses {
1306-
RAISE_SYNTAX_ERROR_KNOWN_RANGE(a, b, "dict unpacking cannot be used in generator expression") }
1318+
| '[' a='**' bitwise_or for_if_clauses {
1319+
RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "dict unpacking cannot be used in list comprehension") }
1320+
| '(' a='**' bitwise_or for_if_clauses {
1321+
RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "dict unpacking cannot be used in generator expression") }
13071322
| ('[' | '{') a=star_named_expression ',' b=star_named_expressions for_if_clauses {
13081323
RAISE_SYNTAX_ERROR_KNOWN_RANGE(a, PyPegen_last_item(b, expr_ty),
13091324
"did you forget parentheses around the comprehension target?") }
13101325
| ('[' | '{') a=star_named_expression b=',' for_if_clauses {
13111326
RAISE_SYNTAX_ERROR_KNOWN_RANGE(a, b, "did you forget parentheses around the comprehension target?") }
1327+
invalid_dictcomp:
1328+
| '{' a='**' b=if_expression for_if_clauses {
1329+
RAISE_SYNTAX_ERROR_KNOWN_RANGE(a, b, "cannot unpack conditional expression in dict comprehension") }
13121330
invalid_parameters:
13131331
| a="/" ',' {
13141332
RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "at least one parameter must precede /") }
@@ -1496,6 +1514,8 @@ invalid_class_def_raw:
14961514

14971515
invalid_double_starred_kvpairs:
14981516
| ','.double_starred_kvpair+ ',' invalid_kvpair
1517+
| a='**' b=if_expression {
1518+
RAISE_SYNTAX_ERROR_KNOWN_RANGE(a, b, "cannot use dict unpacking operator here. did you forget to wrap the conditional expression in parentheses?") }
14991519
| a='*' b=bitwise_or ':' expression { RAISE_SYNTAX_ERROR_KNOWN_RANGE(a, b, "cannot use a starred expression in a dictionary key") }
15001520
| a='**' b=bitwise_or ':' expression { RAISE_SYNTAX_ERROR_KNOWN_RANGE(a, b, "cannot use dict unpacking in a dictionary key") }
15011521
| expression ':' a='*' b=bitwise_or { RAISE_SYNTAX_ERROR_KNOWN_RANGE(a, b, "cannot use a starred expression in a dictionary value") }
@@ -1506,7 +1526,12 @@ invalid_kvpair:
15061526
RAISE_ERROR_KNOWN_LOCATION(p, PyExc_SyntaxError, a->lineno, a->end_col_offset - 1, a->end_lineno, -1, "':' expected after dictionary key") }
15071527
| expression ':' a='*' bitwise_or { RAISE_SYNTAX_ERROR_STARTING_FROM(a, "cannot use a starred expression in a dictionary value") }
15081528
| expression a=':' &('}'|',') {RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "expression expected after dictionary key and ':'") }
1529+
invalid_starred_expression_unpacking_listset:
1530+
| a='**' bitwise_or {
1531+
RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "cannot use dict unpacking here") }
15091532
invalid_starred_expression_unpacking:
1533+
| a='*' b=if_expression {
1534+
RAISE_SYNTAX_ERROR_KNOWN_RANGE(a, b, "cannot use unpacking operator here. did you forget to wrap the conditional expression in parentheses?") }
15101535
| a='*' expression '=' b=expression { RAISE_SYNTAX_ERROR_KNOWN_RANGE(a, b, "cannot assign to iterable argument unpacking") }
15111536
invalid_starred_expression:
15121537
| '*' { RAISE_SYNTAX_ERROR("Invalid star expression") }

Lib/test/test_unpack_ex.py

Lines changed: 60 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,14 @@
213213
>>> z
214214
[1, 2, 3]
215215
216+
>>> x = [1, 2, 3]
217+
>>> y = [4, 5, 6]
218+
>>> def f(*args):
219+
... print(args)
220+
221+
>>> f(*x if x else y)
222+
(1, 2, 3)
223+
216224
217225
Malformed comperehension element unpacking
218226
@@ -222,13 +230,65 @@
222230
[*x for x in [1, 2, 3]]
223231
^^
224232
TypeError: Value after * must be an iterable, not int
233+
234+
235+
Error messages for specific failure modes of unpacking
236+
237+
>>> [*x if x else y for x in z]
238+
Traceback (most recent call last):
239+
...
240+
[*x if x else y for x in z]
241+
^^^^^^^^^^^^^^
242+
SyntaxError: cannot use unpacking operator here. did you forget to wrap the conditional expression in parentheses?
243+
244+
>>> [*x if x else y]
245+
Traceback (most recent call last):
246+
...
247+
[*x if x else y]
248+
^^^^^^^^^^^^^^
249+
SyntaxError: cannot use unpacking operator here. did you forget to wrap the conditional expression in parentheses?
250+
251+
>>> [x if x else *y for x in z]
252+
Traceback (most recent call last):
253+
...
254+
[x if x else *y for x in z]
255+
^
256+
SyntaxError: cannot use unpacking operator on part of a conditional expression
257+
258+
>>> [x if x else *y]
259+
Traceback (most recent call last):
260+
...
261+
[x if x else *y]
262+
^
263+
SyntaxError: cannot use unpacking operator on part of a conditional expression
264+
265+
>>> {**x if x else y}
266+
Traceback (most recent call last):
267+
...
268+
{**x if x else y}
269+
^^^^^^^^^^^^^^^^
270+
SyntaxError: cannot use dict unpacking operator here. did you forget to wrap the conditional expression in parentheses?
271+
>>> {x if x else **y}
272+
Traceback (most recent call last):
273+
...
274+
{x if x else **y}
275+
^^
276+
SyntaxError: cannot use dict unpacking operator on part of a conditional expression
277+
225278
>>> [**x for x in [{1: 2}]]
226279
Traceback (most recent call last):
227280
...
228281
[**x for x in [{1: 2}]]
229282
^^^
230283
SyntaxError: dict unpacking cannot be used in list comprehension
231284
285+
>>> dict(**x for x in [{1:2}])
286+
Traceback (most recent call last):
287+
...
288+
dict(**x for x in [{1:2}])
289+
^^^
290+
SyntaxError: dict unpacking cannot be used in generator expression
291+
232292
>>> {*a: b for a, b in {1: 2}.items()}
233293
Traceback (most recent call last):
234294
...
@@ -269,13 +329,6 @@
269329
>>> f(*x for x in [[1,2,3]])
270330
<class 'generator'> [1, 2, 3] []
271331
272-
>>> dict(**x for x in [{1:2}])
273-
Traceback (most recent call last):
274-
...
275-
dict(**x for x in [{1:2}])
276-
^^^
277-
SyntaxError: dict unpacking cannot be used in generator expression
278-
279332
Iterable argument unpacking
280333
281334
>>> print(*[1], *[2], 3)

0 commit comments

Comments
 (0)