UtilitiesPython2/py2flakes/checker.py

changeset 3212
27a73942b8c8
parent 3160
209a07d7e401
--- a/UtilitiesPython2/py2flakes/checker.py	Sat Jan 18 14:49:00 2014 +0100
+++ b/UtilitiesPython2/py2flakes/checker.py	Sat Jan 18 14:52:33 2014 +0100
@@ -1,37 +1,65 @@
 # -*- coding: utf-8 -*-
 
-# Copyright (c) 2011 - 2014 Detlev Offenbach <detlev@die-offenbachs.de>
+# Copyright (c) 2010 - 2014 Detlev Offenbach <detlev@die-offenbachs.de>
 #
 # Original (c) 2005-2010 Divmod, Inc.
 #
-# This module is based on pyflakes for Python2 but was heavily hacked to
-# work within eric5
+# This module is based on pyflakes for Python2 and Python3, but was modified to
+# be integrated into eric5
 
-import __builtin__
-import os.path
-import _ast
+import doctest
+import os
+import sys
+try:
+    builtin_vars = dir(__import__('builtins'))
+    PY2 = False
+    ## added for eric5
+    basestring = str
+    ## end added for eric5
+except ImportError:
+    builtin_vars = dir(__import__('__builtin__'))
+    PY2 = True
 
-from py2flakes import messages
-
-
-# utility function to iterate over an AST node's children, adapted
-# from Python 2.6's standard ast module
 try:
     import ast
     iter_child_nodes = ast.iter_child_nodes
-except (ImportError, AttributeError):
-    def iter_child_nodes(node, astcls=_ast.AST):
+except ImportError:     # Python 2.5
+    import _ast as ast
+
+    if 'decorator_list' not in ast.ClassDef._fields:
+        # Patch the missing attribute 'decorator_list'
+        ast.ClassDef.decorator_list = ()
+        ast.FunctionDef.decorator_list = property(lambda s: s.decorators)
+
+    def iter_child_nodes(node):
         """
-        Yield all direct child nodes of *node*, that is, all fields that are
-        nodes and all items of fields that are lists of nodes.
+        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):
+            if isinstance(field, ast.AST):
                 yield field
             elif isinstance(field, list):
                 for item in field:
                     yield item
+# Python >= 3.3 uses ast.Try instead of (ast.TryExcept + ast.TryFinally)
+if hasattr(ast, 'Try'):
+    ast_TryExcept = ast.Try
+    ast_TryFinally = ()
+else:
+    ast_TryExcept = ast.TryExcept
+    ast_TryFinally = ast.TryFinally
+
+from . import messages
+
+if PY2:
+    def getNodeType(node_class):
+        # workaround str.upper() which is locale-dependent
+        return str(unicode(node_class.__name__).upper())  # __IGNORE_WARNING__
+else:
+    def getNodeType(node_class):
+        return node_class.__name__.upper()
 
 
 class Binding(object):
@@ -39,7 +67,7 @@
     Represents the binding of a value to a name.
 
     The checker uses this to keep track of which names have been bound and
-    which names have not. See L{Assignment} for a special type of binding that
+    which names have not. See Assignment for a special type of binding that
     is checked with stricter rules.
     """
     def __init__(self, name, source):
@@ -58,12 +86,6 @@
             id(self))
 
 
-class UnBinding(Binding):
-    '''
-    Created by the 'del' operator.
-    '''
-
-
 class Importation(Binding):
     """
     A binding created by an import statement.
@@ -78,6 +100,14 @@
     """
     Represents binding a name as an argument.
     """
+    pass
+
+
+class Definition(Binding):
+    """
+    A binding that defines a function or a class.
+    """
+    pass
 
 
 class Assignment(Binding):
@@ -88,15 +118,23 @@
     the checker does not consider assignments in tuple/list unpacking to be
     Assignments, rather it treats them as simple Bindings.
     """
+    pass
 
 
-class FunctionDefinition(Binding):
+class FunctionDefinition(Definition):
     """
     Represents a function definition.
     """
     pass
 
 
+class ClassDefinition(Definition):
+    """
+    Represents a class definition.
+    """
+    pass
+
+
 class ExportBinding(Binding):
     """
     A binding created by an __all__ assignment.  If the names in the list
@@ -116,9 +154,9 @@
         Return a list of the names referenced by this binding.
         """
         names = []
-        if isinstance(self.source, _ast.List):
+        if isinstance(self.source, ast.List):
             for node in self.source.elts:
-                if isinstance(node, _ast.Str):
+                if isinstance(node, ast.Str):
                     names.append(node.s)
         return names
 
@@ -130,11 +168,8 @@
     importStarred = False       # set to True when import * is found
 
     def __repr__(self):
