summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorryneeverett <ryneeverett@gmail.com>2015-04-27 20:28:01 -0400
committerryneeverett <ryneeverett@gmail.com>2015-04-27 20:31:21 -0400
commit03ffc76367c2910f37908105391c425cc4ae52da (patch)
treec5c969bb4c5a349a2f88e2215a21f9850895d21c
parent7b7f50a0810b343be3623c141cca50a487bdec31 (diff)
downloadpyflakes-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.py26
-rw-r--r--pyflakes/test/test_imports.py15
-rw-r--r--pyflakes/test/test_undefined_names.py16
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):
"""