From fb0a4651f1be4ad936f8277478f73f262d8eeb72 Mon Sep 17 00:00:00 2001 From: Pablo Galindo Date: Sat, 3 Oct 2020 20:45:55 +0100 Subject: [PATCH] bpo-41840: Report module-level globals as both local and global in the symtable module (GH-22391) --- Lib/symtable.py | 19 ++++++++++++------- Lib/test/test_symtable.py | 18 ++++++++++++++++-- .../2020-09-23-23-17-59.bpo-41840.QRFr4L.rst | 3 +++ 3 files changed, 31 insertions(+), 9 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2020-09-23-23-17-59.bpo-41840.QRFr4L.rst diff --git a/Lib/symtable.py b/Lib/symtable.py index 9ff27ef74ff..98db1e2557d 100644 --- a/Lib/symtable.py +++ b/Lib/symtable.py @@ -39,7 +39,7 @@ class SymbolTableFactory: _newSymbolTable = SymbolTableFactory() -class SymbolTable(object): +class SymbolTable: def __init__(self, raw_table, filename): self._table = raw_table @@ -52,7 +52,7 @@ class SymbolTable(object): 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 @@ class SymbolTable(object): 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 @@ class Class(SymbolTable): 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 "".format(self.__name) @@ -244,7 +247,8 @@ class Symbol(object): 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 @@ class Symbol(object): 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 fa514917a1f..a30e5349603 100644 --- a/Lib/test/test_symtable.py +++ b/Lib/test/test_symtable.py @@ -11,6 +11,8 @@ import sys 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 @@ class Mine: 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 @@ class SymtableTest(unittest.TestCase): 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 @@ class SymtableTest(unittest.TestCase): 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 @@ class SymtableTest(unittest.TestCase): 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 @@ class SymtableTest(unittest.TestCase): top = symtable.symtable(code, "?", "exec") self.assertIsNotNone(find_block(top, "\u017d")) + def test_symtable_repr(self): + self.assertEqual(str(self.top), "") + self.assertEqual(str(self.spam), "") + 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 00000000000..e96942d8ebd --- /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.