diff --git a/Grammar/python.gram b/Grammar/python.gram index e4533b1a1b8..2f52bd7f2f6 100644 --- a/Grammar/python.gram +++ b/Grammar/python.gram @@ -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) } diff --git a/Lib/test/test_with.py b/Lib/test/test_with.py index b1d7a15b5e4..f21bf65fed8 100644 --- a/Lib/test/test_with.py +++ b/Lib/test/test_with.py @@ -7,7 +7,7 @@ __email__ = "mbland at acm dot org" import sys import unittest from collections import deque -from contextlib import _GeneratorContextManager, contextmanager +from contextlib import _GeneratorContextManager, contextmanager, nullcontext class MockContextManager(_GeneratorContextManager): @@ -641,6 +641,12 @@ class AssignmentTargetTestCase(unittest.TestCase): 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): diff --git a/Misc/NEWS.d/next/Core and Builtins/2020-10-09-10-55-50.bpo-41979.ImXIk2.rst b/Misc/NEWS.d/next/Core and Builtins/2020-10-09-10-55-50.bpo-41979.ImXIk2.rst new file mode 100644 index 00000000000..3250309ca22 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2020-10-09-10-55-50.bpo-41979.ImXIk2.rst @@ -0,0 +1 @@ +Star-unpacking is now allowed for with item's targets in the PEG parser. diff --git a/Parser/parser.c b/Parser/parser.c index 1bd74a38fbc..0d92256a3eb 100644 --- a/Parser/parser.c +++ b/Parser/parser.c @@ -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) { @@ -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; @@ -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; @@ -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) {