]> granicus.if.org Git - python/commitdiff
bpo-38469: Handle named expression scope with global/nonlocal keywords (GH-16755)
authorPablo Galindo <Pablogsal@gmail.com>
Mon, 14 Oct 2019 04:18:05 +0000 (05:18 +0100)
committerGitHub <noreply@github.com>
Mon, 14 Oct 2019 04:18:05 +0000 (05:18 +0100)
The symbol table handing of PEP572's assignment expressions is not resolving correctly the scope of some variables in presence of global/nonlocal keywords in conjunction with comprehensions.

Lib/test/test_named_expressions.py
Misc/NEWS.d/next/Core and Builtins/2019-10-13-23-41-38.bpo-38469.9kmuQj.rst [new file with mode: 0644]
Python/symtable.c

index b1027ce78006decdc22e4b8097a0f6697d8de51e..01e26c8dfaf259c37c70b61d6a8f506467cc9392 100644 (file)
@@ -1,6 +1,7 @@
 import os
 import unittest
 
+GLOBAL_VAR = None
 
 class NamedExpressionInvalidTest(unittest.TestCase):
 
@@ -470,5 +471,49 @@ spam()"""
                 self.assertEqual(ns["x"], 2)
                 self.assertEqual(ns["result"], [0, 1, 2])
 
+    def test_named_expression_global_scope(self):
+        sentinel = object()
+        global GLOBAL_VAR
+        def f():
+            global GLOBAL_VAR
+            [GLOBAL_VAR := sentinel for _ in range(1)]
+            self.assertEqual(GLOBAL_VAR, sentinel)
+        try:
+            f()
+            self.assertEqual(GLOBAL_VAR, sentinel)
+        finally:
+            GLOBAL_VAR = None
+
+    def test_named_expression_global_scope_no_global_keyword(self):
+        sentinel = object()
+        def f():
+            GLOBAL_VAR = None
+            [GLOBAL_VAR := sentinel for _ in range(1)]
+            self.assertEqual(GLOBAL_VAR, sentinel)
+        f()
+        self.assertEqual(GLOBAL_VAR, None)
+
+    def test_named_expression_nonlocal_scope(self):
+        sentinel = object()
+        def f():
+            nonlocal_var = None
+            def g():
+                nonlocal nonlocal_var
+                [nonlocal_var := sentinel for _ in range(1)]
+            g()
+            self.assertEqual(nonlocal_var, sentinel)
+        f()
+
+    def test_named_expression_nonlocal_scope_no_nonlocal_keyword(self):
+        sentinel = object()
+        def f():
+            nonlocal_var = None
+            def g():
+                [nonlocal_var := sentinel for _ in range(1)]
+            g()
+            self.assertEqual(nonlocal_var, None)
+        f()
+
+
 if __name__ == "__main__":
     unittest.main()
diff --git a/Misc/NEWS.d/next/Core and Builtins/2019-10-13-23-41-38.bpo-38469.9kmuQj.rst b/Misc/NEWS.d/next/Core and Builtins/2019-10-13-23-41-38.bpo-38469.9kmuQj.rst
new file mode 100644 (file)
index 0000000..328a1b7
--- /dev/null
@@ -0,0 +1,2 @@
+Fixed a bug where the scope of named expressions was not being resolved
+correctly in the presence of the *global* keyword. Patch by Pablo Galindo.
index f2453db69dd7dd07addab8abc18d9314f1f95db2..b8713588b9a91495c9c9a0702fa03d1970cfee36 100644 (file)
@@ -1467,10 +1467,16 @@ symtable_extend_namedexpr_scope(struct symtable *st, expr_ty e)
             continue;
         }
 
-        /* If we find a FunctionBlock entry, add as NONLOCAL/LOCAL */
+        /* If we find a FunctionBlock entry, add as GLOBAL/LOCAL or NONLOCAL/LOCAL */
         if (ste->ste_type == FunctionBlock) {
-            if (!symtable_add_def(st, target_name, DEF_NONLOCAL))
-                VISIT_QUIT(st, 0);
+            long target_in_scope = _PyST_GetSymbol(ste, target_name);
+            if (target_in_scope & DEF_GLOBAL) {
+                if (!symtable_add_def(st, target_name, DEF_GLOBAL))
+                    VISIT_QUIT(st, 0);
+            } else {
+                if (!symtable_add_def(st, target_name, DEF_NONLOCAL))
+                    VISIT_QUIT(st, 0);
+            }
             if (!symtable_record_directive(st, target_name, e->lineno, e->col_offset))
                 VISIT_QUIT(st, 0);