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

This commit is contained in:
Pablo Galindo 2020-10-03 20:45:55 +01:00 committed by GitHub
parent d646e91f5c
commit fb0a4651f1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 31 additions and 9 deletions

View File

@ -39,7 +39,7 @@ class SymbolTableFactory:
_newSymbolTable = SymbolTableFactory() _newSymbolTable = SymbolTableFactory()
class SymbolTable(object): class SymbolTable:
def __init__(self, raw_table, filename): def __init__(self, raw_table, filename):
self._table = raw_table self._table = raw_table
@ -52,7 +52,7 @@ class SymbolTable(object):
else: else:
kind = "%s " % self.__class__.__name__ 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) return "<{0}SymbolTable for module {1}>".format(kind, self._filename)
else: else:
return "<{0}SymbolTable for {1} in {2}>".format(kind, return "<{0}SymbolTable for {1} in {2}>".format(kind,
@ -124,7 +124,9 @@ class SymbolTable(object):
if sym is None: if sym is None:
flags = self._table.symbols[name] flags = self._table.symbols[name]
namespaces = self.__check_children(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 return sym
def get_symbols(self): def get_symbols(self):
@ -214,13 +216,14 @@ class Class(SymbolTable):
return self.__methods 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.__name = name
self.__flags = flags self.__flags = flags
self.__scope = (flags >> SCOPE_OFF) & SCOPE_MASK # like PyST_GetScope() self.__scope = (flags >> SCOPE_OFF) & SCOPE_MASK # like PyST_GetScope()
self.__namespaces = namespaces or () self.__namespaces = namespaces or ()
self.__module_scope = module_scope
def __repr__(self): def __repr__(self):
return "<symbol {0!r}>".format(self.__name) return "<symbol {0!r}>".format(self.__name)
@ -244,7 +247,8 @@ class Symbol(object):
def is_global(self): def is_global(self):
"""Return *True* if the sysmbol is global. """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): def is_nonlocal(self):
"""Return *True* if the symbol is nonlocal.""" """Return *True* if the symbol is nonlocal."""
@ -258,7 +262,8 @@ class Symbol(object):
def is_local(self): def is_local(self):
"""Return *True* if the symbol is local. """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): def is_annotated(self):
"""Return *True* if the symbol is annotated. """Return *True* if the symbol is annotated.

View File

@ -11,6 +11,8 @@ import sys
glob = 42 glob = 42
some_var = 12 some_var = 12
some_non_assigned_global_var = 11
some_assigned_global_var = 11
class Mine: class Mine:
instance_var = 24 instance_var = 24
@ -19,6 +21,8 @@ class Mine:
def spam(a, b, *var, **kw): def spam(a, b, *var, **kw):
global bar global bar
global some_assigned_global_var
some_assigned_global_var = 12
bar = 47 bar = 47
some_var = 10 some_var = 10
x = 23 x = 23
@ -88,14 +92,14 @@ class SymtableTest(unittest.TestCase):
def test_lineno(self): def test_lineno(self):
self.assertEqual(self.top.get_lineno(), 0) 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): def test_function_info(self):
func = self.spam func = self.spam
self.assertEqual(sorted(func.get_parameters()), ["a", "b", "kw", "var"]) self.assertEqual(sorted(func.get_parameters()), ["a", "b", "kw", "var"])
expected = ['a', 'b', 'internal', 'kw', 'other_internal', 'some_var', 'var', 'x'] expected = ['a', 'b', 'internal', 'kw', 'other_internal', 'some_var', 'var', 'x']
self.assertEqual(sorted(func.get_locals()), expected) 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",)) self.assertEqual(self.internal.get_frees(), ("x",))
def test_globals(self): def test_globals(self):
@ -106,6 +110,9 @@ class SymtableTest(unittest.TestCase):
self.assertFalse(self.internal.lookup("x").is_global()) self.assertFalse(self.internal.lookup("x").is_global())
self.assertFalse(self.Mine.lookup("instance_var").is_global()) self.assertFalse(self.Mine.lookup("instance_var").is_global())
self.assertTrue(self.spam.lookup("bar").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): def test_nonlocal(self):
self.assertFalse(self.spam.lookup("some_var").is_nonlocal()) self.assertFalse(self.spam.lookup("some_var").is_nonlocal())
@ -116,6 +123,9 @@ class SymtableTest(unittest.TestCase):
def test_local(self): def test_local(self):
self.assertTrue(self.spam.lookup("x").is_local()) self.assertTrue(self.spam.lookup("x").is_local())
self.assertFalse(self.spam.lookup("bar").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): def test_free(self):
self.assertTrue(self.internal.lookup("x").is_free()) self.assertTrue(self.internal.lookup("x").is_free())
@ -234,6 +244,10 @@ class SymtableTest(unittest.TestCase):
top = symtable.symtable(code, "?", "exec") top = symtable.symtable(code, "?", "exec")
self.assertIsNotNone(find_block(top, "\u017d")) 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__': if __name__ == '__main__':
unittest.main() unittest.main()

View File

@ -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.