-        return '<%s at 0x%x %s>' % (
-            self.__class__.__name__, id(self), dict.__repr__(self))
-
-    def __init__(self):
-        super(Scope, self).__init__()
+        scope_cls = self.__class__.__name__
+        return '<%s at 0x%x %s>' % (scope_cls, id(self), dict.__repr__(self))
 
 
 class ClassScope(Scope):
@@ -148,9 +183,31 @@
     """
     Class representing a name scope for a function.
     """
+    usesLocals = False
+    alwaysUsed = set(['__tracebackhide__',
+                      '__traceback_info__', '__traceback_supplement__'])
+
     def __init__(self):
         super(FunctionScope, self).__init__()
-        self.globals = {}
+        # Simplify: manage the special locals as globals
+        self.globals = self.alwaysUsed.copy()
+
+    def unusedAssignments(self):
+        """
+        Return a generator for the assignments which have not been used.
+        """
+        for name, binding in self.items():
+            if (not binding.used and name not in self.globals
+                    and not self.usesLocals
+                    and isinstance(binding, Assignment)):
+                yield name, binding
+
+
+class GeneratorScope(Scope):
+    """
+    Class representing a name scope for a generator function.
+    """
+    pass
 
 
 class ModuleScope(Scope):
@@ -159,8 +216,20 @@
     """
     pass
 
-# Globally defined names which are not attributes of the __builtin__ module.
-_MAGIC_GLOBALS = ['__file__', '__builtins__']
+# Globally defined names which are not attributes of the builtins module, or
+# are only present on some platforms.
+_MAGIC_GLOBALS = ['__file__', '__builtins__', 'WindowsError']
+
+
+def getNodeName(node):
+    """
+    Module function for getting the name of a node.
+    """
+    # Returns node.id, or node.name, or None
+    if hasattr(node, 'id'):     # One of the many nodes with an id
+        return node.id
+    if hasattr(node, 'name'):   # a ExceptHandler node
+        return node.name
 
 
 class Checker(object):
@@ -168,37 +237,54 @@
     Class to check the cleanliness and sanity of Python code.
     """
     nodeDepth = 0
+    offset = None
     traceTree = False
+    withDoctest = ('PYFLAKES_NODOCTEST' not in os.environ)
 
-    def __init__(self, module, filename='(none)'):
+    builtIns = set(builtin_vars).union(_MAGIC_GLOBALS)
+    _customBuiltIns = os.environ.get('PYFLAKES_BUILTINS')
+    if _customBuiltIns:
+        builtIns.update(_customBuiltIns.split(','))
+    del _customBuiltIns
+
+    def __init__(self, tree, filename='(none)', builtins=None):
         """
         Constructor
         
-        @param module parsed module tree or module source code
+        @param tree parsed module tree or module source code
         @param filename name of the module file (string)
+        @param builtins set of names to be treated as builtins (set of string)
         """
+        self._nodeHandlers = {}
         self._deferredFunctions = []
         self._deferredAssignments = []
-        self.dead_scopes = []
+        self.deadScopes = []
         self.messages = []
         self.filename = filename
+        if builtins:
+            self.builtIns = self.builtIns.union(builtins)
         self.scopeStack = [ModuleScope()]
+        self.exceptHandlers = [()]
         self.futuresAllowed = True
         
-        if isinstance(module, str):
-            module = ast.parse(module, filename, "exec")
-        self.handleChildren(module)
-        self._runDeferred(self._deferredFunctions)
+        ## added for eric5
+        if isinstance(tree, basestring):
+            tree = compile(tree, filename, "exec", ast.PyCF_ONLY_AST)
+        ## end added for eric5
+        
+        self.root = tree
+        self.handleChildren(tree)
+        self.runDeferred(self._deferredFunctions)
         # Set _deferredFunctions to None so that deferFunction will fail
         # noisily if called after we've run through the deferred functions.
         self._deferredFunctions = None
-        self._runDeferred(self._deferredAssignments)
+        self.runDeferred(self._deferredAssignments)
         # Set _deferredAssignments to None so that deferAssignment will fail
-        # noisly if called after we've run through the deferred assignments.
+        # noisily if called after we've run through the deferred assignments.
         self._deferredAssignments = None
         del self.scopeStack[1:]
         self.popScope()
-        self.check_dead_scopes()
+        self.checkDeadScopes()
 
     def deferFunction(self, callable):
         '''
@@ -209,68 +295,251 @@
         `callable` is called, the scope at the time this is called will be
         restored, however it will contain any new bindings added to it.
         '''
-        self._deferredFunctions.append((callable, self.scopeStack[:]))
+        self._deferredFunctions.append((callable, self.scopeStack[:],
+                                        self.offset))
 
     def deferAssignment(self, callable):
         """
         Schedule an assignment handler to be called just after deferred
         function handlers.
         """
