Skip to content

Commit 6f7f425

Browse files
committed
Catch usage of undefined name with global statement
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.
1 parent 45fc732 commit 6f7f425

File tree

3 files changed

+47
-3
lines changed

3 files changed

+47
-3
lines changed

pyflakes/checker.py

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -435,6 +435,7 @@ def __init__(self):
435435
super(FunctionScope, self).__init__()
436436
# Simplify: manage the special locals as globals
437437
self.globals = self.alwaysUsed.copy()
438+
self.global_names = []
438439
self.returnValue = None # First non-empty return
439440
self.isGenerator = False # Detect a generator
440441

@@ -574,6 +575,9 @@ def _in_doctest(self):
574575
return (len(self.scopeStack) >= 2 and
575576
isinstance(self.scopeStack[1], DoctestScope))
576577

578+
def _global_scope(self):
579+
return self.scopeStack[1 if self._in_doctest() else 0]
580+
577581
@property
578582
def futuresAllowed(self):
579583
if not all(isinstance(scope, ModuleScope)
@@ -735,6 +739,11 @@ def addBinding(self, node, value):
735739
elif isinstance(existing, Importation) and value.redefines(existing):
736740
existing.redefined.append(node)
737741

742+
if (isinstance(self.scope, FunctionScope) and
743+
isinstance(value, Importation) and
744+
value.name in self.scope.global_names):
745+
self.store_global_scope(value.name, value)
746+
738747
if value.name in self.scope:
739748
# then assume the rebound name is used as a global or within a loop
740749
value.used = self.scope[value.name].used
@@ -757,6 +766,12 @@ def handleNodeLoad(self, node):
757766
in_generators = None
758767
importStarred = None
759768

769+
if (isinstance(self.scope, FunctionScope) and
770+
name in self.scope.global_names and
771+
name not in self._global_scope()):
772+
# report name declared with global statement but undefined
773+
self.report(messages.UndefinedName, node, name)
774+
760775
# try enclosing function scopes and global scope
761776
for scope in self.scopeStack[-1::-1]:
762777
if isinstance(scope, ClassScope):
@@ -833,6 +848,10 @@ def handleNodeStore(self, node):
833848
scope[name].used[1], name, scope[name].source)
834849
break
835850

851+
if (isinstance(self.scope, FunctionScope) and
852+
name in self.scope.global_names):
853+
self.store_global_scope(name, Assignment(name, node))
854+
836855
parent_stmt = self.getParent(node)
837856
if isinstance(parent_stmt, (ast.For, ast.comprehension)) or (
838857
parent_stmt != node.parent and
@@ -868,8 +887,15 @@ def on_conditional_branch():
868887
# be executed.
869888
return
870889

871-
if isinstance(self.scope, FunctionScope) and name in self.scope.globals:
872-
self.scope.globals.remove(name)
890+
if isinstance(self.scope, FunctionScope):
891+
if name in self.scope.globals:
892+
self.scope.globals.remove(name)
893+
if name in self.scope.global_names:
894+
self.scope.global_names.remove(name)
895+
try:
896+
del self._global_scope()[name]
897+
except KeyError:
898+
self.report(messages.UndefinedName, node, name)
873899
else:
874900
try:
875901
del self.scope[name]
@@ -1016,6 +1042,10 @@ def handleForwardAnnotation():
10161042
def ignore(self, node):
10171043
pass
10181044

1045+
def store_global_scope(self, name, value):
1046+
"""This store name in global scope"""
1047+
self._global_scope()[name] = value
1048+
10191049
# "stmt" type nodes
10201050
DELETE = PRINT = FOR = ASYNCFOR = WHILE = IF = WITH = WITHITEM = \
10211051
ASYNCWITH = ASYNCWITHITEM = TRYFINALLY = EXEC = \
@@ -1122,7 +1152,8 @@ def GLOBAL(self, node):
11221152
m.message_args[0] != node_name]
11231153

11241154
# Bind name to global scope if it doesn't exist already.
1125-
global_scope.setdefault(node_name, node_value)
1155+
if isinstance(self.scope, FunctionScope):
1156+
self.scope.global_names.append(node_name)
11261157

11271158
# Bind name to non-global scopes, but as already "used".
11281159
node_value.used = (global_scope, node)

pyflakes/test/test_imports.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -686,6 +686,11 @@ def f(): global foo; import foo
686686
def g(): foo.is_used()
687687
''')
688688

689+
self.flakes('''
690+
def f(): global foo; import foo.bar
691+
def g(): foo.is_used()
692+
''')
693+
689694
@skipIf(version_info >= (3,), 'deprecated syntax')
690695
def test_usedInBackquote(self):
691696
self.flakes('import fu; `fu`')

pyflakes/test/test_undefined_names.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,14 @@ def f2():
353353
global m
354354
''', m.UndefinedName)
355355

356+
def test_undefined_global(self):
357+
"""Use an undefined name with global statement"""
358+
self.flakes('''
359+
def f():
360+
global m
361+
print(m)
362+
''', m.UndefinedName)
363+
356364
def test_del(self):
357365
"""Del deletes bindings."""
358366
self.flakes('a = 1; del a; a', m.UndefinedName)

0 commit comments

Comments
 (0)