diff options
author | John Vandenberg <jayvdb@gmail.com> | 2015-11-16 23:08:12 +1100 |
---|---|---|
committer | John Vandenberg <jayvdb@gmail.com> | 2015-11-20 02:12:43 +1100 |
commit | 265766da8921620b44752bdc6ba95b3775f0390b (patch) | |
tree | b76a3ac7ea6b8d6b09f1d336a584fd8d5531743f | |
parent | 3088ffbd6256521e0213b361bc2294c1e218e6fb (diff) | |
download | pyflakes-265766da8921620b44752bdc6ba95b3775f0390b.tar.gz |
Fix undefined name in generators in class
8c8a27b8 provided a partial solution for generators in the class scope,
however it did not account for generators enclosing other generators.
-rw-r--r-- | pyflakes/checker.py | 27 | ||||
-rw-r--r-- | pyflakes/test/test_undefined_names.py | 25 |
2 files changed, 36 insertions, 16 deletions
diff --git a/pyflakes/checker.py b/pyflakes/checker.py index f538d3f..ddaf86e 100644 --- a/pyflakes/checker.py +++ b/pyflakes/checker.py @@ -480,23 +480,17 @@ class Checker(object): name = getNodeName(node) if not name: return - # try local scope - try: - self.scope[name].used = (self.scope, node) - except KeyError: - pass - else: - return - scopes = [scope for scope in self.scopeStack[:-1] - if isinstance(scope, (FunctionScope, ModuleScope, GeneratorScope))] - if isinstance(self.scope, GeneratorScope) and scopes[-1] != self.scopeStack[-2]: - scopes.append(self.scopeStack[-2]) + in_generators = None + importStarred = None # try enclosing function scopes and global scope - importStarred = self.scope.importStarred - for scope in reversed(scopes): - importStarred = importStarred or scope.importStarred + for scope in self.scopeStack[-1::-1]: + # only generators used in a class scope can access the names + # of the class. this is skipped during the first iteration + if in_generators is False and isinstance(scope, ClassScope): + continue + try: scope[name].used = (self.scope, node) except KeyError: @@ -504,6 +498,11 @@ class Checker(object): else: return + importStarred = importStarred or scope.importStarred + + if in_generators is not False: + in_generators = isinstance(scope, GeneratorScope) + # look in the built-ins if importStarred or name in self.builtIns: return diff --git a/pyflakes/test/test_undefined_names.py b/pyflakes/test/test_undefined_names.py index 54af0eb..81dab13 100644 --- a/pyflakes/test/test_undefined_names.py +++ b/pyflakes/test/test_undefined_names.py @@ -481,8 +481,20 @@ class Test(TestCase): Using the loop variable of a generator expression results in no warnings. """ - self.flakes('(a for a in %srange(10) if a)' % - ('x' if version_info < (3,) else '')) + self.flakes('(a for a in [1, 2, 3] if a)') + + self.flakes('(b for b in (a for a in [1, 2, 3] if a) if b)') + + def test_undefinedInGenExpNested(self): + """ + The loop variables of generator expressions nested together are + not defined in the other generator. + """ + self.flakes('(b for b in (a for a in [1, 2, 3] if b) if b)', + m.UndefinedName) + + self.flakes('(b for b in (a for a in [1, 2, 3] if a) if a)', + m.UndefinedName) def test_undefinedWithErrorHandler(self): """ @@ -537,6 +549,15 @@ class Test(TestCase): Y = {x:x for x in T} ''') + def test_definedInClassNested(self): + """Defined name for nested generator expressions in a class.""" + self.flakes(''' + class A: + T = range(10) + + Z = (x for x in (a for a in T)) + ''') + def test_undefinedInLoop(self): """ The loop variable is defined after the expression is computed. |