Skip to content

Commit 7b7aa94

Browse files
authored
[3.9] bpo-41840: Report module-level globals as both local and global in the symtable module (GH-22391). (GH-22528)
(cherry picked from commit fb0a465) Co-authored-by: Pablo Galindo <[email protected]>
1 parent e892537 commit 7b7aa94

File tree

3 files changed

+35
-9
lines changed

3 files changed

+35
-9
lines changed

Lib/symtable.py

+16-7
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ def __call__(self, table, filename):
3434
_newSymbolTable = SymbolTableFactory()
3535

3636

37-
class SymbolTable(object):
37+
class SymbolTable:
3838

3939
def __init__(self, raw_table, filename):
4040
self._table = raw_table
@@ -47,7 +47,7 @@ def __repr__(self):
4747
else:
4848
kind = "%s " % self.__class__.__name__
4949

50-
if self._table.name == "global":
50+
if self._table.name == "top":
5151
return "<{0}SymbolTable for module {1}>".format(kind, self._filename)
5252
else:
5353
return "<{0}SymbolTable for {1} in {2}>".format(kind,
@@ -90,7 +90,9 @@ def lookup(self, name):
9090
if sym is None:
9191
flags = self._table.symbols[name]
9292
namespaces = self.__check_children(name)
93-
sym = self._symbols[name] = Symbol(name, flags, namespaces)
93+
module_scope = (self._table.name == "top")
94+
sym = self._symbols[name] = Symbol(name, flags, namespaces,
95+
module_scope=module_scope)
9496
return sym
9597

9698
def get_symbols(self):
@@ -163,13 +165,14 @@ def get_methods(self):
163165
return self.__methods
164166

165167

166-
class Symbol(object):
168+
class Symbol:
167169

168-
def __init__(self, name, flags, namespaces=None):
170+
def __init__(self, name, flags, namespaces=None, *, module_scope=False):
169171
self.__name = name
170172
self.__flags = flags
171173
self.__scope = (flags >> SCOPE_OFF) & SCOPE_MASK # like PyST_GetScope()
172174
self.__namespaces = namespaces or ()
175+
self.__module_scope = module_scope
173176

174177
def __repr__(self):
175178
return "<symbol {0!r}>".format(self.__name)
@@ -184,7 +187,10 @@ def is_parameter(self):
184187
return bool(self.__flags & DEF_PARAM)
185188

186189
def is_global(self):
187-
return bool(self.__scope in (GLOBAL_IMPLICIT, GLOBAL_EXPLICIT))
190+
"""Return *True* if the sysmbol is global.
191+
"""
192+
return bool(self.__scope in (GLOBAL_IMPLICIT, GLOBAL_EXPLICIT)
193+
or (self.__module_scope and self.__flags & DEF_BOUND))
188194

189195
def is_nonlocal(self):
190196
return bool(self.__flags & DEF_NONLOCAL)
@@ -193,7 +199,10 @@ def is_declared_global(self):
193199
return bool(self.__scope == GLOBAL_EXPLICIT)
194200

195201
def is_local(self):
196-
return bool(self.__scope in (LOCAL, CELL))
202+
"""Return *True* if the symbol is local.
203+
"""
204+
return bool(self.__scope in (LOCAL, CELL)
205+
or (self.__module_scope and self.__flags & DEF_BOUND))
197206

198207
def is_annotated(self):
199208
return bool(self.__flags & DEF_ANNOT)

Lib/test/test_symtable.py

+16-2
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
@@ -81,14 +85,14 @@ def test_children(self):
8185

8286
def test_lineno(self):
8387
self.assertEqual(self.top.get_lineno(), 0)
84-
self.assertEqual(self.spam.get_lineno(), 12)
88+
self.assertEqual(self.spam.get_lineno(), 14)
8589

8690
def test_function_info(self):
8791
func = self.spam
8892
self.assertEqual(sorted(func.get_parameters()), ["a", "b", "kw", "var"])
8993
expected = ['a', 'b', 'internal', 'kw', 'other_internal', 'some_var', 'var', 'x']
9094
self.assertEqual(sorted(func.get_locals()), expected)
91-
self.assertEqual(sorted(func.get_globals()), ["bar", "glob"])
95+
self.assertEqual(sorted(func.get_globals()), ["bar", "glob", "some_assigned_global_var"])
9296
self.assertEqual(self.internal.get_frees(), ("x",))
9397

9498
def test_globals(self):
@@ -99,6 +103,9 @@ def test_globals(self):
99103
self.assertFalse(self.internal.lookup("x").is_global())
100104
self.assertFalse(self.Mine.lookup("instance_var").is_global())
101105
self.assertTrue(self.spam.lookup("bar").is_global())
106+
# Module-scope globals are both global and local
107+
self.assertTrue(self.top.lookup("some_non_assigned_global_var").is_global())
108+
self.assertTrue(self.top.lookup("some_assigned_global_var").is_global())
102109

103110
def test_nonlocal(self):
104111
self.assertFalse(self.spam.lookup("some_var").is_nonlocal())
@@ -109,6 +116,9 @@ def test_nonlocal(self):
109116
def test_local(self):
110117
self.assertTrue(self.spam.lookup("x").is_local())
111118
self.assertFalse(self.spam.lookup("bar").is_local())
119+
# Module-scope globals are both global and local
120+
self.assertTrue(self.top.lookup("some_non_assigned_global_var").is_local())
121+
self.assertTrue(self.top.lookup("some_assigned_global_var").is_local())
112122

113123
def test_free(self):
114124
self.assertTrue(self.internal.lookup("x").is_free())
@@ -227,6 +237,10 @@ def test_bytes(self):
227237
top = symtable.symtable(code, "?", "exec")
228238
self.assertIsNotNone(find_block(top, "\u017d"))
229239

240+
def test_symtable_repr(self):
241+
self.assertEqual(str(self.top), "<SymbolTable for module ?>")
242+
self.assertEqual(str(self.spam), "<Function SymbolTable for spam in ?>")
243+
230244

231245
if __name__ == '__main__':
232246
unittest.main()
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)