Commits

Georg Brandl committed 186c0b2 Merge

merge

Comments (0)

Files changed (4)

pyflakes/checker.py

 
 # utility function to iterate over an AST node's children, adapted
 # from Python 2.6's standard ast module
-
-def iter_child_nodes(node, astcls=_ast.AST):
-    """
-    Yield all direct child nodes of *node*, that is, all fields that are nodes
-    and all items of fields that are lists of nodes.
-    """
-    for name in node._fields:
-        field = getattr(node, name, None)
-        if isinstance(field, astcls):
-            yield field
-        elif isinstance(field, list):
-            for item in field:
-                if isinstance(item, astcls):
+try:
+    import ast
+    iter_child_nodes = ast.iter_child_nodes
+except (ImportError, AttributeError):
+    def iter_child_nodes(node, astcls=_ast.AST):
+        """
+        Yield all direct child nodes of *node*, that is, all fields that are nodes
+        and all items of fields that are lists of nodes.
+        """
+        for name in node._fields:
+            field = getattr(node, name, None)
+            if isinstance(field, astcls):
+                yield field
+            elif isinstance(field, list):
+                for item in field:
                     yield item
 
 
     CONTINUE = BREAK = PASS = ignore
 
     # "expr" type nodes
-    BOOLOP = UNARYOP = LAMBDA = IFEXP = DICT = SET = YIELD = COMPARE = \
+    BOOLOP = UNARYOP = IFEXP = DICT = SET = YIELD = COMPARE = \
         REPR = SUBSCRIPT = LIST = TUPLE = handleChildren
 
     NUM = STR = ELLIPSIS = ignore
         """
         Handle occurrence of Name (which can be a load/store/delete access.)
         """
-        context = node.ctx.__class__.__name__
         # Locate the name in locals / function / globals scopes.
-        if context in ('Load', 'AugLoad'):
+        if isinstance(node.ctx, (_ast.Load, _ast.AugLoad)):
             # try local scope
             importStarred = self.scope.importStarred
             try:
                         pass
                     else:
                         self.report(messages.UndefinedName, node.lineno, node.id)
-        elif context in ('Store', 'AugStore'):
+        elif isinstance(node.ctx, (_ast.Store, _ast.AugStore)):
             # if the name hasn't already been defined in the current scope
             if isinstance(self.scope, FunctionScope) and node.id not in self.scope:
                 # for each function or module scope above us
             if node.id in self.scope:
                 binding.used = self.scope[node.id].used
             self.addBinding(node.lineno, binding)
-        elif context == 'Del':
+        elif isinstance(node.ctx, _ast.Del):
             if isinstance(self.scope, FunctionScope) and \
                    node.id in self.scope.globals:
                 del self.scope.globals[node.id]
         else:
             # must be a Param context -- this only happens for names in function
             # arguments, but these aren't dispatched through here
-            assert False, 'got impossible expression context ' + context
+            raise RuntimeError(
+                "Got impossible expression context: %r" % (node.ctx,))
 
 
     def FUNCTIONDEF(self, node):

pyflakes/scripts/pyflakes.py

File contents unchanged.

pyflakes/test/test_other.py

         ''')
 
 
-    def test_additionalSyntax(self):
+    def test_comparison(self):
         """
-        Exercise all syntax not otherwise explicitly tested.
+        If a defined name is used on either side of any of the six comparison
+        operators, no warning is emitted.
         """
         self.flakes('''
-        def f(x):
-            a = x[..., ...]
-            for b in a:
-                break
-                continue
-            return a != b, a < b, a <= b, a >= b, a is b, a is not b, \
-                   a in b, a not in b
-        g = lambda x, y: x + y
+        x = 10
+        y = 20
+        x < y
+        x <= y
+        x == y
+        x != y
+        x >= y
+        x > y
+        ''')
+
+
+    def test_identity(self):
+        """
+        If a deefined name is used on either side of an identity test, no
+        warning is emitted.
+        """
+        self.flakes('''
+        x = 10
+        y = 20
+        x is y
+        x is not y
+        ''')
+
+
+    def test_containment(self):
+        """
+        If a defined name is used on either side of a containment test, no
+        warning is emitted.
+        """
+        self.flakes('''
+        x = 10
+        y = 20
+        x in y
+        x not in y
+        ''')
+
+
+    def test_loopControl(self):
+        """
+        break and continue statements are supported.
+        """
+        self.flakes('''
+        for x in [1, 2]:
+            break
+        ''')
+        self.flakes('''
+        for x in [1, 2]:
+            continue
+        ''')
+
+
+    def test_ellipsis(self):
+        """
+        Ellipsis in a slice is supported.
+        """
+        self.flakes('''
+        [1, 2][...]
+        ''')
+
+
+    def test_extendedSlice(self):
+        """
+        Extended slices are supported.
+        """
+        self.flakes('''
+        x = 3
+        [1, 2][x,:]
         ''')
 
 

pyflakes/test/test_undefined_names.py

 
-from sys import version_info
+from _ast import PyCF_ONLY_AST
 
-from pyflakes import messages as m
+from twisted.trial.unittest import TestCase
+
+from pyflakes import messages as m, checker
 from pyflakes.test import harness
 
 
         ''')
     test_definedByGlobal.todo = ''
 
+    def test_globalInGlobalScope(self):
+        """
+        A global statement in the global scope is ignored.
+        """
+        self.flakes('''
+        global x
+        def foo():
+            print x
+        ''', m.UndefinedName)
+
     def test_del(self):
         '''del deletes bindings'''
         self.flakes('a = 1; del a; a', m.UndefinedName)
         ''', m.UndefinedLocal)
 
 
+    def test_intermediateClassScopeIgnored(self):
+        """
+        If a name defined in an enclosing scope is shadowed by a local variable
+        and the name is used locally before it is bound, an unbound local
+        warning is emitted, even if there is a class scope between the enclosing
+        scope and the local scope.
+        """
+        self.flakes('''
+        def f():
+            x = 1
+            class g:
+                def h(self):
+                    a = x
+                    x = None
+                    print x, a
+            print x
+        ''', m.UndefinedLocal)
+
+
     def test_doubleNestingReportsClosestName(self):
         """
         Test that referencing a local name in a nested scope that shadows a
         warnings.
         """
         self.flakes('(a for a in xrange(10) if a)')
+
+
+
+class NameTests(TestCase):
+    """
+    Tests for some extra cases of name handling.
+    """
+    def test_impossibleContext(self):
+        """
+        A Name node with an unrecognized context results in a RuntimeError being
+        raised.
+        """
+        tree = compile("x = 10", "<test>", "exec", PyCF_ONLY_AST)
+        # Make it into something unrecognizable.
+        tree.body[0].targets[0].ctx = object()
+        self.assertRaises(RuntimeError, checker.Checker, tree)