-        self._deferredAssignments.append((callable, self.scopeStack[:]))
+        self._deferredAssignments.append((callable, self.scopeStack[:],
+                                          self.offset))
 
-    def _runDeferred(self, deferred):
+    def runDeferred(self, deferred):
         """
         Run the callables in deferred using their associated scope stack.
         """
-        for handler, scope in deferred:
+        for handler, scope, offset in deferred:
             self.scopeStack = scope
+            self.offset = offset
             handler()
 
+    @property
     def scope(self):
         return self.scopeStack[-1]
-    scope = property(scope)
 
     def popScope(self):
-        self.dead_scopes.append(self.scopeStack.pop())
+        self.deadScopes.append(self.scopeStack.pop())
 
-    def check_dead_scopes(self):
+    def checkDeadScopes(self):
         """
         Look at scopes which have been fully examined and report names in them
         which were imported but unused.
         """
-        for scope in self.dead_scopes:
+        for scope in self.deadScopes:
             export = isinstance(scope.get('__all__'), ExportBinding)
             if export:
                 all = scope['__all__'].names()
-                if os.path.split(self.filename)[1] != '__init__.py':
+                if not scope.importStarred and \
+                   os.path.basename(self.filename) != '__init__.py':
                     # Look for possible mistakes in the export list
                     undefined = set(all) - set(scope)
                     for name in undefined:
-                        self.report(
-                            messages.UndefinedExport,
-                            scope['__all__'].source.lineno,
-                            name)
+                        self.report(messages.UndefinedExport,
+                                    scope['__all__'].source, name)
             else:
                 all = []
 
             # Look for imported names that aren't used.
-            for importation in scope.itervalues():
+            for importation in scope.values():
                 if isinstance(importation, Importation):
                     if not importation.used and importation.name not in all:
-                        self.report(
-                            messages.UnusedImport,
-                            importation.source.lineno,
-                            importation.name)
+                        self.report(messages.UnusedImport,
+                                    importation.source, importation.name)
+
+    def pushScope(self, scopeClass=FunctionScope):
+        self.scopeStack.append(scopeClass())
 
-    def pushFunctionScope(self):
-        self.scopeStack.append(FunctionScope())
+    def pushFunctionScope(self):    # XXX Deprecated
+        self.pushScope(FunctionScope)
 
-    def pushClassScope(self):
-        self.scopeStack.append(ClassScope())
+    def pushClassScope(self):       # XXX Deprecated
+        self.pushScope(ClassScope)
 
     def report(self, messageClass, *args, **kwargs):
         self.messages.append(messageClass(self.filename, *args, **kwargs))
 
