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 Sep 16, 2022
1 parent 4dcd92e commit bcf8194
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 @@ -262,10 +262,11 @@ class Binding:
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 @@ -1073,6 +1074,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 @@ -1155,6 +1162,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 @@ -1224,12 +1235,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 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 @@ -1933,11 +1951,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 @@ -298,6 +298,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 bcf8194

Please sign in to comment.