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.
Note test_undefined_global for example of this.
  • Loading branch information
seeeturtle committed Dec 14, 2018
1 parent 45fc732 commit 6f7f425
Show file tree
Hide file tree
Showing 3 changed files with 47 additions and 3 deletions.
37 changes: 34 additions & 3 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 @@ -574,6 +575,9 @@ def _in_doctest(self):
return (len(self.scopeStack) >= 2 and
isinstance(self.scopeStack[1], DoctestScope))

def _global_scope(self):
return self.scopeStack[1 if self._in_doctest() else 0]

@property
def futuresAllowed(self):
if not all(isinstance(scope, ModuleScope)
Expand Down Expand Up @@ -735,6 +739,11 @@ def addBinding(self, node, value):
elif isinstance(existing, Importation) and value.redefines(existing):
existing.redefined.append(node)

if (isinstance(self.scope, FunctionScope) and
isinstance(value, Importation) and
value.name in self.scope.global_names):
self.store_global_scope(value.name, value)

if value.name in self.scope:
# then assume the rebound name is used as a global or within a loop
value.used = self.scope[value.name].used
Expand All @@ -757,6 +766,12 @@ def handleNodeLoad(self, node):
in_generators = None
importStarred = None

if (isinstance(self.scope, FunctionScope) and
name in self.scope.global_names and
name not in self._global_scope()):
# report name declared with global statement but undefined
self.report(messages.UndefinedName, node, name)

# try enclosing function scopes and global scope
for scope in self.scopeStack[-1::-1]:
if isinstance(scope, ClassScope):
Expand Down Expand Up @@ -833,6 +848,10 @@ def handleNodeStore(self, node):
scope[name].used[1], name, scope[name].source)
break

if (isinstance(self.scope, FunctionScope) and
name in self.scope.global_names):
self.store_global_scope(name, Assignment(name, node))

parent_stmt = self.getParent(node)
if isinstance(parent_stmt, (ast.For, ast.comprehension)) or (
parent_stmt != node.parent and
Expand Down Expand Up @@ -868,8 +887,15 @@ def on_conditional_branch():
# be executed.
return

if isinstance(self.scope, FunctionScope) and name in self.scope.globals:
self.scope.globals.remove(name)
if isinstance(self.scope, FunctionScope):
if name in self.scope.globals:
self.scope.globals.remove(name)
if name in self.scope.global_names:
self.scope.global_names.remove(name)
try:
del self._global_scope()[name]
except KeyError:
self.report(messages.UndefinedName, node, name)
else:
try:
del self.scope[name]
Expand Down Expand Up @@ -1016,6 +1042,10 @@ def handleForwardAnnotation():
def ignore(self, node):
pass

def store_global_scope(self, name, value):
"""This store name in global scope"""
self._global_scope()[name] = value

# "stmt" type nodes
DELETE = PRINT = FOR = ASYNCFOR = WHILE = IF = WITH = WITHITEM = \
ASYNCWITH = ASYNCWITHITEM = TRYFINALLY = EXEC = \
Expand Down Expand Up @@ -1122,7 +1152,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 Down
5 changes: 5 additions & 0 deletions pyflakes/test/test_imports.py
Original file line number Diff line number Diff line change
Expand Up @@ -686,6 +686,11 @@ def f(): global foo; import foo
def g(): foo.is_used()
''')

self.flakes('''
def f(): global foo; import foo.bar
def g(): foo.is_used()
''')

@skipIf(version_info >= (3,), 'deprecated syntax')
def test_usedInBackquote(self):
self.flakes('import fu; `fu`')
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 6f7f425

Please sign in to comment.