Skip to content

Commit ae14583

Browse files
authored
bpo-40334: Produce better error messages for non-parenthesized genexps (GH-20153)
The error message, generated for a non-parenthesized generator expression in function calls, was still the generic `invalid syntax`, when the generator expression wasn't appearing as the first argument in the call. With this patch, even on input like `f(a, b, c for c in d, e)`, the correct error message gets produced.
1 parent 7864f11 commit ae14583

File tree

5 files changed

+79
-6
lines changed

5 files changed

+79
-6
lines changed

Grammar/python.gram

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -627,6 +627,9 @@ incorrect_arguments:
627627
| args ',' '*' { RAISE_SYNTAX_ERROR("iterable argument unpacking follows keyword argument unpacking") }
628628
| a=expression for_if_clauses ',' [args | expression for_if_clauses] {
629629
RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "Generator expression must be parenthesized") }
630+
| a=args for_if_clauses { _PyPegen_nonparen_genexp_in_call(p, a) }
631+
| args ',' a=expression for_if_clauses {
632+
RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "Generator expression must be parenthesized") }
630633
| a=args ',' args { _PyPegen_arguments_parsing_error(p, a) }
631634
invalid_kwarg:
632635
| a=expression '=' {

Lib/test/test_syntax.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -216,11 +216,9 @@
216216
>>> f(x for x in L, **{})
217217
Traceback (most recent call last):
218218
SyntaxError: Generator expression must be parenthesized
219-
220-
# >>> f(L, x for x in L)
221-
# Traceback (most recent call last):
222-
# SyntaxError: Generator expression must be parenthesized
223-
219+
>>> f(L, x for x in L)
220+
Traceback (most recent call last):
221+
SyntaxError: Generator expression must be parenthesized
224222
>>> f(x for x in L, y for y in L)
225223
Traceback (most recent call last):
226224
SyntaxError: Generator expression must be parenthesized

Parser/pegen/parse.c

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11670,6 +11670,8 @@ t_atom_rule(Parser *p)
1167011670
// incorrect_arguments:
1167111671
// | args ',' '*'
1167211672
// | expression for_if_clauses ',' [args | expression for_if_clauses]
11673+
// | args for_if_clauses
11674+
// | args ',' expression for_if_clauses
1167311675
// | args ',' args
1167411676
static void *
1167511677
incorrect_arguments_rule(Parser *p)
@@ -11731,6 +11733,54 @@ incorrect_arguments_rule(Parser *p)
1173111733
}
1173211734
p->mark = _mark;
1173311735
}
11736+
{ // args for_if_clauses
11737+
if (p->error_indicator) {
11738+
return NULL;
11739+
}
11740+
expr_ty a;
11741+
asdl_seq* for_if_clauses_var;
11742+
if (
11743+
(a = args_rule(p)) // args
11744+
&&
11745+
(for_if_clauses_var = for_if_clauses_rule(p)) // for_if_clauses
11746+
)
11747+
{
11748+
_res = _PyPegen_nonparen_genexp_in_call ( p , a );
11749+
if (_res == NULL && PyErr_Occurred()) {
11750+
p->error_indicator = 1;
11751+
return NULL;
11752+
}
11753+
goto done;
11754+
}
11755+
p->mark = _mark;
11756+
}
11757+
{ // args ',' expression for_if_clauses
11758+
if (p->error_indicator) {
11759+
return NULL;
11760+
}
11761+
Token * _literal;
11762+
expr_ty a;
11763+
expr_ty args_var;
11764+
asdl_seq* for_if_clauses_var;
11765+
if (
11766+
(args_var = args_rule(p)) // args
11767+
&&
11768+
(_literal = _PyPegen_expect_token(p, 12)) // token=','
11769+
&&
11770+
(a = expression_rule(p)) // expression
11771+
&&
11772+
(for_if_clauses_var = for_if_clauses_rule(p)) // for_if_clauses
11773+
)
11774+
{
11775+
_res = RAISE_SYNTAX_ERROR_KNOWN_LOCATION ( a , "Generator expression must be parenthesized" );
11776+
if (_res == NULL && PyErr_Occurred()) {
11777+
p->error_indicator = 1;
11778+
return NULL;
11779+
}
11780+
goto done;
11781+
}
11782+
p->mark = _mark;
11783+
}
1173411784
{ // args ',' args
1173511785
if (p->error_indicator) {
1173611786
return NULL;

Parser/pegen/pegen.c

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2100,3 +2100,24 @@ void *_PyPegen_arguments_parsing_error(Parser *p, expr_ty e) {
21002100

21012101
return RAISE_SYNTAX_ERROR(msg);
21022102
}
2103+
2104+
void *
2105+
_PyPegen_nonparen_genexp_in_call(Parser *p, expr_ty args)
2106+
{
2107+
/* The rule that calls this function is 'args for_if_clauses'.
2108+
For the input f(L, x for x in y), L and x are in args and
2109+
the for is parsed as a for_if_clause. We have to check if
2110+
len <= 1, so that input like dict((a, b) for a, b in x)
2111+
gets successfully parsed and then we pass the last
2112+
argument (x in the above example) as the location of the
2113+
error */
2114+
Py_ssize_t len = asdl_seq_LEN(args->v.Call.args);
2115+
if (len <= 1) {
2116+
return NULL;
2117+
}
2118+
2119+
return RAISE_SYNTAX_ERROR_KNOWN_LOCATION(
2120+
(expr_ty) asdl_seq_GET(args->v.Call.args, len - 1),
2121+
"Generator expression must be parenthesized"
2122+
);
2123+
}

Parser/pegen/pegen.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ RAISE_ERROR_KNOWN_LOCATION(Parser *p, PyObject *errtype, int lineno,
152152
#define RAISE_SYNTAX_ERROR(msg, ...) _PyPegen_raise_error(p, PyExc_SyntaxError, msg, ##__VA_ARGS__)
153153
#define RAISE_INDENTATION_ERROR(msg, ...) _PyPegen_raise_error(p, PyExc_IndentationError, msg, ##__VA_ARGS__)
154154
#define RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, msg, ...) \
155-
RAISE_ERROR_KNOWN_LOCATION(p, PyExc_SyntaxError, a->lineno, a->col_offset, msg, ##__VA_ARGS__)
155+
RAISE_ERROR_KNOWN_LOCATION(p, PyExc_SyntaxError, (a)->lineno, (a)->col_offset, msg, ##__VA_ARGS__)
156156

157157
Py_LOCAL_INLINE(void *)
158158
CHECK_CALL(Parser *p, void *result)
@@ -262,6 +262,7 @@ mod_ty _PyPegen_make_module(Parser *, asdl_seq *);
262262
// Error reporting helpers
263263
expr_ty _PyPegen_get_invalid_target(expr_ty e);
264264
void *_PyPegen_arguments_parsing_error(Parser *, expr_ty);
265+
void *_PyPegen_nonparen_genexp_in_call(Parser *p, expr_ty args);
265266

266267

267268
void *_PyPegen_parse(Parser *);

0 commit comments

Comments
 (0)