Skip to content

Commit fb0a465

Browse files
authored
bpo-41840: Report module-level globals as both local and global in the symtable module (GH-22391)
1 parent d646e91 commit fb0a465

File tree

3 files changed

+31
-9
lines changed

3 files changed

+31
-9
lines changed

Lib/symtable.py

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ def __call__(self, table, filename):
3939
_newSymbolTable = SymbolTableFactory()
4040

4141

42-
class SymbolTable(object):
42+
class SymbolTable:
4343

4444
def __init__(self, raw_table, filename):
4545
self._table = raw_table
@@ -52,7 +52,7 @@ def __repr__(self):
5252
else:
5353
kind = "%s " % self.__class__.__name__
5454

55-
if self._table.name == "global":
55+
if self._table.name == "top":
5656
return "<{0}SymbolTable for module {1}>".format(kind, self._filename)
5757
else:
5858
return "<{0}SymbolTable for {1} in {2}>".format(kind,
@@ -124,7 +124,9 @@ def lookup(self, name):
124124
if sym is None:
125125
flags = self._table.symbols[name]
126126
namespaces = self.__check_children(name)
127-
sym = self._symbols[name] = Symbol(name, flags, namespaces)
127+
module_scope = (self._table.name == "top")
128+
sym = self._symbols[name] = Symbol(name, flags, namespaces,
129+
module_scope=module_scope)
128130
return sym
129131

130132
def get_symbols(self):
@@ -214,13 +216,14 @@ def get_methods(self):
214216
return self.__methods
215217

216218

217-
class Symbol(object):
219+
class Symbol:
218220

219-
def __init__(self, name, flags, namespaces=None):
221+
def __init__(self, name, flags, namespaces=None, *, module_scope=False):
220222
self.__name = name
221223
self.__flags = flags
222224
self.__scope = (flags >> SCOPE_OFF) & SCOPE_MASK # like PyST_GetScope()
223225
self.__namespaces = namespaces or ()
226+
self.__module_scope = module_scope
224227

225228
def __repr__(self):
226229
return "<symbol {0!r}>".format(self.__name)
@@ -244,7 +247,8 @@ def is_parameter(self):
244247
def is_global(self):
245248
"""Return *True* if the sysmbol is global.
246249
"""
247-
return bool(self.__scope in (GLOBAL_IMPLICIT, GLOBAL_EXPLICIT))
250+
return bool(self.__scope in (GLOBAL_IMPLICIT, GLOBAL_EXPLICIT)
251+
or (self.__module_scope and self.__flags & DEF_BOUND))
248252

249253
def is_nonlocal(self):
250254
"""Return *True* if the symbol is nonlocal."""
@@ -258,7 +262,8 @@ def is_declared_global(self):
258262
def is_local(self):
259263
"""Return *True* if the symbol is local.
260264
"""
261-
return bool(self.__scope in (LOCAL, CELL))
265+
return bool(self.__scope in (LOCAL, CELL)
266+
or (self.__module_scope and self.__flags & DEF_BOUND))
262267

263268
def is_annotated(self):
264269
"""Return *True* if the symbol is annotated.

Lib/test/test_symtable.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
1212
glob = 42
1313
some_var = 12
14+
some_non_assigned_global_var = 11
15+
some_assigned_global_var = 11
1416
1517
class Mine:
1618
instance_var = 24
@@ -19,6 +21,8 @@ def a_method(p1, p2):
1921
2022
def spam(a, b, *var, **kw):
2123
global bar
24+
global some_assigned_global_var
25+
some_assigned_global_var = 12
2226
bar = 47
2327
some_var = 10
2428
x = 23
@@ -88,14 +92,14 @@ def test_children(self):
8892

8993
def test_lineno(self):
9094
self.assertEqual(self.top.get_lineno(), 0)
91-
self.assertEqual(self.spam.get_lineno(), 12)
95+
self.assertEqual(self.spam.get_lineno(), 14)
9296

9397
def test_function_info(self):
9498
func = self.spam
9599
self.assertEqual(sorted(func.get_parameters()), ["a", "b", "kw", "var"])
96100
expected = ['a', 'b', 'internal', 'kw', 'other_internal', 'some_var', 'var', 'x']
97101
self.assertEqual(sorted(func.get_locals()), expected)
98-
self.assertEqual(sorted(func.get_globals()), ["bar", "glob"])
102+
self.assertEqual(sorted(func.get_globals()), ["bar", "glob", "some_assigned_global_var"])
99103
self.assertEqual(self.internal.get_frees(), ("x",))
100104

101105
def test_globals(self):
@@ -106,6 +110,9 @@ def test_globals(self):
106110
self.assertFalse(self.internal.lookup("x").is_global())
107111
self.assertFalse(self.Mine.lookup("instance_var").is_global())
108112
self.assertTrue(self.spam.lookup("bar").is_global())
113+
# Module-scope globals are both global and local
114+
self.assertTrue(self.top.lookup("some_non_assigned_global_var").is_global())
115+
self.assertTrue(self.top.lookup("some_assigned_global_var").is_global())
109116

110117
def test_nonlocal(self):
111118
self.assertFalse(self.spam.lookup("some_var").is_nonlocal())
@@ -116,6 +123,9 @@ def test_nonlocal(self):
116123
def test_local(self):
117124
self.assertTrue(self.spam.lookup("x").is_local())
118125
self.assertFalse(self.spam.lookup("bar").is_local())
126+
# Module-scope globals are both global and local
127+
self.assertTrue(self.top.lookup("some_non_assigned_global_var").is_local())
128+
self.assertTrue(self.top.lookup("some_assigned_global_var").is_local())
119129

120130
def test_free(self):
121131
self.assertTrue(self.internal.lookup("x").is_free())
@@ -234,6 +244,10 @@ def test_bytes(self):
234244
top = symtable.symtable(code, "?", "exec")
235245
self.assertIsNotNone(find_block(top, "\u017d"))
236246

247+
def test_symtable_repr(self):
248+
self.assertEqual(str(self.top), "<SymbolTable for module ?>")
249+
self.assertEqual(str(self.spam), "<Function SymbolTable for spam in ?>")
250+
237251

238252
if __name__ == '__main__':
239253
unittest.main()
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix a bug in the :mod:`symtable` module that was causing module-scope global
2+
variables to not be reported as both local and global. Patch by Pablo
3+
Galindo.

0 commit comments

Comments
 (0)