lib2to3: Support named assignment expressions (GH-12702)

There are two copies of the grammar -- the one used by Python itself as
Grammar/Grammar, and the one used by lib2to3 which has necessarily diverged at
Lib/lib2to3/Grammar.txt because it needs to support older syntax an we want it
to be reasonable stable to avoid requiring fixer rewrites.

This brings suport for syntax like `if x:= foo():` to match what the live
Python grammar does.

This should've been added at the time of the walrus operator itself, but lib2to3 being
independent is often overlooked.  So we do consider this a bugfix rather than enhancement.
This commit is contained in:
Tim Hatch 2020-04-02 15:34:54 -07:00 committed by GitHub
parent 45217af29c
commit 3c3aa4516c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 27 additions and 6 deletions

View File

@ -67,8 +67,8 @@ assert_stmt: 'assert' test [',' test]
compound_stmt: if_stmt | while_stmt | for_stmt | try_stmt | with_stmt | funcdef | classdef | decorated | async_stmt compound_stmt: if_stmt | while_stmt | for_stmt | try_stmt | with_stmt | funcdef | classdef | decorated | async_stmt
async_stmt: ASYNC (funcdef | with_stmt | for_stmt) async_stmt: ASYNC (funcdef | with_stmt | for_stmt)
if_stmt: 'if' test ':' suite ('elif' test ':' suite)* ['else' ':' suite] if_stmt: 'if' namedexpr_test ':' suite ('elif' namedexpr_test ':' suite)* ['else' ':' suite]
while_stmt: 'while' test ':' suite ['else' ':' suite] while_stmt: 'while' namedexpr_test ':' suite ['else' ':' suite]
for_stmt: 'for' exprlist 'in' testlist ':' suite ['else' ':' suite] for_stmt: 'for' exprlist 'in' testlist ':' suite ['else' ':' suite]
try_stmt: ('try' ':' suite try_stmt: ('try' ':' suite
((except_clause ':' suite)+ ((except_clause ':' suite)+
@ -91,6 +91,7 @@ testlist_safe: old_test [(',' old_test)+ [',']]
old_test: or_test | old_lambdef old_test: or_test | old_lambdef
old_lambdef: 'lambda' [varargslist] ':' old_test old_lambdef: 'lambda' [varargslist] ':' old_test
namedexpr_test: test [':=' test]
test: or_test ['if' or_test 'else' test] | lambdef test: or_test ['if' or_test 'else' test] | lambdef
or_test: and_test ('or' and_test)* or_test: and_test ('or' and_test)*
and_test: not_test ('and' not_test)* and_test: not_test ('and' not_test)*
@ -111,8 +112,8 @@ atom: ('(' [yield_expr|testlist_gexp] ')' |
'{' [dictsetmaker] '}' | '{' [dictsetmaker] '}' |
'`' testlist1 '`' | '`' testlist1 '`' |
NAME | NUMBER | STRING+ | '.' '.' '.') NAME | NUMBER | STRING+ | '.' '.' '.')
listmaker: (test|star_expr) ( comp_for | (',' (test|star_expr))* [','] ) listmaker: (namedexpr_test|star_expr) ( comp_for | (',' (namedexpr_test|star_expr))* [','] )
testlist_gexp: (test|star_expr) ( comp_for | (',' (test|star_expr))* [','] ) testlist_gexp: (namedexpr_test|star_expr) ( comp_for | (',' (namedexpr_test|star_expr))* [','] )
lambdef: 'lambda' [varargslist] ':' test lambdef: 'lambda' [varargslist] ':' test
trailer: '(' [arglist] ')' | '[' subscriptlist ']' | '.' NAME trailer: '(' [arglist] ')' | '[' subscriptlist ']' | '.' NAME
subscriptlist: subscript (',' subscript)* [','] subscriptlist: subscript (',' subscript)* [',']
@ -137,6 +138,7 @@ arglist: argument (',' argument)* [',']
# multiple (test comp_for) arguments are blocked; keyword unpackings # multiple (test comp_for) arguments are blocked; keyword unpackings
# that precede iterable unpackings are blocked; etc. # that precede iterable unpackings are blocked; etc.
argument: ( test [comp_for] | argument: ( test [comp_for] |
test ':=' test |
test '=' test | test '=' test |
'**' test | '**' test |
'*' test ) '*' test )

View File

@ -178,6 +178,7 @@ opmap_raw = """
// DOUBLESLASH // DOUBLESLASH
//= DOUBLESLASHEQUAL //= DOUBLESLASHEQUAL
-> RARROW -> RARROW
:= COLONEQUAL
""" """
opmap = {} opmap = {}

View File

@ -65,7 +65,8 @@ RARROW = 55
AWAIT = 56 AWAIT = 56
ASYNC = 57 ASYNC = 57
ERRORTOKEN = 58 ERRORTOKEN = 58
N_TOKENS = 59 COLONEQUAL = 59
N_TOKENS = 60
NT_OFFSET = 256 NT_OFFSET = 256
#--end constants-- #--end constants--

View File

@ -93,7 +93,7 @@ Operator = group(r"\*\*=?", r">>=?", r"<<=?", r"<>", r"!=",
r"~") r"~")
Bracket = '[][(){}]' Bracket = '[][(){}]'
Special = group(r'\r?\n', r'[:;.,`@]') Special = group(r'\r?\n', r':=', r'[:;.,`@]')
Funny = group(Operator, Bracket, Special) Funny = group(Operator, Bracket, Special)
PlainToken = group(Number, Funny, String, Name) PlainToken = group(Number, Funny, String, Name)

View File

@ -629,6 +629,21 @@ class TestLiterals(GrammarTest):
self.validate(s) self.validate(s)
class TestNamedAssignments(GrammarTest):
def test_named_assignment_if(self):
driver.parse_string("if f := x(): pass\n")
def test_named_assignment_while(self):
driver.parse_string("while f := x(): pass\n")
def test_named_assignment_generator(self):
driver.parse_string("any((lastNum := num) == 1 for num in [1, 2, 3])\n")
def test_named_assignment_listcomp(self):
driver.parse_string("[(lastNum := num) == 1 for num in [1, 2, 3]]\n")
class TestPickleableException(unittest.TestCase): class TestPickleableException(unittest.TestCase):
def test_ParseError(self): def test_ParseError(self):
err = ParseError('msg', 2, None, (1, 'context')) err = ParseError('msg', 2, None, (1, 'context'))

View File

@ -0,0 +1,2 @@
lib2to3 now recognizes named assignment expressions (the walrus operator,
``:=``)