+    def hasParent(self, node, kind):
+        while hasattr(node, 'parent'):
+            node = node.parent
+            if isinstance(node, kind):
+                return True
+
+    def getCommonAncestor(self, lnode, rnode, stop=None):
+        if not stop:
+            stop = self.root
+        if lnode is rnode:
+            return lnode
+        if stop in (lnode, rnode):
+            return stop
+
+        if not hasattr(lnode, 'parent') or not hasattr(rnode, 'parent'):
+            return
+        if (lnode.level > rnode.level):
+            return self.getCommonAncestor(lnode.parent, rnode, stop)
+        if (rnode.level > lnode.level):
+            return self.getCommonAncestor(lnode, rnode.parent, stop)
+        return self.getCommonAncestor(lnode.parent, rnode.parent, stop)
+
+    def descendantOf(self, node, ancestors, stop=None):
+        for a in ancestors:
+            if self.getCommonAncestor(node, a, stop) not in (stop, None):
+                return True
+        return False
+
+    def onFork(self, parent, lnode, rnode, items):
+        return (self.descendantOf(lnode, items, parent) ^
+                self.descendantOf(rnode, items, parent))
+
+    def differentForks(self, lnode, rnode):
+        """True, if lnode and rnode are located on different forks of IF/TRY"""
+        ancestor = self.getCommonAncestor(lnode, rnode)
+        if isinstance(ancestor, ast.If):
+            for fork in (ancestor.body, ancestor.orelse):
+                if self.onFork(ancestor, lnode, rnode, fork):
+                    return True
+        elif isinstance(ancestor, ast_TryExcept):
+            body = ancestor.body + ancestor.orelse
+            for fork in [body] + [[hdl] for hdl in ancestor.handlers]:
+                if self.onFork(ancestor, lnode, rnode, fork):
+                    return True
+        elif isinstance(ancestor, ast_TryFinally):
+            if self.onFork(ancestor, lnode, rnode, ancestor.body):
+                return True
+        return False
+
+    def addBinding(self, node, value, reportRedef=True):
+        """
+        Called when a binding is altered.
+
+        - `node` is the statement responsible for the change
+        - `value` is the optional new value, a Binding instance, associated
+          with the binding; if None, the binding is deleted if it exists.
+        - if `reportRedef` is True (default), rebinding while unused will be
+          reported.
+        """
+        redefinedWhileUnused = False
+        if not isinstance(self.scope, ClassScope):
+            for scope in self.scopeStack[::-1]:
+                existing = scope.get(value.name)
+                if (isinstance(existing, Importation)
+                        and not existing.used
+                        and (not isinstance(value, Importation) or
+                             value.fullName == existing.fullName)
+                        and reportRedef
+                        and not self.differentForks(node, existing.source)):
+                    redefinedWhileUnused = True
+                    self.report(messages.RedefinedWhileUnused,
+                                node, value.name, existing.source)
+
+        existing = self.scope.get(value.name)
+        if not redefinedWhileUnused and self.hasParent(value.source, ast.ListComp):
+            if (existing and reportRedef
+                    and not self.hasParent(existing.source, (ast.For, ast.ListComp))
+                    and not self.differentForks(node, existing.source)):
+                self.report(messages.RedefinedInListComp,
+                            node, value.name, existing.source)
+
+        if (isinstance(existing, Definition)
+                and not existing.used
+                and not self.differentForks(node, existing.source)):
+            self.report(messages.RedefinedWhileUnused,
+                        node, value.name, existing.source)
+        else:
+            self.scope[value.name] = value
+
+    def getNodeHandler(self, node_class):
+        try:
+            return self._nodeHandlers[node_class]
+        except KeyError:
+            nodeType = getNodeType(node_class)
+        self._nodeHandlers[node_class] = handler = getattr(self, nodeType)
+        return handler
+
+    def handleNodeLoad(self, node):
+        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))]
+        if isinstance(self.scope, GeneratorScope) and scopes[-1] != self.scopeStack[-2]:
+            scopes.append(self.scopeStack[-2])
+
+        # try enclosing function scopes and global scope
+        importStarred = self.scope.importStarred
+        for scope in reversed(scopes):
+            importStarred = importStarred or scope.importStarred
+            try:
+                scope[name].used = (self.scope, node)
+            except KeyError:
+                pass
+            else:
+                return
+
+        # look in the built-ins
+        if importStarred or name in self.builtIns:
+            return
+        if name == '__path__' and os.path.basename(self.filename) == '__init__.py':
+            # the special name __path__ is valid only in packages
+            return
+
+        # protected with a NameError handler?
+        if 'NameError' not in self.exceptHandlers[-1]:
+            self.report(messages.UndefinedName, node, name)
+
+    def handleNodeStore(self, node):
+        name = getNodeName(node)
+        if not name:
+            return
+        # if the name hasn't already been defined in the current scope
+        if isinstance(self.scope, FunctionScope) and name not in self.scope:
+            # for each function or module scope above us
+            for scope in self.scopeStack[:-1]:
+                if not isinstance(scope, (FunctionScope, ModuleScope)):
+                    continue
+                # if the name was defined in that scope, and the name has
+                # been accessed already in the current scope, and hasn't
+                # been declared global
+                used = name in scope and scope[name].used
+                if used and used[0] is self.scope and name not in self.scope.globals:
+                    # then it's probably a mistake
+                    self.report(messages.UndefinedLocal,
+                                scope[name].used[1], name, scope[name].source)
+                    break
+
+        parent = getattr(node, 'parent', None)
+        if isinstance(parent, (ast.For, ast.comprehension, ast.Tuple, ast.List)):
+            binding = Binding(name, node)
+        elif (parent is not None and name == '__all__' and
+              isinstance(self.scope, ModuleScope)):
+            binding = ExportBinding(name, parent.value)
+        else:
+            binding = Assignment(name, node)
+        if name in self.scope:
+            binding.used = self.scope[name].used
+        self.addBinding(node, binding)
+
+    def handleNodeDelete(self, node):
+        name = getNodeName(node)
+        if not name:
+            return
+        if isinstance(self.scope, FunctionScope) and name in self.scope.globals:
+            self.scope.globals.remove(name)
+        else:
+            try:
+                del self.scope[name]
+            except KeyError:
+                self.report(messages.UndefinedName, node, name)
+
     def handleChildren(self, tree):
         for node in iter_child_nodes(tree):
             self.handleNode(node, tree)
