Skip to content

Commit 42c9f0f

Browse files
authored
bpo-36541: Add lib2to3 grammar PEP-570 pos-only arg parsing (GH-23759)
Add positional only args support to lib2to3 pgen2. This adds 3.8's PEP-570 support to lib2to3's pgen2. lib2to3, while being deprecated is still used by things to parse all versions of Python code today. We need it to support parsing modern 3.8 and 3.9 constructs. Also add tests for complex *expr and **expr's.
1 parent f5e97b7 commit 42c9f0f

File tree

3 files changed

+79
-6
lines changed

3 files changed

+79
-6
lines changed

Lib/lib2to3/Grammar.txt

Lines changed: 46 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,55 @@ decorated: decorators (classdef | funcdef | async_funcdef)
1818
async_funcdef: ASYNC funcdef
1919
funcdef: 'def' NAME parameters ['->' test] ':' suite
2020
parameters: '(' [typedargslist] ')'
21-
typedargslist: ((tfpdef ['=' test] ',')*
22-
('*' [tname] (',' tname ['=' test])* [',' ['**' tname [',']]] | '**' tname [','])
23-
| tfpdef ['=' test] (',' tfpdef ['=' test])* [','])
21+
22+
# The following definition for typedarglist is equivalent to this set of rules:
23+
#
24+
# arguments = argument (',' argument)*
25+
# argument = tfpdef ['=' test]
26+
# kwargs = '**' tname [',']
27+
# args = '*' [tname]
28+
# kwonly_kwargs = (',' argument)* [',' [kwargs]]
29+
# args_kwonly_kwargs = args kwonly_kwargs | kwargs
30+
# poskeyword_args_kwonly_kwargs = arguments [',' [args_kwonly_kwargs]]
31+
# typedargslist_no_posonly = poskeyword_args_kwonly_kwargs | args_kwonly_kwargs
32+
# typedarglist = arguments ',' '/' [',' [typedargslist_no_posonly]])|(typedargslist_no_posonly)"
33+
#
34+
# It needs to be fully expanded to allow our LL(1) parser to work on it.
35+
36+
typedargslist: tfpdef ['=' test] (',' tfpdef ['=' test])* ',' '/' [
37+
',' [((tfpdef ['=' test] ',')* ('*' [tname] (',' tname ['=' test])*
38+
[',' ['**' tname [',']]] | '**' tname [','])
39+
| tfpdef ['=' test] (',' tfpdef ['=' test])* [','])]
40+
] | ((tfpdef ['=' test] ',')* ('*' [tname] (',' tname ['=' test])*
41+
[',' ['**' tname [',']]] | '**' tname [','])
42+
| tfpdef ['=' test] (',' tfpdef ['=' test])* [','])
43+
2444
tname: NAME [':' test]
2545
tfpdef: tname | '(' tfplist ')'
2646
tfplist: tfpdef (',' tfpdef)* [',']
27-
varargslist: ((vfpdef ['=' test] ',')*
28-
('*' [vname] (',' vname ['=' test])* [',' ['**' vname [',']]] | '**' vname [','])
29-
| vfpdef ['=' test] (',' vfpdef ['=' test])* [','])
47+
48+
# The following definition for varargslist is equivalent to this set of rules:
49+
#
50+
# arguments = argument (',' argument )*
51+
# argument = vfpdef ['=' test]
52+
# kwargs = '**' vname [',']
53+
# args = '*' [vname]
54+
# kwonly_kwargs = (',' argument )* [',' [kwargs]]
55+
# args_kwonly_kwargs = args kwonly_kwargs | kwargs
56+
# poskeyword_args_kwonly_kwargs = arguments [',' [args_kwonly_kwargs]]
57+
# vararglist_no_posonly = poskeyword_args_kwonly_kwargs | args_kwonly_kwargs
58+
# varargslist = arguments ',' '/' [','[(vararglist_no_posonly)]] | (vararglist_no_posonly)
59+
#
60+
# It needs to be fully expanded to allow our LL(1) parser to work on it.
61+
62+
varargslist: vfpdef ['=' test ](',' vfpdef ['=' test])* ',' '/' [',' [
63+
((vfpdef ['=' test] ',')* ('*' [vname] (',' vname ['=' test])*
64+
[',' ['**' vname [',']]] | '**' vname [','])
65+
| vfpdef ['=' test] (',' vfpdef ['=' test])* [','])
66+
]] | ((vfpdef ['=' test] ',')*
67+
('*' [vname] (',' vname ['=' test])* [',' ['**' vname [',']]]| '**' vname [','])
68+
| vfpdef ['=' test] (',' vfpdef ['=' test])* [','])
69+
3070
vname: NAME
3171
vfpdef: vname | '(' vfplist ')'
3272
vfplist: vfpdef (',' vfpdef)* [',']

Lib/lib2to3/tests/test_parser.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,12 @@ def test_dict_display_1(self):
272272
def test_dict_display_2(self):
273273
self.validate("""{**{}, 3:4, **{5:6, 7:8}}""")
274274

275+
def test_complex_star_expression(self):
276+
self.validate("func(* [] or [1])")
277+
278+
def test_complex_double_star_expression(self):
279+
self.validate("func(**{1: 3} if False else {x: x for x in range(3)})")
280+
275281
def test_argument_unpacking_1(self):
276282
self.validate("""f(a, *b, *c, d)""")
277283

@@ -630,6 +636,7 @@ def test_multiline_str_literals(self):
630636

631637

632638
class TestNamedAssignments(GrammarTest):
639+
"""Also known as the walrus operator."""
633640

634641
def test_named_assignment_if(self):
635642
driver.parse_string("if f := x(): pass\n")
@@ -644,6 +651,30 @@ def test_named_assignment_listcomp(self):
644651
driver.parse_string("[(lastNum := num) == 1 for num in [1, 2, 3]]\n")
645652

646653

654+
class TestPositionalOnlyArgs(GrammarTest):
655+
656+
def test_one_pos_only_arg(self):
657+
driver.parse_string("def one_pos_only_arg(a, /): pass\n")
658+
659+
def test_all_markers(self):
660+
driver.parse_string(
661+
"def all_markers(a, b=2, /, c, d=4, *, e=5, f): pass\n")
662+
663+
def test_all_with_args_and_kwargs(self):
664+
driver.parse_string(
665+
"""def all_markers_with_args_and_kwargs(
666+
aa, b, /, _cc, d, *args, e, f_f, **kwargs,
667+
):
668+
pass\n""")
669+
670+
def test_lambda_soup(self):
671+
driver.parse_string(
672+
"lambda a, b, /, c, d, *args, e, f, **kw: kw\n")
673+
674+
def test_only_positional_or_keyword(self):
675+
driver.parse_string("def func(a,b,/,*,g,e=3): pass\n")
676+
677+
647678
class TestPickleableException(unittest.TestCase):
648679
def test_ParseError(self):
649680
err = ParseError('msg', 2, None, (1, 'context'))
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fixed lib2to3.pgen2 to be able to parse PEP-570 positional only argument
2+
syntax.

0 commit comments

Comments
 (0)