]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
[3.7] bpo-36541: lib2to3: Support named assignment expressions (GH-12702) (GH-19317)
authorTim Hatch <tim@timhatch.com>
Fri, 3 Apr 2020 19:14:15 +0000 (12:14 -0700)
committerGitHub <noreply@github.com>
Fri, 3 Apr 2020 19:14:15 +0000 (12:14 -0700)
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.

(cherry picked from commit 3c3aa4516c70753de06bb142b6793d01330fcf0f)

Co-authored-by: Tim Hatch <tim@timhatch.com>
Lib/lib2to3/Grammar.txt
Lib/lib2to3/pgen2/grammar.py
Lib/lib2to3/pgen2/token.py
Lib/lib2to3/pgen2/tokenize.py
Lib/lib2to3/tests/test_parser.py
Misc/NEWS.d/next/Library/2019-06-18-19-38-27.bpo-36541.XI8mi1.rst [new file with mode: 0644]

index 68b73868b58282fce8ad32809c6350f587706090..8ce7fd8a89da67dade690e8c993c1d66d090bd0a 100644 (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
 async_stmt: ASYNC (funcdef | with_stmt | for_stmt)
-if_stmt: 'if' test ':' suite ('elif' test ':' suite)* ['else' ':' suite]
-while_stmt: 'while' test ':' suite ['else' ':' suite]
+if_stmt: 'if' namedexpr_test ':' suite ('elif' namedexpr_test ':' suite)* ['else' ':' suite]
+while_stmt: 'while' namedexpr_test ':' suite ['else' ':' suite]
 for_stmt: 'for' exprlist 'in' testlist ':' suite ['else' ':' suite]
 try_stmt: ('try' ':' suite
            ((except_clause ':' suite)+
@@ -91,6 +91,7 @@ testlist_safe: old_test [(',' old_test)+ [',']]
 old_test: or_test | old_lambdef
 old_lambdef: 'lambda' [varargslist] ':' old_test
 
+namedexpr_test: test [':=' test]
 test: or_test ['if' or_test 'else' test] | lambdef
 or_test: and_test ('or' and_test)*
 and_test: not_test ('and' not_test)*
@@ -111,8 +112,8 @@ atom: ('(' [yield_expr|testlist_gexp] ')' |
        '{' [dictsetmaker] '}' |
        '`' testlist1 '`' |
        NAME | NUMBER | STRING+ | '.' '.' '.')
-listmaker: (test|star_expr) ( comp_for | (',' (test|star_expr))* [','] )
-testlist_gexp: (test|star_expr) ( comp_for | (',' (test|star_expr))* [','] )
+listmaker: (namedexpr_test|star_expr) ( comp_for | (',' (namedexpr_test|star_expr))* [','] )
+testlist_gexp: (namedexpr_test|star_expr) ( comp_for | (',' (namedexpr_test|star_expr))* [','] )
 lambdef: 'lambda' [varargslist] ':' test
 trailer: '(' [arglist] ')' | '[' subscriptlist ']' | '.' NAME
 subscriptlist: subscript (',' subscript)* [',']
@@ -137,6 +138,7 @@ arglist: argument (',' argument)* [',']
 # multiple (test comp_for) arguments are blocked; keyword unpackings
 # that precede iterable unpackings are blocked; etc.
 argument: ( test [comp_for] |
+            test ':=' test |
             test '=' test |
             '**' test |
                '*' test )
index 088c58bfa99c1b1b0ea5f269604e919e74685c3c..997fdf530f72bc38d1a3e8b4f0f0368e890cc30f 100644 (file)
@@ -202,6 +202,7 @@ opmap_raw = """
 // DOUBLESLASH
 //= DOUBLESLASHEQUAL
 -> RARROW
+:= COLONEQUAL
 """
 
 opmap = {}
index 1a679554d2db4e86c8d56eaca49b85113c1cb312..5f6612f5b30681dab79e4f71800850202c43a9aa 100755 (executable)
@@ -65,7 +65,8 @@ RARROW = 55
 AWAIT = 56
 ASYNC = 57
 ERRORTOKEN = 58
-N_TOKENS = 59
+COLONEQUAL = 59
+N_TOKENS = 60
 NT_OFFSET = 256
 #--end constants--
 
index 279d322971da991a0a00e157a8b0a0e75f2bdfb7..94dd792805eaaebf195283c7ea7a9ec320eb3656 100644 (file)
@@ -93,7 +93,7 @@ Operator = group(r"\*\*=?", r">>=?", r"<<=?", r"<>", r"!=",
                  r"~")
 
 Bracket = '[][(){}]'
-Special = group(r'\r?\n', r'[:;.,`@]')
+Special = group(r'\r?\n', r':=', r'[:;.,`@]')
 Funny = group(Operator, Bracket, Special)
 
 PlainToken = group(Number, Funny, String, Name)
index 7ec881e01a9f889f822cfcb71514b007436da0ef..753c846b7be7a7be563e377f8e2992f5e64cf506 100644 (file)
@@ -629,6 +629,21 @@ class TestLiterals(GrammarTest):
         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")
+
+
 def diff_texts(a, b, filename):
     a = a.splitlines()
     b = b.splitlines()
diff --git a/Misc/NEWS.d/next/Library/2019-06-18-19-38-27.bpo-36541.XI8mi1.rst b/Misc/NEWS.d/next/Library/2019-06-18-19-38-27.bpo-36541.XI8mi1.rst
new file mode 100644 (file)
index 0000000..e7b9dd6
--- /dev/null
@@ -0,0 +1,2 @@
+lib2to3 now recognizes named assignment expressions (the walrus operator,
+``:=``)