@@ -280,45 +549,84 @@
         Determine if the given node is a docstring, as long as it is at the
         correct place in the node tree.
         """
-        return isinstance(node, _ast.Str) or \
-               (isinstance(node, _ast.Expr) and
-                isinstance(node.value, _ast.Str))
+        return isinstance(node, ast.Str) or (isinstance(node, ast.Expr) and
+                                             isinstance(node.value, ast.Str))
+
+    def getDocstring(self, node):
+        if isinstance(node, ast.Expr):
+            node = node.value
+        if not isinstance(node, ast.Str):
+            return (None, None)
+        # Computed incorrectly if the docstring has backslash
+        doctest_lineno = node.lineno - node.s.count('\n') - 1
+        return (node.s, doctest_lineno)
 
     def handleNode(self, node, parent):
-        if node:
-            node.parent = parent
-            if self.traceTree:
-                print '  ' * self.nodeDepth + node.__class__.__name__
-            self.nodeDepth += 1
-            if self.futuresAllowed and not \
-                   (isinstance(node, _ast.ImportFrom) or 
-                    self.isDocstring(node)):
-                self.futuresAllowed = False
-            nodeType = node.__class__.__name__.upper()
+        if node is None:
+            return
+        if self.offset and getattr(node, 'lineno', None) is not None:
+            node.lineno += self.offset[0]
+            node.col_offset += self.offset[1]
+        if self.traceTree:
+            print('  ' * self.nodeDepth + node.__class__.__name__)
+        if self.futuresAllowed and not (isinstance(node, ast.ImportFrom) or
+                                        self.isDocstring(node)):
+            self.futuresAllowed = False
+        self.nodeDepth += 1
+        node.level = self.nodeDepth
+        node.parent = parent
+        try:
+            handler = self.getNodeHandler(node.__class__)
+            handler(node)
+        finally:
+            self.nodeDepth -= 1
+        if self.traceTree:
+            print('  ' * self.nodeDepth + 'end ' + node.__class__.__name__)
+
+    _getDoctestExamples = doctest.DocTestParser().get_examples
+
+    def handleDoctests(self, node):
+        try:
+            docstring, node_lineno = self.getDocstring(node.body[0])
+            if not docstring:
+                return
+            examples = self._getDoctestExamples(docstring)
+        except (ValueError, IndexError):
+            # e.g. line 6 of the docstring for <string> has inconsistent
+            # leading whitespace: ...
+            return
+        node_offset = self.offset or (0, 0)
+        self.pushScope()
+        for example in examples:
             try:
-                handler = getattr(self, nodeType)
-                handler(node)
-            except AttributeError:
-                print nodeType, "not supported yet. Please report this."
-            finally:
-                self.nodeDepth -= 1
-            if self.traceTree:
-                print '  ' * self.nodeDepth + 'end ' + node.__class__.__name__
+                tree = compile(example.source, "<doctest>", "exec", ast.PyCF_ONLY_AST)
+            except SyntaxError:
+                e = sys.exc_info()[1]
+                position = (node_lineno + example.lineno + e.lineno,
+                            example.indent + 4 + e.offset)
+                self.report(messages.DoctestSyntaxError, node, position)
+            else:
+                self.offset = (node_offset[0] + node_lineno + example.lineno,
+                               node_offset[1] + example.indent + 4)
+                self.handleChildren(tree)
+                self.offset = node_offset
+        self.popScope()
 
     def ignore(self, node):
         pass
 
     # "stmt" type nodes
-    RETURN = DELETE = PRINT = WHILE = IF = WITH = RAISE = TRYEXCEPT = \
+    RETURN = DELETE = PRINT = WHILE = IF = WITH = WITHITEM = RAISE = \
         TRYFINALLY = ASSERT = EXEC = EXPR = handleChildren
 
     CONTINUE = BREAK = PASS = ignore
 
     # "expr" type nodes
-    BOOLOP = BINOP = UNARYOP = IFEXP = DICT = SET = YIELD = COMPARE = \
-    CALL = REPR = ATTRIBUTE = SUBSCRIPT = LIST = TUPLE = handleChildren
+    BOOLOP = BINOP = UNARYOP = IFEXP = DICT = SET = YIELD = YIELDFROM = \
+        COMPARE = CALL = REPR = ATTRIBUTE = SUBSCRIPT = LIST = TUPLE = \
+        STARRED = handleChildren
 
-    NUM = STR = ELLIPSIS = ignore
+    NUM = STR = BYTES = ELLIPSIS = ignore
 
     # "slice" type nodes
     SLICE = EXTSLICE = INDEX = handleChildren
@@ -328,56 +636,20 @@
 
     # same for operators
     AND = OR = ADD = SUB = MULT = DIV = MOD = POW = LSHIFT = RSHIFT = \
-    BITOR = BITXOR = BITAND = FLOORDIV = INVERT = NOT = UADD = USUB = \
-    EQ = NOTEQ = LT = LTE = GT = GTE = IS = ISNOT = IN = NOTIN = ignore
+        BITOR = BITXOR = BITAND = FLOORDIV = INVERT = NOT = UADD = USUB = \
+        EQ = NOTEQ = LT = LTE = GT = GTE = IS = ISNOT = IN = NOTIN = ignore
 
     # additional node types
-    COMPREHENSION = EXCEPTHANDLER = KEYWORD = handleChildren
-
-    def addBinding(self, lineno, value, reportRedef=True):
-        '''
-        Called when a binding is altered.
-
-        @param lineno line of the statement responsible for the change
-            (integer)
-        @param value the optional new value, a Binding instance, associated
-            with the binding; if None, the binding is deleted if it exists
-        @param reportRedef flag indicating if rebinding while unused will be
-            reported (boolean)
-        '''
-        if (isinstance(self.scope.get(value.name), FunctionDefinition)
-                    and isinstance(value, FunctionDefinition)):
-            self.report(messages.RedefinedFunction,
-                        lineno, value.name,
-                        self.scope[value.name].source.lineno)
-
-        if not isinstance(self.scope, ClassScope):
-            for scope in self.scopeStack[::-1]:
-                existing = scope.get(value.name)
-                if (isinstance(existing, Importation)
-                    and not existing.used
-                    and (not isinstance(value, Importation) or
-                          value.fullName == existing.fullName)
-                    and reportRedef):
-
-                    self.report(messages.RedefinedWhileUnused,
-                                lineno, value.name,
-                                scope[value.name].source.lineno)
-
-        if isinstance(value, UnBinding):
-            try:
-                del self.scope[value.name]
-            except KeyError:
-                self.report(messages.UndefinedName, lineno, value.name)
-        else:
-            self.scope[value.name] = value
+    COMPREHENSION = KEYWORD = handleChildren
 
     def GLOBAL(self, node):
         """
         Keep track of globals declarations.
         """
         if isinstance(self.scope, FunctionScope):
-            self.scope.globals.update(dict.fromkeys(node.names))
+            self.scope.globals.update(node.names)
+
+    NONLOCAL = GLOBAL
 
     def LISTCOMP(self, node):
         # handle generators before element
@@ -385,14 +657,23 @@
             self.handleNode(gen, node)
         self.handleNode(node.elt, node)
 
-    GENERATOREXP = SETCOMP = LISTCOMP
+    def GENERATOREXP(self, node):
+        self.pushScope(GeneratorScope)
+        # handle generators before element
+        for gen in node.generators:
+            self.handleNode(gen, node)
+        self.handleNode(node.elt, node)
+        self.popScope()
 
-    # dictionary comprehensions; introduced in Python 2.7
+    SETCOMP = GENERATOREXP
+
     def DICTCOMP(self, node):
+        self.pushScope(GeneratorScope)
         for gen in node.generators:
             self.handleNode(gen, node)
         self.handleNode(node.key, node)
         self.handleNode(node.value, node)
+        self.popScope()
 
     def FOR(self, node):
         """
