Skip to content

Commit bcf8194

Browse files
Danny Seplerdannysepler
authored andcommitted
Global variables must be assigned to be used
1 parent 4dcd92e commit bcf8194

File tree

2 files changed

+50
-5
lines changed

2 files changed

+50
-5
lines changed

pyflakes/checker.py

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -262,10 +262,11 @@ class Binding:
262262
the node that this binding was last used.
263263
"""
264264

265-
def __init__(self, name, source):
265+
def __init__(self, name, source, *, assigned=True):
266266
self.name = name
267267
self.source = source
268268
self.used = False
269+
self.assigned = assigned
269270

270271
def __str__(self):
271272
return self.name
@@ -1073,6 +1074,12 @@ def addBinding(self, node, value):
10731074
break
10741075
existing = scope.get(value.name)
10751076

1077+
global_scope = self.scopeStack[-1]
1078+
if (existing and global_scope.get(value.name) == existing and
1079+
not existing.assigned):
1080+
# make sure the variable is in the global scope before setting as assigned
1081+
existing.assigned = True
1082+
10761083
if (existing and not isinstance(existing, Builtin) and
10771084
not self.differentForks(node, existing.source)):
10781085

@@ -1155,6 +1162,10 @@ def handleNodeLoad(self, node):
11551162
continue
11561163

11571164
binding = scope.get(name, None)
1165+
1166+
if getattr(binding, 'assigned', None) is False:
1167+
self.report(messages.UndefinedName, node, name)
1168+
11581169
if isinstance(binding, Annotation) and not self._in_postponed_annotation:
11591170
continue
11601171

@@ -1224,12 +1235,19 @@ def handleNodeStore(self, node):
12241235
continue
12251236
# if the name was defined in that scope, and the name has
12261237
# been accessed already in the current scope, and hasn't
1227-
# been declared global
1238+
# been assigned globally
12281239
used = name in scope and scope[name].used
12291240
if used and used[0] is self.scope and name not in self.scope.globals:
12301241
# then it's probably a mistake
12311242
self.report(messages.UndefinedLocal,
12321243
scope[name].used[1], name, scope[name].source)
1244+
1245+
# and remove UndefinedName messages already reported for this name
1246+
self.messages = [
1247+
m for m in self.messages if not
1248+
isinstance(m, messages.UndefinedName) or
1249+
m.message_args[0] != name]
1250+
12331251
break
12341252

12351253
parent_stmt = self.getParent(node)
@@ -1933,11 +1951,9 @@ def GLOBAL(self, node):
19331951

19341952
# One 'global' statement can bind multiple (comma-delimited) names.
19351953
for node_name in node.names:
1936-
node_value = Assignment(node_name, node)
1954+
node_value = Assignment(node_name, node, assigned=False)
19371955

19381956
# Remove UndefinedName messages already reported for this name.
1939-
# TODO: if the global is not used in this scope, it does not
1940-
# become a globally defined name. See test_unused_global.
19411957
self.messages = [
19421958
m for m in self.messages if not
19431959
isinstance(m, messages.UndefinedName) or

pyflakes/test/test_undefined_names.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,35 @@ def c(): bar
298298
def b(): global bar; bar = 1
299299
''')
300300

301+
def test_unassigned_global_is_undefined(self):
302+
"""
303+
If a "global" is never given a value, it is undefined
304+
"""
305+
self.flakes('''
306+
def a():
307+
global fu
308+
fu
309+
''', m.UndefinedName)
310+
311+
self.flakes('''
312+
global fu
313+
fu
314+
''', m.UndefinedName)
315+
316+
def test_scope_defined_global(self):
317+
"""
318+
If a "global" is defined inside of a function only,
319+
outside of the function it is undefined
320+
"""
321+
self.flakes('''
322+
global fu
323+
def a():
324+
fu = 1
325+
fu
326+
a()
327+
fu
328+
''', m.UndefinedName)
329+
301330
def test_definedByGlobalMultipleNames(self):
302331
"""
303332
"global" can accept multiple names.

0 commit comments

Comments
 (0)