Skip to content

Commit 265766d

Browse files
committed
Fix undefined name in generators in class
8c8a27b provided a partial solution for generators in the class scope, however it did not account for generators enclosing other generators.
1 parent 3088ffb commit 265766d

File tree

2 files changed

+36
-16
lines changed

2 files changed

+36
-16
lines changed

pyflakes/checker.py

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -480,30 +480,29 @@ def handleNodeLoad(self, node):
480480
name = getNodeName(node)
481481
if not name:
482482
return
483-
# try local scope
484-
try:
485-
self.scope[name].used = (self.scope, node)
486-
except KeyError:
487-
pass
488-
else:
489-
return
490483

491-
scopes = [scope for scope in self.scopeStack[:-1]
492-
if isinstance(scope, (FunctionScope, ModuleScope, GeneratorScope))]
493-
if isinstance(self.scope, GeneratorScope) and scopes[-1] != self.scopeStack[-2]:
494-
scopes.append(self.scopeStack[-2])
484+
in_generators = None
485+
importStarred = None
495486

496487
# try enclosing function scopes and global scope
497-
importStarred = self.scope.importStarred
498-
for scope in reversed(scopes):
499-
importStarred = importStarred or scope.importStarred
488+
for scope in self.scopeStack[-1::-1]:
489+
# only generators used in a class scope can access the names
490+
# of the class. this is skipped during the first iteration
491+
if in_generators is False and isinstance(scope, ClassScope):
492+
continue
493+
500494
try:
501495
scope[name].used = (self.scope, node)
502496
except KeyError:
503497
pass
504498
else:
505499
return
506500

501+
importStarred = importStarred or scope.importStarred
502+
503+
if in_generators is not False:
504+
in_generators = isinstance(scope, GeneratorScope)
505+
507506
# look in the built-ins
508507
if importStarred or name in self.builtIns:
509508
return

pyflakes/test/test_undefined_names.py

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -481,8 +481,20 @@ def test_definedInGenExp(self):
481481
Using the loop variable of a generator expression results in no
482482
warnings.
483483
"""
484-
self.flakes('(a for a in %srange(10) if a)' %
485-
('x' if version_info < (3,) else ''))
484+
self.flakes('(a for a in [1, 2, 3] if a)')
485+
486+
self.flakes('(b for b in (a for a in [1, 2, 3] if a) if b)')
487+
488+
def test_undefinedInGenExpNested(self):
489+
"""
490+
The loop variables of generator expressions nested together are
491+
not defined in the other generator.
492+
"""
493+
self.flakes('(b for b in (a for a in [1, 2, 3] if b) if b)',
494+
m.UndefinedName)
495+
496+
self.flakes('(b for b in (a for a in [1, 2, 3] if a) if a)',
497+
m.UndefinedName)
486498

487499
def test_undefinedWithErrorHandler(self):
488500
"""
@@ -537,6 +549,15 @@ class A:
537549
Y = {x:x for x in T}
538550
''')
539551

552+
def test_definedInClassNested(self):
553+
"""Defined name for nested generator expressions in a class."""
554+
self.flakes('''
555+
class A:
556+
T = range(10)
557+
558+
Z = (x for x in (a for a in T))
559+
''')
560+
540561
def test_undefinedInLoop(self):
541562
"""
542563
The loop variable is defined after the expression is computed.

0 commit comments

Comments
 (0)