diff --git a/pyflakes/checker.py b/pyflakes/checker.py index c2f8d2e8..5eb2112d 100644 --- a/pyflakes/checker.py +++ b/pyflakes/checker.py @@ -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 @@ -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) @@ -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 @@ -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): @@ -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 @@ -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] @@ -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 = \ @@ -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) diff --git a/pyflakes/test/test_imports.py b/pyflakes/test/test_imports.py index 86e1d378..e9bd0155 100644 --- a/pyflakes/test/test_imports.py +++ b/pyflakes/test/test_imports.py @@ -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`') diff --git a/pyflakes/test/test_undefined_names.py b/pyflakes/test/test_undefined_names.py index 88eba933..818004f7 100644 --- a/pyflakes/test/test_undefined_names.py +++ b/pyflakes/test/test_undefined_names.py @@ -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)