@@ -401,9 +682,9 @@
         vars = []
 
         def collectLoopVars(n):
-            if isinstance(n, _ast.Name):
+            if isinstance(n, ast.Name):
                 vars.append(n.id)
-            elif isinstance(n, _ast.expr_context):
+            elif isinstance(n, ast.expr_context):
                 return
             else:
                 for c in iter_child_nodes(n):
@@ -415,136 +696,81 @@
                     # unused ones will get an unused import warning
                     and self.scope[varn].used):
                 self.report(messages.ImportShadowedByLoopVar,
-                            node.lineno, varn, self.scope[varn].source.lineno)
+                            node, varn, self.scope[varn].source)
 
         self.handleChildren(node)
 
     def NAME(self, node):
         """
-        Locate the name in locals / function / globals scopes.
+        Handle occurrence of Name (which can be a load/store/delete access.)
         """
-        if isinstance(node.ctx, (_ast.Load, _ast.AugLoad)):
-            # try local scope
-            importStarred = self.scope.importStarred
-            try:
-                self.scope[node.id].used = (self.scope, node.lineno)
-            except KeyError:
-                pass
-            else:
-                return
-
-            # try enclosing function scopes
-
-            for scope in self.scopeStack[-2:0:-1]:
-                importStarred = importStarred or scope.importStarred
-                if not isinstance(scope, FunctionScope):
-                    continue
-                try:
-                    scope[node.id].used = (self.scope, node.lineno)
-                except KeyError:
-                    pass
-                else:
-                    return
-
-            # try global scope
-
-            importStarred = importStarred or self.scopeStack[0].importStarred
-            try:
-                self.scopeStack[0][node.id].used = (self.scope, node.lineno)
-            except KeyError:
-                if ((not hasattr(__builtin__, node.id))
-                        and node.id not in _MAGIC_GLOBALS
-                        and not importStarred):
-                    if (os.path.basename(self.filename) == '__init__.py' and
-                        node.id == '__path__'):
-                        # the special name __path__ is valid only in packages
-                        pass
-                    else:
-                        self.report(messages.UndefinedName,
-                                    node.lineno, node.id)
-        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
-                for scope in self.scopeStack[:-1]:
-                    if not isinstance(scope, (FunctionScope, ModuleScope)):
-                        continue
-                    # if the name was defined in that scope, and the name has
-                    # been accessed already in the current scope, and hasn't
-                    # been declared global
-                    if (node.id in scope
-                            and scope[node.id].used
-                            and scope[node.id].used[0] is self.scope
-                            and node.id not in self.scope.globals):
-                        # then it's probably a mistake
-                        self.report(messages.UndefinedLocal,
-                                    scope[node.id].used[1],
-                                    node.id,
-                                    scope[node.id].source.lineno)
-                        break
-
-            if isinstance(node.parent,
-                          (_ast.For, _ast.comprehension, _ast.Tuple,
-                           _ast.List)):
-                binding = Binding(node.id, node)
-            elif (node.id == '__all__' and
-                  isinstance(self.scope, ModuleScope)):
-                binding = ExportBinding(node.id, node.parent.value)
-            else:
-                binding = Assignment(node.id, node)
-            if node.id in self.scope:
-                binding.used = self.scope[node.id].used
-            self.addBinding(node.lineno, binding)
-        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:
-                self.addBinding(node.lineno, UnBinding(node.id, node))
+        # Locate the name in locals / function / globals scopes.
+        if isinstance(node.ctx, (ast.Load, ast.AugLoad)):
+            self.handleNodeLoad(node)
+            if (node.id == 'locals' and isinstance(self.scope, FunctionScope)
+                    and isinstance(node.parent, ast.Call)):
+                # we are doing locals() call in current scope
+                self.scope.usesLocals = True
+        elif isinstance(node.ctx, (ast.Store, ast.AugStore)):
+            self.handleNodeStore(node)
+        elif isinstance(node.ctx, ast.Del):
+            self.handleNodeDelete(node)
         else:
