Brodie Rao avatar Brodie Rao committed 39c32e3

Added a warning for list comprehensions that shadow outer variables

Comments (0)

Files changed (3)

pyflakes/checker.py

     # additional node types
     COMPREHENSION = EXCEPTHANDLER = KEYWORD = handleChildren
 
+    def hasParent(self, node, kind):
+        parent = getattr(node, 'parent', None)
+        while True:
+            if not parent:
+                return False
+            elif isinstance(parent, kind):
+                return True
+            parent = getattr(parent, 'parent', None)
+
     def addBinding(self, lineno, value, reportRedef=True):
         '''Called when a binding is altered.
 
             self.report(messages.RedefinedFunction,
                         lineno, value.name, self.scope[value.name].source.lineno)
 
+        redefinedWhileUnused = False
+
         if not isinstance(self.scope, ClassScope):
             for scope in self.scopeStack[::-1]:
                 existing = scope.get(value.name)
                         and not existing.used
                         and (not isinstance(value, Importation) or value.fullName == existing.fullName)
                         and reportRedef):
-
+                    redefinedWhileUnused = True
                     self.report(messages.RedefinedWhileUnused,
                                 lineno, value.name, scope[value.name].source.lineno)
 
+        if (not redefinedWhileUnused and
+            self.hasParent(value.source, _ast.ListComp)):
+            existing = self.scope.get(value.name)
+            if (existing and
+                not self.hasParent(existing.source, (_ast.For, _ast.ListComp))
+                and reportRedef):
+                self.report(messages.RedefinedInListComp, lineno, value.name,
+                            self.scope[value.name].source.lineno)
+
         if isinstance(value, UnBinding):
             try:
                 del self.scope[value.name]

pyflakes/messages.py

         self.message_args = (name, orig_lineno)
 
 
+class RedefinedInListComp(Message):
+    message = 'list comprehension redefines %r from line %r'
+    def __init__(self, filename, lineno, name, orig_lineno):
+        Message.__init__(self, filename, lineno)
+        self.message_args = (name, orig_lineno)
+
+
 class ImportShadowedByLoopVar(Message):
     message = 'import %r from line %r shadowed by loop variable'
     def __init__(self, filename, lineno, name, orig_lineno):

pyflakes/test/test_other.py

         ''', m.UndefinedName)
     test_localReferencedBeforeAssignment.todo = 'this requires finding all assignments in the function body first'
 
+    def test_redefinedInListComp(self):
+        """
+        Test that shadowing a variable in a list comprehension raises
+        a warning.
+        """
+        self.flakes('''
+        a = 1
+        [1 for a, b in [(1, 2)]]
+        ''', m.RedefinedInListComp)
+        self.flakes('''
+        class A:
+            a = 1
+            [1 for a, b in [(1, 2)]]
+        ''', m.RedefinedInListComp)
+        self.flakes('''
+        def f():
+            a = 1
+            [1 for a, b in [(1, 2)]]
+        ''', m.RedefinedInListComp)
+        self.flakes('''
+        [1 for a, b in [(1, 2)]]
+        [1 for a, b in [(1, 2)]]
+        ''')
+        self.flakes('''
+        for a, b in [(1, 2)]:
+            pass
+        [1 for a, b in [(1, 2)]]
+        ''')
+
+
     def test_redefinedFunction(self):
         """
         Test that shadowing a function definition with another one raises a
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.