diff options
author | ryneeverett <ryneeverett@gmail.com> | 2015-04-27 20:28:01 -0400 |
---|---|---|
committer | ryneeverett <ryneeverett@gmail.com> | 2015-04-27 20:31:21 -0400 |
commit | 03ffc76367c2910f37908105391c425cc4ae52da (patch) | |
tree | c5c969bb4c5a349a2f88e2215a21f9850895d21c | |
parent | 7b7f50a0810b343be3623c141cca50a487bdec31 (diff) | |
download | pyflakes-03ffc76367c2910f37908105391c425cc4ae52da.tar.gz |
Improve 'global' statement support.
Add declared globals to each scope in the scope stack to avoid
erroneous UndefinedName errors when they are loaded.
-rw-r--r-- | pyflakes/checker.py | 26 | ||||
-rw-r--r-- | pyflakes/test/test_imports.py | 15 | ||||
-rw-r--r-- | pyflakes/test/test_undefined_names.py | 16 |
3 files changed, 53 insertions, 4 deletions
diff --git a/pyflakes/checker.py b/pyflakes/checker.py index df2f907..de7c6f8 100644 --- a/pyflakes/checker.py +++ b/pyflakes/checker.py @@ -678,8 +678,30 @@ class Checker(object): """ Keep track of globals declarations. """ - if isinstance(self.scope, FunctionScope): - self.scope.globals.update(node.names) + # In doctests, the global scope is an anonymous function at index 1. + global_scope_index = 1 if self.withDoctest else 0 + global_scope = self.scopeStack[global_scope_index] + + # Ignore 'global' statement in global scope. + if self.scope is not global_scope: + + # One 'global' statement can bind multiple (comma-delimited) names. + for node_name in node.names: + node_value = Assignment(node_name, node) + + # Remove UndefinedName messages already reported for this name. + self.messages = [ + m for m in self.messages if not + isinstance(m, messages.UndefinedName) and not + m.message_args[0] == node_name] + + # Bind name to global scope if it doesn't exist already. + global_scope.setdefault(node_name, node_value) + + # Bind name to non-global scopes, but as already "used". + node_value.used = True + for scope in self.scopeStack[global_scope_index + 1:]: + scope[node_name] = node_value NONLOCAL = GLOBAL diff --git a/pyflakes/test/test_imports.py b/pyflakes/test/test_imports.py index 389ebbf..077a728 100644 --- a/pyflakes/test/test_imports.py +++ b/pyflakes/test/test_imports.py @@ -505,11 +505,26 @@ class Test(TestCase): ''') def test_usedInGlobal(self): + """ + A 'global' statement shadowing an unused import should not prevent it + from being reported. + """ self.flakes(''' import fu def f(): global fu ''', m.UnusedImport) + def test_usedAndGlobal(self): + """ + A 'global' statement shadowing a used import should not cause it to be + reported as unused. + """ + self.flakes(''' + import foo + def f(): global foo + 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 67c3f1f..faaaf8c 100644 --- a/pyflakes/test/test_undefined_names.py +++ b/pyflakes/test/test_undefined_names.py @@ -3,7 +3,7 @@ from _ast import PyCF_ONLY_AST from sys import version_info from pyflakes import messages as m, checker -from pyflakes.test.harness import TestCase, skip, skipIf +from pyflakes.test.harness import TestCase, skipIf class Test(TestCase): @@ -92,7 +92,6 @@ class Test(TestCase): bar; baz ''') - @skip("todo") def test_definedByGlobal(self): """ "global" can make an otherwise undefined name in another function @@ -102,6 +101,19 @@ class Test(TestCase): def a(): global fu; fu = 1 def b(): fu ''') + self.flakes(''' + def c(): bar + def b(): global bar; bar = 1 + ''') + + def test_definedByGlobalMultipleNames(self): + """ + "global" can accept multiple names. + """ + self.flakes(''' + def a(): global fu, bar; fu = 1; bar = 2 + def b(): fu; bar + ''') def test_globalInGlobalScope(self): """ |