[Python-checkins] bpo-41840: Report module-level globals as both local and global in the symtable module (GH-22391)

Pablo Galindo webhook-mailer at python.org
Sat Oct 3 15:45:59 EDT 2020


https://github.com/python/cpython/commit/fb0a4651f1be4ad936f8277478f73f262d8eeb72
commit: fb0a4651f1be4ad936f8277478f73f262d8eeb72
branch: master
author: Pablo Galindo <Pablogsal at gmail.com>
committer: GitHub <noreply at github.com>
date: 2020-10-03T20:45:55+01:00
summary:

bpo-41840: Report module-level globals as both local and global in the symtable module (GH-22391)

files:
A Misc/NEWS.d/next/Library/2020-09-23-23-17-59.bpo-41840.QRFr4L.rst
M Lib/symtable.py
M Lib/test/test_symtable.py

diff --git a/Lib/symtable.py b/Lib/symtable.py
index 9ff27ef74ffe8..98db1e2557d37 100644
--- a/Lib/symtable.py
+++ b/Lib/symtable.py
@@ -39,7 +39,7 @@ def __call__(self, table, filename):
 _newSymbolTable = SymbolTableFactory()
 
 
-class SymbolTable(object):
+class SymbolTable:
 
     def __init__(self, raw_table, filename):
         self._table = raw_table
@@ -52,7 +52,7 @@ def __repr__(self):
         else:
             kind = "%s " % self.__class__.__name__
 
-        if self._table.name == "global":
+        if self._table.name == "top":
             return "<{0}SymbolTable for module {1}>".format(kind, self._filename)
         else:
             return "<{0}SymbolTable for {1} in {2}>".format(kind,
@@ -124,7 +124,9 @@ def lookup(self, name):
         if sym is None:
             flags = self._table.symbols[name]
             namespaces = self.__check_children(name)
-            sym = self._symbols[name] = Symbol(name, flags, namespaces)
+            module_scope = (self._table.name == "top")
+            sym = self._symbols[name] = Symbol(name, flags, namespaces,
+                                               module_scope=module_scope)
         return sym
 
     def get_symbols(self):
@@ -214,13 +216,14 @@ def get_methods(self):
         return self.__methods
 
 
-class Symbol(object):
+class Symbol:
 
-    def __init__(self, name, flags, namespaces=None):
+    def __init__(self, name, flags, namespaces=None, *, module_scope=False):
         self.__name = name
         self.__flags = flags
         self.__scope = (flags >> SCOPE_OFF) & SCOPE_MASK # like PyST_GetScope()
         self.__namespaces = namespaces or ()
+        self.__module_scope = module_scope
 
     def __repr__(self):
         return "<symbol {0!r}>".format(self.__name)
@@ -244,7 +247,8 @@ def is_parameter(self):
     def is_global(self):
         """Return *True* if the sysmbol is global.
         """
-        return bool(self.__scope in (GLOBAL_IMPLICIT, GLOBAL_EXPLICIT))
+        return bool(self.__scope in (GLOBAL_IMPLICIT, GLOBAL_EXPLICIT)
+                    or (self.__module_scope and self.__flags & DEF_BOUND))
 
     def is_nonlocal(self):
         """Return *True* if the symbol is nonlocal."""
@@ -258,7 +262,8 @@ def is_declared_global(self):
     def is_local(self):
         """Return *True* if the symbol is local.
         """
-        return bool(self.__scope in (LOCAL, CELL))
+        return bool(self.__scope in (LOCAL, CELL)
+                    or (self.__module_scope and self.__flags & DEF_BOUND))
 
     def is_annotated(self):
         """Return *True* if the symbol is annotated.
diff --git a/Lib/test/test_symtable.py b/Lib/test/test_symtable.py
index fa514917a1f02..a30e53496039b 100644
--- a/Lib/test/test_symtable.py
+++ b/Lib/test/test_symtable.py
@@ -11,6 +11,8 @@
 
 glob = 42
 some_var = 12
+some_non_assigned_global_var = 11
+some_assigned_global_var = 11
 
 class Mine:
     instance_var = 24
@@ -19,6 +21,8 @@ def a_method(p1, p2):
 
 def spam(a, b, *var, **kw):
     global bar
+    global some_assigned_global_var
+    some_assigned_global_var = 12
     bar = 47
     some_var = 10
     x = 23
@@ -88,14 +92,14 @@ def test_children(self):
 
     def test_lineno(self):
         self.assertEqual(self.top.get_lineno(), 0)
-        self.assertEqual(self.spam.get_lineno(), 12)
+        self.assertEqual(self.spam.get_lineno(), 14)
 
     def test_function_info(self):
         func = self.spam
         self.assertEqual(sorted(func.get_parameters()), ["a", "b", "kw", "var"])
         expected = ['a', 'b', 'internal', 'kw', 'other_internal', 'some_var', 'var', 'x']
         self.assertEqual(sorted(func.get_locals()), expected)
-        self.assertEqual(sorted(func.get_globals()), ["bar", "glob"])
+        self.assertEqual(sorted(func.get_globals()), ["bar", "glob", "some_assigned_global_var"])
         self.assertEqual(self.internal.get_frees(), ("x",))
 
     def test_globals(self):
@@ -106,6 +110,9 @@ def test_globals(self):
         self.assertFalse(self.internal.lookup("x").is_global())
         self.assertFalse(self.Mine.lookup("instance_var").is_global())
         self.assertTrue(self.spam.lookup("bar").is_global())
+        # Module-scope globals are both global and local
+        self.assertTrue(self.top.lookup("some_non_assigned_global_var").is_global())
+        self.assertTrue(self.top.lookup("some_assigned_global_var").is_global())
 
     def test_nonlocal(self):
         self.assertFalse(self.spam.lookup("some_var").is_nonlocal())
@@ -116,6 +123,9 @@ def test_nonlocal(self):
     def test_local(self):
         self.assertTrue(self.spam.lookup("x").is_local())
         self.assertFalse(self.spam.lookup("bar").is_local())
+        # Module-scope globals are both global and local
+        self.assertTrue(self.top.lookup("some_non_assigned_global_var").is_local())
+        self.assertTrue(self.top.lookup("some_assigned_global_var").is_local())
 
     def test_free(self):
         self.assertTrue(self.internal.lookup("x").is_free())
@@ -234,6 +244,10 @@ def test_bytes(self):
         top = symtable.symtable(code, "?", "exec")
         self.assertIsNotNone(find_block(top, "\u017d"))
 
+    def test_symtable_repr(self):
+        self.assertEqual(str(self.top), "<SymbolTable for module ?>")
+        self.assertEqual(str(self.spam), "<Function SymbolTable for spam in ?>")
+
 
 if __name__ == '__main__':
     unittest.main()
diff --git a/Misc/NEWS.d/next/Library/2020-09-23-23-17-59.bpo-41840.QRFr4L.rst b/Misc/NEWS.d/next/Library/2020-09-23-23-17-59.bpo-41840.QRFr4L.rst
new file mode 100644
index 0000000000000..e96942d8ebd07
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2020-09-23-23-17-59.bpo-41840.QRFr4L.rst
@@ -0,0 +1,3 @@
+Fix a bug in the :mod:`symtable` module that was causing module-scope global
+variables to not be reported as both local and global. Patch by Pablo
+Galindo.



More information about the Python-checkins mailing list