Skip to content

Commit 5302f1f

Browse files
Danny Seplerdannysepler
authored andcommitted
Global variables must be assigned to be used
1 parent 853cce9 commit 5302f1f

File tree

3 files changed

+91
-7
lines changed

3 files changed

+91
-7
lines changed

pyflakes/checker.py

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -250,10 +250,11 @@ class Binding:
250250
the node that this binding was last used.
251251
"""
252252

253-
def __init__(self, name, source):
253+
def __init__(self, name, source, *, assigned=True):
254254
self.name = name
255255
self.source = source
256256
self.used = False
257+
self.assigned = assigned
257258

258259
def __str__(self):
259260
return self.name
@@ -1007,6 +1008,12 @@ def addBinding(self, node, value):
10071008
break
10081009
existing = scope.get(value.name)
10091010

1011+
global_scope = self.scopeStack[-1]
1012+
if (existing and global_scope.get(value.name) == existing and
1013+
not existing.assigned):
1014+
# make sure the variable is in the global scope before setting as assigned
1015+
existing.assigned = True
1016+
10101017
if (existing and not isinstance(existing, Builtin) and
10111018
not self.differentForks(node, existing.source)):
10121019

@@ -1089,6 +1096,10 @@ def handleNodeLoad(self, node):
10891096
continue
10901097

10911098
binding = scope.get(name, None)
1099+
1100+
if binding and binding.assigned is False:
1101+
self.report(messages.UndefinedName, node, name)
1102+
10921103
if isinstance(binding, Annotation) and not self._in_postponed_annotation:
10931104
scope[name].used = True
10941105
continue
@@ -1159,12 +1170,19 @@ def handleNodeStore(self, node):
11591170
continue
11601171
# if the name was defined in that scope, and the name has
11611172
# been accessed already in the current scope, and hasn't
1162-
# been declared global
1173+
# been assigned globally
11631174
used = name in scope and scope[name].used
11641175
if used and used[0] is self.scope and name not in self.scope.globals:
11651176
# then it's probably a mistake
11661177
self.report(messages.UndefinedLocal,
11671178
scope[name].used[1], name, scope[name].source)
1179+
1180+
# and remove UndefinedName messages already reported for this name
1181+
self.messages = [
1182+
m for m in self.messages if not
1183+
isinstance(m, messages.UndefinedName) or
1184+
m.message_args[0] != name]
1185+
11681186
break
11691187

11701188
parent_stmt = self.getParent(node)
@@ -1836,7 +1854,7 @@ def ASSERT(self, node):
18361854
self.report(messages.AssertTuple, node)
18371855
self.handleChildren(node)
18381856

1839-
def GLOBAL(self, node):
1857+
def GLOBAL(self, node, assign_by_default=False):
18401858
"""
18411859
Keep track of globals declarations.
18421860
"""
@@ -1848,11 +1866,9 @@ def GLOBAL(self, node):
18481866

18491867
# One 'global' statement can bind multiple (comma-delimited) names.
18501868
for node_name in node.names:
1851-
node_value = Assignment(node_name, node)
1869+
node_value = Assignment(node_name, node, assigned=assign_by_default)
18521870

18531871
# Remove UndefinedName messages already reported for this name.
1854-
# TODO: if the global is not used in this scope, it does not
1855-
# become a globally defined name. See test_unused_global.
18561872
self.messages = [
18571873
m for m in self.messages if not
18581874
isinstance(m, messages.UndefinedName) or
@@ -1866,7 +1882,11 @@ def GLOBAL(self, node):
18661882
for scope in self.scopeStack[global_scope_index + 1:]:
18671883
scope[node_name] = node_value
18681884

1869-
NONLOCAL = GLOBAL
1885+
def NONLOCAL(self, node):
1886+
for node_name in node.names:
1887+
if not any(node_name in scope for scope in self.scopeStack[:-1]):
1888+
self.report(messages.NoBindingForNonlocal, node, node_name)
1889+
self.GLOBAL(node, assign_by_default=True)
18701890

18711891
def GENERATOREXP(self, node):
18721892
self.pushScope(GeneratorScope)

pyflakes/messages.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,14 @@ def __init__(self, filename, loc, name):
7373
self.message_args = (name,)
7474

7575

76+
class NoBindingForNonlocal(Message):
77+
message = 'no binding for nonlocal %r found'
78+
79+
def __init__(self, filename, loc, name):
80+
Message.__init__(self, filename, loc)
81+
self.message_args = (name,)
82+
83+
7684
class DoctestSyntaxError(Message):
7785
message = 'syntax error in doctest'
7886

pyflakes/test/test_undefined_names.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,62 @@ 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+
330+
def test_scope_defined_nonlocal(self):
331+
"""
332+
If a "nonlocal" is declared in a previous scope,
333+
it is defined
334+
"""
335+
self.flakes('''
336+
def a():
337+
fu = 1
338+
def b():
339+
nonlocal fu
340+
fu
341+
''')
342+
343+
def test_scope_undefined_nonlocal(self):
344+
"""
345+
If a "nonlocal" is never given a value, it is undefined
346+
"""
347+
self.flakes('''
348+
def a():
349+
nonlocal fu
350+
''', m.NoBindingForNonlocal)
351+
352+
self.flakes('''
353+
def a():
354+
nonlocal fu, bar
355+
''', m.NoBindingForNonlocal, m.NoBindingForNonlocal)
356+
301357
def test_definedByGlobalMultipleNames(self):
302358
"""
303359
"global" can accept multiple names.

0 commit comments

Comments
 (0)