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
  • Loading branch information
seeeturtle committed Oct 8, 2018
1 parent 45fc732 commit 72f208f
Show file tree
Hide file tree
Showing 2 changed files with 42 additions and 2 deletions.
36 changes: 34 additions & 2 deletions pyflakes/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,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 Down Expand Up @@ -1016,6 +1017,15 @@ def handleForwardAnnotation():
def ignore(self, node):
pass

def store_global_scope(self, name, value):
"""This store name in global scope if it is in global_names"""
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, value))

# "stmt" type nodes
DELETE = PRINT = FOR = ASYNCFOR = WHILE = IF = WITH = WITHITEM = \
ASYNCWITH = ASYNCWITHITEM = TRYFINALLY = EXEC = \
Expand Down Expand Up @@ -1122,7 +1132,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 @@ -1144,17 +1155,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, ast.Param)):
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 node.id not 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:
# Unknown context
raise RuntimeError("Got impossible expression context: %r" % (node.ctx,))
Expand Down Expand Up @@ -1366,13 +1393,16 @@ 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)
self.addBinding(node, importation)

self.store_global_scope(name, alias)

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

self.store_global_scope(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 @@ -353,6 +353,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 72f208f

Please sign in to comment.