Skip to content

Commit

Permalink
Closes #249
Browse files Browse the repository at this point in the history
  • Loading branch information
seeeturtle committed Jan 17, 2018
1 parent 8a1feac commit a9bdb17
Show file tree
Hide file tree
Showing 2 changed files with 34 additions and 1 deletion.
27 changes: 26 additions & 1 deletion pyflakes/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,7 @@ def __init__(self):
super(FunctionScope, self).__init__()
# Simplify: manage the special locals as globals
self.globals = self.alwaysUsed.copy()
self.global_names = []
self.returnValue = None # First non-empty return
self.isGenerator = False # Detect a generator

Expand All @@ -430,6 +431,13 @@ def unusedAssignments(self):
and isinstance(binding, Assignment)):
yield name, binding

def usedAssignments(self):
for name, binding in self.items():
if (name not in self.globals and
not self.usesLocals and
isinstance(binding, Assignment)):
yield name, binding


class GeneratorScope(Scope):
pass
Expand Down Expand Up @@ -1052,7 +1060,8 @@ def GLOBAL(self, node):
m.message_args[0] != node_name]

# Bind name to global scope if it doesn't exist already.
global_scope.setdefault(node_name, node_value)
if isinstance(self.scope, FunctionScope):
self.scope.global_names.append(node_name)

# Bind name to non-global scopes, but as already "used".
node_value.used = (global_scope, node)
Expand All @@ -1074,17 +1083,33 @@ def NAME(self, node):
"""
Handle occurrence of Name (which can be a load/store/delete access.)
"""
global_scope_index = 1 if self._in_doctest() else 0
global_scope = self.scopeStack[global_scope_index]
# Locate the name in locals / function / globals scopes.
if isinstance(node.ctx, (ast.Load, ast.AugLoad)):
self.handleNodeLoad(node)
if (node.id == 'locals' and isinstance(self.scope, FunctionScope)
and isinstance(node.parent, ast.Call)):
# we are doing locals() call in current scope
self.scope.usesLocals = True
if (isinstance(self.scope, FunctionScope) and
node.id in self.scope.global_names):
if node.id not in global_scope:
self.report(messages.UndefinedName, node, node.id)
elif isinstance(node.ctx, (ast.Store, ast.AugStore)):
self.handleNodeStore(node)
if (isinstance(self.scope, FunctionScope) and
node.id in self.scope.global_names):
global_scope.setdefault(node.id, Assignment(node.id, node))
elif isinstance(node.ctx, ast.Del):
self.handleNodeDelete(node)
if (isinstance(self.scope, FunctionScope) and
node.id in self.scope.global_names):
if not node.id in global_scope:
self.report(messages.UndefinedName, node, node.id)
else:
global_scope.pop(node.id, None)
self.scope.global_names.remove(node.id)
else:
# must be a Param context -- this only happens for names in function
# arguments, but these aren't dispatched through here
Expand Down
8 changes: 8 additions & 0 deletions pyflakes/test/test_undefined_names.py
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,14 @@ def f2():
global m
''', m.UndefinedName)

def test_undefined_global(self):
"""Use an undefined name with global statement"""
self.flakes('''
def f():
global m
print(m)
''', m.UndefinedName)

def test_del(self):
"""Del deletes bindings."""
self.flakes('a = 1; del a; a', m.UndefinedName)
Expand Down

0 comments on commit a9bdb17

Please sign in to comment.