Skip to content

Commit

Permalink
Global variables must be assigned to be used
Browse files Browse the repository at this point in the history
  • Loading branch information
Danny Sepler authored and dannysepler committed Dec 25, 2021
1 parent dbb1843 commit bd0a4d6
Show file tree
Hide file tree
Showing 2 changed files with 50 additions and 5 deletions.
26 changes: 21 additions & 5 deletions pyflakes/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -322,10 +322,11 @@ class Binding(object):
the node that this binding was last used.
"""

def __init__(self, name, source):
def __init__(self, name, source, *, assigned=True):
self.name = name
self.source = source
self.used = False
self.assigned = assigned

def __str__(self):
return self.name
Expand Down Expand Up @@ -1129,6 +1130,12 @@ def addBinding(self, node, value):
break
existing = scope.get(value.name)

global_scope = self.scopeStack[-1]
if (existing and global_scope.get(value.name) == existing and
not existing.assigned):
# make sure the variable is in the global scope before setting as assigned
existing.assigned = True

if (existing and not isinstance(existing, Builtin) and
not self.differentForks(node, existing.source)):

Expand Down Expand Up @@ -1207,6 +1214,10 @@ def handleNodeLoad(self, node):
continue

binding = scope.get(name, None)

if getattr(binding, 'assigned', None) is False:
self.report(messages.UndefinedName, node, name)

if isinstance(binding, Annotation) and not self._in_postponed_annotation:
continue

Expand Down Expand Up @@ -1276,12 +1287,19 @@ def handleNodeStore(self, node):
continue
# if the name was defined in that scope, and the name has
# been accessed already in the current scope, and hasn't
# been declared global
# been assigned globally
used = name in scope and scope[name].used
if used and used[0] is self.scope and name not in self.scope.globals:
# then it's probably a mistake
self.report(messages.UndefinedLocal,
scope[name].used[1], name, scope[name].source)

# and we can remove UndefinedName messages already reported for this name
self.messages = [
m for m in self.messages if not
isinstance(m, messages.UndefinedName) or
m.message_args[0] != name]

break

parent_stmt = self.getParent(node)
Expand Down Expand Up @@ -2002,11 +2020,9 @@ def GLOBAL(self, node):

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

# Remove UndefinedName messages already reported for this name.
# TODO: if the global is not used in this scope, it does not
# become a globally defined name. See test_unused_global.
self.messages = [
m for m in self.messages if not
isinstance(m, messages.UndefinedName) or
Expand Down
29 changes: 29 additions & 0 deletions pyflakes/test/test_undefined_names.py
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,35 @@ def c(): bar
def b(): global bar; bar = 1
''')

def test_unassigned_global_is_undefined(self):
"""
If a "global" is never given a value, it is undefined
"""
self.flakes('''
def a():
global fu
fu
''', m.UndefinedName)

self.flakes('''
global fu
fu
''', m.UndefinedName)

def test_scope_defined_global(self):
"""
If a "global" is defined inside of a function only,
outside of the function it is undefined
"""
self.flakes('''
global fu
def a():
fu = 1
fu
a()
fu
''', m.UndefinedName)

def test_definedByGlobalMultipleNames(self):
"""
"global" can accept multiple names.
Expand Down

0 comments on commit bd0a4d6

Please sign in to comment.