-            # must be a Param context -- this only happens for names in
-            # function arguments, but these aren't dispatched through here
-            raise RuntimeError(
-                "Got impossible expression context: %r" % (node.ctx,))
+            # must be a Param context -- this only happens for names in function
+            # arguments, but these aren't dispatched through here
+            raise RuntimeError("Got impossible expression context: %r" % (node.ctx,))
 
     def FUNCTIONDEF(self, node):
-        if hasattr(node, 'decorators'):
-            for deco in node.decorators:
-                self.handleNode(deco, node)
-        else:
-            for deco in node.decorator_list:
-                self.handleNode(deco, node)
-        self.addBinding(node.lineno, FunctionDefinition(node.name, node))
+        for deco in node.decorator_list:
+            self.handleNode(deco, node)
+        self.addBinding(node, FunctionDefinition(node.name, node))
         self.LAMBDA(node)
+        if self.withDoctest:
+            self.deferFunction(lambda: self.handleDoctests(node))
 
     def LAMBDA(self, node):
-        for default in node.args.defaults:
-            self.handleNode(default, node)
+        args = []
 
-        def runFunction():
-            args = []
-
+        if PY2:
             def addArgs(arglist):
                 for arg in arglist:
-                    if isinstance(arg, _ast.Tuple):
+                    if isinstance(arg, ast.Tuple):
                         addArgs(arg.elts)
                     else:
                         if arg.id in args:
                             self.report(messages.DuplicateArgument,
-                                        node.lineno, arg.id)
+                                        node, arg.id)
                         args.append(arg.id)
-
-            self.pushFunctionScope()
             addArgs(node.args.args)
-            # vararg/kwarg identifiers are not Name nodes
-            if node.args.vararg:
-                args.append(node.args.vararg)
-            if node.args.kwarg:
-                args.append(node.args.kwarg)
+            defaults = node.args.defaults
+        else:
+            for arg in node.args.args + node.args.kwonlyargs:
+                if arg.arg in args:
+                    self.report(messages.DuplicateArgument,
+                                node, arg.arg)
+                args.append(arg.arg)
+                self.handleNode(arg.annotation, node)
+            if hasattr(node, 'returns'):    # Only for FunctionDefs
+                for annotation in (node.args.varargannotation,
+                                   node.args.kwargannotation, node.returns):
+                    self.handleNode(annotation, node)
+            defaults = node.args.defaults + node.args.kw_defaults
+
+        # vararg/kwarg identifiers are not Name nodes
+        for wildcard in (node.args.vararg, node.args.kwarg):
+            if not wildcard:
+                continue
+            if wildcard in args:
+                self.report(messages.DuplicateArgument, node, wildcard)
+            args.append(wildcard)
+        for default in defaults:
+            self.handleNode(default, node)
+
+        def runFunction():
+
+            self.pushScope()
             for name in args:
