Skip to content

Commit

Permalink
Catch usage of undefined name with global statement
Browse files Browse the repository at this point in the history
When we use undefined name declared by global statement,
pyflakes should catch the error but it doesn't.
This will fix pyflakes to catch this error.
Check test_undefined_globa

Fixes #249
  • Loading branch information
seeeturtle committed Jan 17, 2018
1 parent 8a1feac commit fdf2353
Show file tree
Hide file tree
Showing 2 changed files with 51 additions and 3 deletions.
46 changes: 43 additions & 3 deletions 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 Expand Up @@ -1292,13 +1317,21 @@ def TUPLE(self, node):

def IMPORT(self, node):
for alias in node.names:
name = alias.name
if '.' in alias.name and not alias.asname:
importation = SubmoduleImportation(alias.name, node)
importation = SubmoduleImportation(name, node)
else:
name = alias.asname or alias.name
importation = Importation(name, node, alias.name)
importation = Importation(name, node, name)
self.addBinding(node, importation)

if isinstance(self.scope, FunctionScope):
global_scope_index = 1 if self._in_doctest() else 0
global_scope = self.scopeStack[global_scope_index]
if name in self.scope.global_names:
global_scope.setdefault(name,
Assignment(name, alias))

def IMPORTFROM(self, node):
if node.module == '__future__':
if not self.futuresAllowed:
Expand Down Expand Up @@ -1331,6 +1364,13 @@ def IMPORTFROM(self, node):
module, alias.name)
self.addBinding(node, importation)

if isinstance(self.scope, FunctionScope):
global_scope_index = 1 if self._in_doctest() else 0
global_scope = self.scopeStack[global_scope_index]
if name in self.scope.global_names:
global_scope.setdefault(name,
Assignment(name, alias))

def TRY(self, node):
handler_names = []
# List the exception handlers
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 fdf2353

Please sign in to comment.