Skip to content

Commit 3397e31

Browse files
bpo-43914: Correctly highlight SyntaxError exceptions for invalid generator expression in function calls (GH-28576)
(cherry picked from commit e5f13ce) Co-authored-by: Pablo Galindo Salgado <[email protected]>
1 parent 0eb57c3 commit 3397e31

File tree

5 files changed

+31
-8
lines changed

5 files changed

+31
-8
lines changed

Grammar/python.gram

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -833,7 +833,7 @@ invalid_arguments:
833833
RAISE_SYNTAX_ERROR_KNOWN_RANGE(a, PyPegen_last_item(b, comprehension_ty)->target, "Generator expression must be parenthesized") }
834834
| a=NAME b='=' expression for_if_clauses {
835835
RAISE_SYNTAX_ERROR_KNOWN_RANGE(a, b, "invalid syntax. Maybe you meant '==' or ':=' instead of '='?")}
836-
| a=args for_if_clauses { _PyPegen_nonparen_genexp_in_call(p, a) }
836+
| a=args b=for_if_clauses { _PyPegen_nonparen_genexp_in_call(p, a, b) }
837837
| args ',' a=expression b=for_if_clauses {
838838
RAISE_SYNTAX_ERROR_KNOWN_RANGE(a, asdl_seq_GET(b, b->size-1)->target, "Generator expression must be parenthesized") }
839839
| a=args ',' args { _PyPegen_arguments_parsing_error(p, a) }

Lib/test/test_syntax.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1273,7 +1273,8 @@
12731273
class SyntaxTestCase(unittest.TestCase):
12741274

12751275
def _check_error(self, code, errtext,
1276-
filename="<testcase>", mode="exec", subclass=None, lineno=None, offset=None):
1276+
filename="<testcase>", mode="exec", subclass=None,
1277+
lineno=None, offset=None, end_lineno=None, end_offset=None):
12771278
"""Check that compiling code raises SyntaxError with errtext.
12781279
12791280
errtest is a regular expression that must be present in the
@@ -1293,6 +1294,11 @@ def _check_error(self, code, errtext,
12931294
self.assertEqual(err.lineno, lineno)
12941295
if offset is not None:
12951296
self.assertEqual(err.offset, offset)
1297+
if end_lineno is not None:
1298+
self.assertEqual(err.end_lineno, end_lineno)
1299+
if end_offset is not None:
1300+
self.assertEqual(err.end_offset, end_offset)
1301+
12961302
else:
12971303
self.fail("compile() did not raise SyntaxError")
12981304

@@ -1432,6 +1438,11 @@ def test_kwargs_last3(self):
14321438
self._check_error("int(**{'base': 10}, *['2'])",
14331439
"iterable argument unpacking follows "
14341440
"keyword argument unpacking")
1441+
1442+
def test_generator_in_function_call(self):
1443+
self._check_error("foo(x, y for y in range(3) for z in range(2) if z , p)",
1444+
"Generator expression must be parenthesized",
1445+
lineno=1, end_lineno=1, offset=11, end_offset=53)
14351446

14361447
def test_empty_line_after_linecont(self):
14371448
# See issue-40847

Parser/parser.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17902,15 +17902,15 @@ invalid_arguments_rule(Parser *p)
1790217902
}
1790317903
D(fprintf(stderr, "%*c> invalid_arguments[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "args for_if_clauses"));
1790417904
expr_ty a;
17905-
asdl_comprehension_seq* for_if_clauses_var;
17905+
asdl_comprehension_seq* b;
1790617906
if (
1790717907
(a = args_rule(p)) // args
1790817908
&&
17909-
(for_if_clauses_var = for_if_clauses_rule(p)) // for_if_clauses
17909+
(b = for_if_clauses_rule(p)) // for_if_clauses
1791017910
)
1791117911
{
1791217912
D(fprintf(stderr, "%*c+ invalid_arguments[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "args for_if_clauses"));
17913-
_res = _PyPegen_nonparen_genexp_in_call ( p , a );
17913+
_res = _PyPegen_nonparen_genexp_in_call ( p , a , b );
1791417914
if (_res == NULL && PyErr_Occurred()) {
1791517915
p->error_indicator = 1;
1791617916
D(p->level--);

Parser/pegen.c

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2530,8 +2530,17 @@ void *_PyPegen_arguments_parsing_error(Parser *p, expr_ty e) {
25302530
return RAISE_SYNTAX_ERROR(msg);
25312531
}
25322532

2533+
2534+
static inline expr_ty
2535+
_PyPegen_get_last_comprehension_item(comprehension_ty comprehension) {
2536+
if (comprehension->ifs == NULL || asdl_seq_LEN(comprehension->ifs) == 0) {
2537+
return comprehension->iter;
2538+
}
2539+
return PyPegen_last_item(comprehension->ifs, expr_ty);
2540+
}
2541+
25332542
void *
2534-
_PyPegen_nonparen_genexp_in_call(Parser *p, expr_ty args)
2543+
_PyPegen_nonparen_genexp_in_call(Parser *p, expr_ty args, asdl_comprehension_seq *comprehensions)
25352544
{
25362545
/* The rule that calls this function is 'args for_if_clauses'.
25372546
For the input f(L, x for x in y), L and x are in args and
@@ -2545,8 +2554,11 @@ _PyPegen_nonparen_genexp_in_call(Parser *p, expr_ty args)
25452554
return NULL;
25462555
}
25472556

2548-
return RAISE_SYNTAX_ERROR_STARTING_FROM(
2557+
comprehension_ty last_comprehension = PyPegen_last_item(comprehensions, comprehension_ty);
2558+
2559+
return RAISE_SYNTAX_ERROR_KNOWN_RANGE(
25492560
(expr_ty) asdl_seq_GET(args->v.Call.args, len - 1),
2561+
_PyPegen_get_last_comprehension_item(last_comprehension),
25502562
"Generator expression must be parenthesized"
25512563
);
25522564
}

Parser/pegen.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -325,7 +325,7 @@ _RAISE_SYNTAX_ERROR_INVALID_TARGET(Parser *p, TARGETS_TYPE type, void *e)
325325
}
326326

327327
void *_PyPegen_arguments_parsing_error(Parser *, expr_ty);
328-
void *_PyPegen_nonparen_genexp_in_call(Parser *p, expr_ty args);
328+
void *_PyPegen_nonparen_genexp_in_call(Parser *p, expr_ty args, asdl_comprehension_seq *comprehensions);
329329

330330

331331
// Generated function in parse.c - function definition in python.gram

0 commit comments

Comments
 (0)