Skip to content

Commit 99db2a1

Browse files
authored
bpo-40334: Allow trailing comma in parenthesised context managers (GH-19964)
1 parent 441416c commit 99db2a1

File tree

3 files changed

+79
-7
lines changed

3 files changed

+79
-7
lines changed

Grammar/python.gram

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -170,11 +170,11 @@ for_stmt[stmt_ty]:
170170
CHECK_VERSION(5, "Async for loops are", _Py_AsyncFor(t, ex, b, el, NEW_TYPE_COMMENT(p, tc), EXTRA)) }
171171

172172
with_stmt[stmt_ty]:
173-
| 'with' '(' a=','.with_item+ ')' ':' b=block {
173+
| 'with' '(' a=','.with_item+ ','? ')' ':' b=block {
174174
_Py_With(a, b, NULL, EXTRA) }
175175
| 'with' a=','.with_item+ ':' tc=[TYPE_COMMENT] b=block {
176176
_Py_With(a, b, NEW_TYPE_COMMENT(p, tc), EXTRA) }
177-
| ASYNC 'with' '(' a=','.with_item+ ')' ':' b=block {
177+
| ASYNC 'with' '(' a=','.with_item+ ','? ')' ':' b=block {
178178
CHECK_VERSION(5, "Async with statements are", _Py_AsyncWith(a, b, NULL, EXTRA)) }
179179
| ASYNC 'with' a=','.with_item+ ':' tc=[TYPE_COMMENT] b=block {
180180
CHECK_VERSION(5, "Async with statements are", _Py_AsyncWith(a, b, NEW_TYPE_COMMENT(p, tc), EXTRA)) }

Lib/test/test_grammar.py

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Python test set -- part 1, grammar.
22
# This just tests whether the parser accepts them all.
33

4-
from test.support import check_syntax_error, check_syntax_warning
4+
from test.support import check_syntax_error, check_syntax_warning, use_old_parser
55
import inspect
66
import unittest
77
import sys
@@ -1694,6 +1694,70 @@ def __exit__(self, *args):
16941694
with manager() as x, manager():
16951695
pass
16961696

1697+
if not use_old_parser():
1698+
test_cases = [
1699+
"""if 1:
1700+
with (
1701+
manager()
1702+
):
1703+
pass
1704+
""",
1705+
"""if 1:
1706+
with (
1707+
manager() as x
1708+
):
1709+
pass
1710+
""",
1711+
"""if 1:
1712+
with (
1713+
manager() as (x, y),
1714+
manager() as z,
1715+
):
1716+
pass
1717+
""",
1718+
"""if 1:
1719+
with (
1720+
manager(),
1721+
manager()
1722+
):
1723+
pass
1724+
""",
1725+
"""if 1:
1726+
with (
1727+
manager() as x,
1728+
manager() as y
1729+
):
1730+
pass
1731+
""",
1732+
"""if 1:
1733+
with (
1734+
manager() as x,
1735+
manager()
1736+
):
1737+
pass
1738+
""",
1739+
"""if 1:
1740+
with (
1741+
manager() as x,
1742+
manager() as y,
1743+
manager() as z,
1744+
):
1745+
pass
1746+
""",
1747+
"""if 1:
1748+
with (
1749+
manager() as x,
1750+
manager() as y,
1751+
manager(),
1752+
):
1753+
pass
1754+
""",
1755+
]
1756+
for case in test_cases:
1757+
with self.subTest(case=case):
1758+
compile(case, "<string>", "exec")
1759+
1760+
16971761
def test_if_else_expr(self):
16981762
# Test ifelse expressions in various cases
16991763
def _checkeval(msg, ret):

Parser/pegen/parse.c

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3031,9 +3031,9 @@ for_stmt_rule(Parser *p)
30313031
}
30323032

30333033
// with_stmt:
3034-
// | 'with' '(' ','.with_item+ ')' ':' block
3034+
// | 'with' '(' ','.with_item+ ','? ')' ':' block
30353035
// | 'with' ','.with_item+ ':' TYPE_COMMENT? block
3036-
// | ASYNC 'with' '(' ','.with_item+ ')' ':' block
3036+
// | ASYNC 'with' '(' ','.with_item+ ','? ')' ':' block
30373037
// | ASYNC 'with' ','.with_item+ ':' TYPE_COMMENT? block
30383038
static stmt_ty
30393039
with_stmt_rule(Parser *p)
@@ -3051,20 +3051,24 @@ with_stmt_rule(Parser *p)
30513051
UNUSED(start_lineno); // Only used by EXTRA macro
30523052
int start_col_offset = p->tokens[mark]->col_offset;
30533053
UNUSED(start_col_offset); // Only used by EXTRA macro
3054-
{ // 'with' '(' ','.with_item+ ')' ':' block
3054+
{ // 'with' '(' ','.with_item+ ','? ')' ':' block
30553055
asdl_seq * a;
30563056
asdl_seq* b;
30573057
Token * keyword;
30583058
Token * literal;
30593059
Token * literal_1;
30603060
Token * literal_2;
3061+
void *opt_var;
3062+
UNUSED(opt_var); // Silence compiler warnings
30613063
if (
30623064
(keyword = _PyPegen_expect_token(p, 519))
30633065
&&
30643066
(literal = _PyPegen_expect_token(p, 7))
30653067
&&
30663068
(a = _gather_38_rule(p))
30673069
&&
3070+
(opt_var = _PyPegen_expect_token(p, 12), 1)
3071+
&&
30683072
(literal_1 = _PyPegen_expect_token(p, 8))
30693073
&&
30703074
(literal_2 = _PyPegen_expect_token(p, 11))
@@ -3124,14 +3128,16 @@ with_stmt_rule(Parser *p)
31243128
}
31253129
p->mark = mark;
31263130
}
3127-
{ // ASYNC 'with' '(' ','.with_item+ ')' ':' block
3131+
{ // ASYNC 'with' '(' ','.with_item+ ','? ')' ':' block
31283132
asdl_seq * a;
31293133
Token * async_var;
31303134
asdl_seq* b;
31313135
Token * keyword;
31323136
Token * literal;
31333137
Token * literal_1;
31343138
Token * literal_2;
3139+
void *opt_var;
3140+
UNUSED(opt_var); // Silence compiler warnings
31353141
if (
31363142
(async_var = _PyPegen_expect_token(p, ASYNC))
31373143
&&
@@ -3141,6 +3147,8 @@ with_stmt_rule(Parser *p)
31413147
&&
31423148
(a = _gather_42_rule(p))
31433149
&&
3150+
(opt_var = _PyPegen_expect_token(p, 12), 1)
3151+
&&
31443152
(literal_1 = _PyPegen_expect_token(p, 8))
31453153
&&
31463154
(literal_2 = _PyPegen_expect_token(p, 11))

0 commit comments

Comments
 (0)