Skip to content

Commit fdf2353

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. Check test_undefined_globa Fixes #249
1 parent 8a1feac commit fdf2353

File tree

2 files changed

+51
-3
lines changed

2 files changed

+51
-3
lines changed

pyflakes/checker.py

+43-3
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,7 @@ def __init__(self):
417417
super(FunctionScope, self).__init__()
418418
# Simplify: manage the special locals as globals
419419
self.globals = self.alwaysUsed.copy()
420+
self.global_names = []
420421
self.returnValue = None # First non-empty return
421422
self.isGenerator = False # Detect a generator
422423

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

434+
def usedAssignments(self):
435+
for name, binding in self.items():
436+
if (name not in self.globals and
437+
not self.usesLocals and
438+
isinstance(binding, Assignment)):
439+
yield name, binding
440+
433441

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

10541062
# Bind name to global scope if it doesn't exist already.
1055-
global_scope.setdefault(node_name, node_value)
1063+
if isinstance(self.scope, FunctionScope):
1064+
self.scope.global_names.append(node_name)
10561065

10571066
# Bind name to non-global scopes, but as already "used".
10581067
node_value.used = (global_scope, node)
@@ -1074,17 +1083,33 @@ def NAME(self, node):
10741083
"""
10751084
Handle occurrence of Name (which can be a load/store/delete access.)
10761085
"""
1086+
global_scope_index = 1 if self._in_doctest() else 0
1087+
global_scope = self.scopeStack[global_scope_index]
10771088
# Locate the name in locals / function / globals scopes.
10781089
if isinstance(node.ctx, (ast.Load, ast.AugLoad)):
10791090
self.handleNodeLoad(node)
10801091
if (node.id == 'locals' and isinstance(self.scope, FunctionScope)
10811092
and isinstance(node.parent, ast.Call)):
10821093
# we are doing locals() call in current scope
10831094
self.scope.usesLocals = True
1095+
if (isinstance(self.scope, FunctionScope) and
1096+
node.id in self.scope.global_names):
1097+
if node.id not in global_scope:
1098+
self.report(messages.UndefinedName, node, node.id)
10841099
elif isinstance(node.ctx, (ast.Store, ast.AugStore)):
10851100
self.handleNodeStore(node)
1101+
if (isinstance(self.scope, FunctionScope) and
1102+
node.id in self.scope.global_names):
1103+
global_scope.setdefault(node.id, Assignment(node.id, node))
10861104
elif isinstance(node.ctx, ast.Del):
10871105
self.handleNodeDelete(node)
1106+
if (isinstance(self.scope, FunctionScope) and
1107+
node.id in self.scope.global_names):
1108+
if not node.id in global_scope:
1109+
self.report(messages.UndefinedName, node, node.id)
1110+
else:
1111+
global_scope.pop(node.id, None)
1112+
self.scope.global_names.remove(node.id)
10881113
else:
10891114
# must be a Param context -- this only happens for names in function
10901115
# arguments, but these aren't dispatched through here
@@ -1292,13 +1317,21 @@ def TUPLE(self, node):
12921317

12931318
def IMPORT(self, node):
12941319
for alias in node.names:
1320+
name = alias.name
12951321
if '.' in alias.name and not alias.asname:
1296-
importation = SubmoduleImportation(alias.name, node)
1322+
importation = SubmoduleImportation(name, node)
12971323
else:
12981324
name = alias.asname or alias.name
1299-
importation = Importation(name, node, alias.name)
1325+
importation = Importation(name, node, name)
13001326
self.addBinding(node, importation)
13011327

1328+
if isinstance(self.scope, FunctionScope):
1329+
global_scope_index = 1 if self._in_doctest() else 0
1330+
global_scope = self.scopeStack[global_scope_index]
1331+
if name in self.scope.global_names:
1332+
global_scope.setdefault(name,
1333+
Assignment(name, alias))
1334+
13021335
def IMPORTFROM(self, node):
13031336
if node.module == '__future__':
13041337
if not self.futuresAllowed:
@@ -1331,6 +1364,13 @@ def IMPORTFROM(self, node):
13311364
module, alias.name)
13321365
self.addBinding(node, importation)
13331366

1367+
if isinstance(self.scope, FunctionScope):
1368+
global_scope_index = 1 if self._in_doctest() else 0
1369+
global_scope = self.scopeStack[global_scope_index]
1370+
if name in self.scope.global_names:
1371+
global_scope.setdefault(name,
1372+
Assignment(name, alias))
1373+
13341374
def TRY(self, node):
13351375
handler_names = []
13361376
# List the exception handlers

pyflakes/test/test_undefined_names.py

+8
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,14 @@ def f2():
337337
global m
338338
''', m.UndefinedName)
339339

340+
def test_undefined_global(self):
341+
"""Use an undefined name with global statement"""
342+
self.flakes('''
343+
def f():
344+
global m
345+
print(m)
346+
''', m.UndefinedName)
347+
340348
def test_del(self):
341349
"""Del deletes bindings."""
342350
self.flakes('a = 1; del a; a', m.UndefinedName)

0 commit comments

Comments
 (0)