Skip to content
Merged
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
2 changes: 1 addition & 1 deletion Grammar/python.gram
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ with_stmt[stmt_ty]:
| ASYNC 'with' a[asdl_withitem_seq*]=','.with_item+ ':' tc=[TYPE_COMMENT] b=block {
CHECK_VERSION(5, "Async with statements are", _Py_AsyncWith(a, b, NEW_TYPE_COMMENT(p, tc), EXTRA)) }
with_item[withitem_ty]:
| e=expression 'as' t=target &(',' | ')' | ':') { _Py_withitem(e, t, p->arena) }
| e=expression 'as' t=star_target &(',' | ')' | ':') { _Py_withitem(e, t, p->arena) }
| invalid_with_item
| e=expression { _Py_withitem(e, NULL, p->arena) }

Expand Down
8 changes: 7 additions & 1 deletion Lib/test/test_with.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import sys
import unittest
from collections import deque
from contextlib import _GeneratorContextManager, contextmanager
from contextlib import _GeneratorContextManager, contextmanager, nullcontext


class MockContextManager(_GeneratorContextManager):
Expand Down Expand Up @@ -641,6 +641,12 @@ class B: pass
self.assertEqual(blah.two, 2)
self.assertEqual(blah.three, 3)

def testWithExtendedTargets(self):
with nullcontext(range(1, 5)) as (a, *b, c):
self.assertEqual(a, 1)
self.assertEqual(b, [2, 3])
self.assertEqual(c, 4)


class ExitSwallowsExceptionTestCase(unittest.TestCase):

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Star-unpacking is now allowed for with item's targets in the PEG parser.
15 changes: 9 additions & 6 deletions Parser/parser.c
Original file line number Diff line number Diff line change
Expand Up @@ -4290,7 +4290,10 @@ with_stmt_rule(Parser *p)
return _res;
}

// with_item: expression 'as' target &(',' | ')' | ':') | invalid_with_item | expression
// with_item:
// | expression 'as' star_target &(',' | ')' | ':')
// | invalid_with_item
// | expression
static withitem_ty
with_item_rule(Parser *p)
{
Expand All @@ -4301,12 +4304,12 @@ with_item_rule(Parser *p)
}
withitem_ty _res = NULL;
int _mark = p->mark;
{ // expression 'as' target &(',' | ')' | ':')
{ // expression 'as' star_target &(',' | ')' | ':')
if (p->error_indicator) {
D(p->level--);
return NULL;
}
D(fprintf(stderr, "%*c> with_item[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "expression 'as' target &(',' | ')' | ':')"));
D(fprintf(stderr, "%*c> with_item[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "expression 'as' star_target &(',' | ')' | ':')"));
Token * _keyword;
expr_ty e;
expr_ty t;
Expand All @@ -4315,12 +4318,12 @@ with_item_rule(Parser *p)
&&
(_keyword = _PyPegen_expect_token(p, 520)) // token='as'
&&
(t = target_rule(p)) // target
(t = star_target_rule(p)) // star_target
&&
_PyPegen_lookahead(1, _tmp_47_rule, p)
)
{
D(fprintf(stderr, "%*c+ with_item[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "expression 'as' target &(',' | ')' | ':')"));
D(fprintf(stderr, "%*c+ with_item[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "expression 'as' star_target &(',' | ')' | ':')"));
_res = _Py_withitem ( e , t , p -> arena );
if (_res == NULL && PyErr_Occurred()) {
p->error_indicator = 1;
Expand All @@ -4331,7 +4334,7 @@ with_item_rule(Parser *p)
}
p->mark = _mark;
D(fprintf(stderr, "%*c%s with_item[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "expression 'as' target &(',' | ')' | ':')"));
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "expression 'as' star_target &(',' | ')' | ':')"));
}
{ // invalid_with_item
if (p->error_indicator) {
Expand Down