-                self.addBinding(node.lineno, Argument(name, node),
-                                reportRedef=False)
+                self.addBinding(node, Argument(name, node), reportRedef=False)
             if isinstance(node.body, list):
                 # case for FunctionDefs
                 for stmt in node.body:
@@ -557,11 +783,8 @@
                 """
                 Check to see if any assignments have not been used.
                 """
-                for name, binding in self.scope.iteritems():
-                    if (not binding.used and not name in self.scope.globals
-                        and isinstance(binding, Assignment)):
-                        self.report(messages.UnusedVariable,
-                                    binding.source.lineno, name)
+                for name, binding in self.scope.unusedAssignments():
+                    self.report(messages.UnusedVariable, binding.source, name)
             self.deferAssignment(checkUnusedAssignments)
             self.popScope()
 
@@ -573,16 +796,20 @@
         classes, and the body of its definition.  Additionally, add its name to
         the current scope.
         """
-        # decorator_list is present as of Python 2.6
-        for deco in getattr(node, 'decorator_list', []):
+        for deco in node.decorator_list:
             self.handleNode(deco, node)
         for baseNode in node.bases:
             self.handleNode(baseNode, node)
-        self.pushClassScope()
+        if not PY2:
+            for keywordNode in node.keywords:
+                self.handleNode(keywordNode, node)
+        self.pushScope(ClassScope)
+        if self.withDoctest:
+            self.deferFunction(lambda: self.handleDoctests(node))
         for stmt in node.body:
             self.handleNode(stmt, node)
         self.popScope()
-        self.addBinding(node.lineno, Binding(node.name, node))
+        self.addBinding(node, ClassDefinition(node.name, node))
 
     def ASSIGN(self, node):
         self.handleNode(node.value, node)
@@ -590,38 +817,59 @@
             self.handleNode(target, node)
 
     def AUGASSIGN(self, node):
-        # AugAssign is awkward: must set the context explicitly and visit
-        # twice, once with AugLoad context, once with AugStore context
-        node.target.ctx = _ast.AugLoad()
-        self.handleNode(node.target, node)
+        self.handleNodeLoad(node.target)
         self.handleNode(node.value, node)
-        node.target.ctx = _ast.AugStore()
         self.handleNode(node.target, node)
 
     def IMPORT(self, node):
         for alias in node.names:
             name = alias.asname or alias.name
             importation = Importation(name, node)
-            self.addBinding(node.lineno, importation)
+            self.addBinding(node, importation)
 
     def IMPORTFROM(self, node):
         if node.module == '__future__':
             if not self.futuresAllowed:
-                self.report(messages.LateFutureImport, node.lineno,
-                            [n.name for n in node.names])
+                self.report(messages.LateFutureImport,
+                            node, [n.name for n in node.names])
         else:
             self.futuresAllowed = False
 
         for alias in node.names:
             if alias.name == '*':
                 self.scope.importStarred = True
-                self.report(messages.ImportStarUsed, node.lineno, node.module)
+                self.report(messages.ImportStarUsed, node, node.module)
                 continue
             name = alias.asname or alias.name
             importation = Importation(name, node)
             if node.module == '__future__':
-                importation.used = (self.scope, node.lineno)
-            self.addBinding(node.lineno, importation)
-    
-#
-# eflag: FileType = Python2
+                importation.used = (self.scope, node)
+            self.addBinding(node, importation)
+
+    def TRY(self, node):
+        handler_names = []
+        # List the exception handlers
+        for handler in node.handlers:
+            if isinstance(handler.type, ast.Tuple):
+                for exc_type in handler.type.elts:
+                    handler_names.append(getNodeName(exc_type))
+            elif handler.type:
+                handler_names.append(getNodeName(handler.type))
+        # Memorize the except handlers and process the body
+        self.exceptHandlers.append(handler_names)
+        for child in node.body:
+            self.handleNode(child, node)
+        self.exceptHandlers.pop()
+        # Process the other nodes: "except:", "else:", "finally:"
+        for child in iter_child_nodes(node):
+            if child not in node.body:
+                self.handleNode(child, node)
+
+    TRYEXCEPT = TRY
+
+    def EXCEPTHANDLER(self, node):
+        # 3.x: in addition to handling children, we must handle the name of
+        # the exception, which is not a Name node, but a simple string.
+        if isinstance(node.name, str):
+            self.handleNodeStore(node)
+        self.handleChildren(node)

eric ide

mercurial