| Index: third_party/pylint/checkers/variables.py
 | 
| diff --git a/third_party/pylint/checkers/variables.py b/third_party/pylint/checkers/variables.py
 | 
| index c75768bb66103caa6661a876d09bd22291787a1d..8f6f95743226d934f9cbc0442ed8793e8836a69e 100644
 | 
| --- a/third_party/pylint/checkers/variables.py
 | 
| +++ b/third_party/pylint/checkers/variables.py
 | 
| @@ -17,22 +17,26 @@
 | 
|  """
 | 
|  import os
 | 
|  import sys
 | 
| +import re
 | 
|  from copy import copy
 | 
|  
 | 
|  import astroid
 | 
| -from astroid import are_exclusive, builtin_lookup, AstroidBuildingException
 | 
| +from astroid import are_exclusive, builtin_lookup
 | 
| +from astroid import modutils
 | 
|  
 | 
| -from logilab.common.modutils import file_from_modpath
 | 
| -
 | 
| -from pylint.interfaces import IAstroidChecker
 | 
| +from pylint.interfaces import IAstroidChecker, INFERENCE, INFERENCE_FAILURE, HIGH
 | 
|  from pylint.utils import get_global_option
 | 
|  from pylint.checkers import BaseChecker
 | 
|  from pylint.checkers.utils import (
 | 
|      PYMETHODS, is_ancestor_name, is_builtin,
 | 
|      is_defined_before, is_error, is_func_default, is_func_decorator,
 | 
|      assign_parent, check_messages, is_inside_except, clobber_in_except,
 | 
| -    get_all_elements)
 | 
| +    get_all_elements, has_known_bases)
 | 
| +import six
 | 
| +
 | 
| +SPECIAL_OBJ = re.compile("^_{2}[a-z]+_{2}$")
 | 
|  
 | 
| +PY3K = sys.version_info >= (3, 0)
 | 
|  
 | 
|  def in_for_else_branch(parent, stmt):
 | 
|      """Returns True if stmt in inside the else branch for a parent For stmt."""
 | 
| @@ -42,7 +46,7 @@ def in_for_else_branch(parent, stmt):
 | 
|  def overridden_method(klass, name):
 | 
|      """get overridden method if any"""
 | 
|      try:
 | 
| -        parent = klass.local_attr_ancestors(name).next()
 | 
| +        parent = next(klass.local_attr_ancestors(name))
 | 
|      except (StopIteration, KeyError):
 | 
|          return None
 | 
|      try:
 | 
| @@ -134,6 +138,55 @@ def _detect_global_scope(node, frame, defframe):
 | 
|      # and the definition of the first depends on the second.
 | 
|      return frame.lineno < defframe.lineno
 | 
|  
 | 
| +def _fix_dot_imports(not_consumed):
 | 
| +    """ Try to fix imports with multiple dots, by returning a dictionary
 | 
| +    with the import names expanded. The function unflattens root imports,
 | 
| +    like 'xml' (when we have both 'xml.etree' and 'xml.sax'), to 'xml.etree'
 | 
| +    and 'xml.sax' respectively.
 | 
| +    """
 | 
| +    # TODO: this should be improved in issue astroid #46
 | 
| +    names = {}
 | 
| +    for name, stmts in six.iteritems(not_consumed):
 | 
| +        if any(isinstance(stmt, astroid.AssName)
 | 
| +               and isinstance(stmt.ass_type(), astroid.AugAssign)
 | 
| +               for stmt in stmts):
 | 
| +            continue
 | 
| +        for stmt in stmts:
 | 
| +            if not isinstance(stmt, (astroid.From, astroid.Import)):
 | 
| +                continue
 | 
| +            for imports in stmt.names:
 | 
| +                second_name = None
 | 
| +                if imports[0] == "*":
 | 
| +                    # In case of wildcard imports,
 | 
| +                    # pick the name from inside the imported module.
 | 
| +                    second_name = name
 | 
| +                else:
 | 
| +                    if imports[0].find(".") > -1 or name in imports:
 | 
| +                        # Most likely something like 'xml.etree',
 | 
| +                        # which will appear in the .locals as 'xml'.
 | 
| +                        # Only pick the name if it wasn't consumed.
 | 
| +                        second_name = imports[0]
 | 
| +                if second_name and second_name not in names:
 | 
| +                    names[second_name] = stmt
 | 
| +    return sorted(names.items(), key=lambda a: a[1].fromlineno)
 | 
| +
 | 
| +def _find_frame_imports(name, frame):
 | 
| +    """
 | 
| +    Detect imports in the frame, with the required
 | 
| +    *name*. Such imports can be considered assignments.
 | 
| +    Returns True if an import for the given name was found.
 | 
| +    """
 | 
| +    imports = frame.nodes_of_class((astroid.Import, astroid.From))
 | 
| +    for import_node in imports:
 | 
| +        for import_name, import_alias in import_node.names:
 | 
| +            # If the import uses an alias, check only that.
 | 
| +            # Otherwise, check only the import name.
 | 
| +            if import_alias:
 | 
| +                if import_alias == name:
 | 
| +                    return True
 | 
| +            elif import_name and import_name == name:
 | 
| +                return True
 | 
| +
 | 
|  
 | 
|  MSGS = {
 | 
|      'E0601': ('Using variable %r before assignment',
 | 
| @@ -164,13 +217,13 @@ MSGS = {
 | 
|      'W0603': ('Using the global statement', # W0121
 | 
|                'global-statement',
 | 
|                'Used when you use the "global" statement to update a global \
 | 
| -              variable. PyLint just try to discourage this \
 | 
| +              variable. Pylint just try to discourage this \
 | 
|                usage. That doesn\'t mean you can not use it !'),
 | 
|      'W0604': ('Using the global statement at the module level', # W0103
 | 
|                'global-at-module-level',
 | 
|                'Used when you use the "global" statement at the module level \
 | 
|                since it has no effect'),
 | 
| -    'W0611': ('Unused import %s',
 | 
| +    'W0611': ('Unused %s',
 | 
|                'unused-import',
 | 
|                'Used when an imported module or variable is not used.'),
 | 
|      'W0612': ('Unused variable %r',
 | 
| @@ -250,6 +303,13 @@ variables (i.e. expectedly not used).'}),
 | 
|                   'help' : 'List of additional names supposed to be defined in \
 | 
|  builtins. Remember that you should avoid to define new builtins when possible.'
 | 
|                  }),
 | 
| +               ("callbacks",
 | 
| +                {'default' : ('cb_', '_cb'), 'type' : 'csv',
 | 
| +                 'metavar' : '<callbacks>',
 | 
| +                 'help' : 'List of strings which can identify a callback '
 | 
| +                          'function by name. A callback name must start or '
 | 
| +                          'end with one of those strings.'}
 | 
| +               )
 | 
|                )
 | 
|      def __init__(self, linter=None):
 | 
|          BaseChecker.__init__(self, linter)
 | 
| @@ -261,12 +321,14 @@ builtins. Remember that you should avoid to define new builtins when possible.'
 | 
|          checks globals doesn't overrides builtins
 | 
|          """
 | 
|          self._to_consume = [(copy(node.locals), {}, 'module')]
 | 
| -        for name, stmts in node.locals.iteritems():
 | 
| +        for name, stmts in six.iteritems(node.locals):
 | 
|              if is_builtin(name) and not is_inside_except(stmts[0]):
 | 
|                  # do not print Redefining builtin for additional builtins
 | 
|                  self.add_message('redefined-builtin', args=name, node=stmts[0])
 | 
|  
 | 
| -    @check_messages('unused-import', 'unused-wildcard-import', 'redefined-builtin', 'undefined-all-variable', 'invalid-all-object')
 | 
| +    @check_messages('unused-import', 'unused-wildcard-import',
 | 
| +                    'redefined-builtin', 'undefined-all-variable',
 | 
| +                    'invalid-all-object')
 | 
|      def leave_module(self, node):
 | 
|          """leave module: check globals
 | 
|          """
 | 
| @@ -274,17 +336,18 @@ builtins. Remember that you should avoid to define new builtins when possible.'
 | 
|          not_consumed = self._to_consume.pop()[0]
 | 
|          # attempt to check for __all__ if defined
 | 
|          if '__all__' in node.locals:
 | 
| -            assigned = node.igetattr('__all__').next()
 | 
| +            assigned = next(node.igetattr('__all__'))
 | 
|              if assigned is not astroid.YES:
 | 
|                  for elt in getattr(assigned, 'elts', ()):
 | 
|                      try:
 | 
| -                        elt_name = elt.infer().next()
 | 
| +                        elt_name = next(elt.infer())
 | 
|                      except astroid.InferenceError:
 | 
|                          continue
 | 
|  
 | 
|                      if not isinstance(elt_name, astroid.Const) \
 | 
| -                             or not isinstance(elt_name.value, basestring):
 | 
| -                        self.add_message('invalid-all-object', args=elt.as_string(), node=elt)
 | 
| +                             or not isinstance(elt_name.value, six.string_types):
 | 
| +                        self.add_message('invalid-all-object',
 | 
| +                                         args=elt.as_string(), node=elt)
 | 
|                          continue
 | 
|                      elt_name = elt_name.value
 | 
|                      # If elt is in not_consumed, remove it from not_consumed
 | 
| @@ -301,7 +364,7 @@ builtins. Remember that you should avoid to define new builtins when possible.'
 | 
|                              if os.path.basename(basename) == '__init__':
 | 
|                                  name = node.name + "." + elt_name
 | 
|                                  try:
 | 
| -                                    file_from_modpath(name.split("."))
 | 
| +                                    modutils.file_from_modpath(name.split("."))
 | 
|                                  except ImportError:
 | 
|                                      self.add_message('undefined-all-variable',
 | 
|                                                       args=elt_name,
 | 
| @@ -314,19 +377,52 @@ builtins. Remember that you should avoid to define new builtins when possible.'
 | 
|          # don't check unused imports in __init__ files
 | 
|          if not self.config.init_import and node.package:
 | 
|              return
 | 
| -        for name, stmts in not_consumed.iteritems():
 | 
| -            if any(isinstance(stmt, astroid.AssName)
 | 
| -                   and isinstance(stmt.ass_type(), astroid.AugAssign)
 | 
| -                   for stmt in stmts):
 | 
| -                continue
 | 
| -            stmt = stmts[0]
 | 
| -            if isinstance(stmt, astroid.Import):
 | 
| -                self.add_message('unused-import', args=name, node=stmt)
 | 
| -            elif isinstance(stmt, astroid.From) and stmt.modname != '__future__':
 | 
| -                if stmt.names[0][0] == '*':
 | 
| -                    self.add_message('unused-wildcard-import', args=name, node=stmt)
 | 
| -                else:
 | 
| -                    self.add_message('unused-import', args=name, node=stmt)
 | 
| +
 | 
| +        self._check_imports(not_consumed)
 | 
| +
 | 
| +    def _check_imports(self, not_consumed):
 | 
| +        local_names = _fix_dot_imports(not_consumed)
 | 
| +        checked = set()
 | 
| +        for name, stmt in local_names:
 | 
| +            for imports in stmt.names:
 | 
| +                real_name = imported_name = imports[0]
 | 
| +                if imported_name == "*":
 | 
| +                    real_name = name
 | 
| +                as_name = imports[1]
 | 
| +                if real_name in checked:
 | 
| +                    continue
 | 
| +                if name not in (real_name, as_name):
 | 
| +                    continue
 | 
| +                checked.add(real_name)
 | 
| +
 | 
| +                if (isinstance(stmt, astroid.Import) or
 | 
| +                        (isinstance(stmt, astroid.From) and
 | 
| +                         not stmt.modname)):
 | 
| +                    if (isinstance(stmt, astroid.From) and
 | 
| +                            SPECIAL_OBJ.search(imported_name)):
 | 
| +                        # Filter special objects (__doc__, __all__) etc.,
 | 
| +                        # because they can be imported for exporting.
 | 
| +                        continue
 | 
| +                    if as_name is None:
 | 
| +                        msg = "import %s" % imported_name
 | 
| +                    else:
 | 
| +                        msg = "%s imported as %s" % (imported_name, as_name)
 | 
| +                    self.add_message('unused-import', args=msg, node=stmt)
 | 
| +                elif isinstance(stmt, astroid.From) and stmt.modname != '__future__':
 | 
| +                    if SPECIAL_OBJ.search(imported_name):
 | 
| +                        # Filter special objects (__doc__, __all__) etc.,
 | 
| +                        # because they can be imported for exporting.
 | 
| +                        continue
 | 
| +                    if imported_name == '*':
 | 
| +                        self.add_message('unused-wildcard-import',
 | 
| +                                         args=name, node=stmt)
 | 
| +                    else:
 | 
| +                        if as_name is None:
 | 
| +                            msg = "%s imported from %s" % (imported_name, stmt.modname)
 | 
| +                        else:
 | 
| +                            fields = (imported_name, stmt.modname, as_name)
 | 
| +                            msg = "%s imported from %s as %s" % fields
 | 
| +                        self.add_message('unused-import', args=msg, node=stmt)
 | 
|          del self._to_consume
 | 
|  
 | 
|      def visit_class(self, node):
 | 
| @@ -418,6 +514,10 @@ builtins. Remember that you should avoid to define new builtins when possible.'
 | 
|          klass = node.parent.frame()
 | 
|          if is_method and (klass.type == 'interface' or node.is_abstract()):
 | 
|              return
 | 
| +        if is_method and isinstance(klass, astroid.Class):
 | 
| +            confidence = INFERENCE if has_known_bases(klass) else INFERENCE_FAILURE
 | 
| +        else:
 | 
| +            confidence = HIGH
 | 
|          authorized_rgx = self.config.dummy_variables_rgx
 | 
|          called_overridden = False
 | 
|          argnames = node.argnames()
 | 
| @@ -428,7 +528,7 @@ builtins. Remember that you should avoid to define new builtins when possible.'
 | 
|          for nonlocal_stmt in node.nodes_of_class(astroid.Nonlocal):
 | 
|              nonlocal_names.update(set(nonlocal_stmt.names))
 | 
|  
 | 
| -        for name, stmts in not_consumed.iteritems():
 | 
| +        for name, stmts in six.iteritems(not_consumed):
 | 
|              # ignore some special names specified by user configuration
 | 
|              if authorized_rgx.match(name):
 | 
|                  continue
 | 
| @@ -468,10 +568,12 @@ builtins. Remember that you should avoid to define new builtins when possible.'
 | 
|                          continue
 | 
|                      if node.name in PYMETHODS and node.name not in ('__init__', '__new__'):
 | 
|                          continue
 | 
| -                # don't check callback arguments XXX should be configurable
 | 
| -                if node.name.startswith('cb_') or node.name.endswith('_cb'):
 | 
| +                # don't check callback arguments
 | 
| +                if any(node.name.startswith(cb) or node.name.endswith(cb)
 | 
| +                       for cb in self.config.callbacks):
 | 
|                      continue
 | 
| -                self.add_message('unused-argument', args=name, node=stmt)
 | 
| +                self.add_message('unused-argument', args=name, node=stmt,
 | 
| +                                 confidence=confidence)
 | 
|              else:
 | 
|                  if stmt.parent and isinstance(stmt.parent, astroid.Assign):
 | 
|                      if name in nonlocal_names:
 | 
| @@ -503,25 +605,7 @@ builtins. Remember that you should avoid to define new builtins when possible.'
 | 
|                      # same scope level assignment
 | 
|                      break
 | 
|              else:
 | 
| -                # global but no assignment
 | 
| -                # Detect imports in the current frame, with the required
 | 
| -                # name. Such imports can be considered assignments.
 | 
| -                imports = frame.nodes_of_class((astroid.Import, astroid.From))
 | 
| -                for import_node in imports:
 | 
| -                    found = False
 | 
| -                    for import_name, import_alias in import_node.names:
 | 
| -                        # If the import uses an alias, check only that.
 | 
| -                        # Otherwise, check only the import name.
 | 
| -                        if import_alias:
 | 
| -                            if import_alias == name:
 | 
| -                                found = True
 | 
| -                                break
 | 
| -                        elif import_name and import_name == name:
 | 
| -                            found = True
 | 
| -                            break
 | 
| -                    if found:
 | 
| -                        break
 | 
| -                else:
 | 
| +                if not _find_frame_imports(name, frame):
 | 
|                      self.add_message('global-variable-not-assigned',
 | 
|                                       args=name, node=node)
 | 
|                  default_message = False
 | 
| @@ -541,7 +625,7 @@ builtins. Remember that you should avoid to define new builtins when possible.'
 | 
|          if default_message:
 | 
|              self.add_message('global-statement', node=node)
 | 
|  
 | 
| -    def _check_late_binding_closure(self, node, assignment_node, scope_type):
 | 
| +    def _check_late_binding_closure(self, node, assignment_node):
 | 
|          def _is_direct_lambda_call():
 | 
|              return (isinstance(node_scope.parent, astroid.CallFunc)
 | 
|                      and node_scope.parent.func is node_scope)
 | 
| @@ -651,18 +735,34 @@ builtins. Remember that you should avoid to define new builtins when possible.'
 | 
|                      base_scope_type == 'comprehension' and i == start_index-1):
 | 
|                  # Detect if we are in a local class scope, as an assignment.
 | 
|                  # For example, the following is fair game.
 | 
| +                #
 | 
|                  # class A:
 | 
|                  #    b = 1
 | 
|                  #    c = lambda b=b: b * b
 | 
| -                class_assignment = (isinstance(frame, astroid.Class) and
 | 
| -                                    name in frame.locals)
 | 
| -                if not class_assignment:
 | 
| +                #
 | 
| +                # class B:
 | 
| +                #    tp = 1
 | 
| +                #    def func(self, arg: tp):
 | 
| +                #        ...
 | 
| +
 | 
| +                in_annotation = (
 | 
| +                    PY3K and isinstance(frame, astroid.Function)
 | 
| +                    and node.statement() is frame and
 | 
| +                    (node in frame.args.annotations
 | 
| +                     or node is frame.args.varargannotation
 | 
| +                     or node is frame.args.kwargannotation))
 | 
| +                if in_annotation:
 | 
| +                    frame_locals = frame.parent.scope().locals
 | 
| +                else:
 | 
| +                    frame_locals = frame.locals
 | 
| +                if not ((isinstance(frame, astroid.Class) or in_annotation)
 | 
| +                        and name in frame_locals):
 | 
|                      continue
 | 
|              # the name has already been consumed, only check it's not a loop
 | 
|              # variable used outside the loop
 | 
|              if name in consumed:
 | 
|                  defnode = assign_parent(consumed[name][0])
 | 
| -                self._check_late_binding_closure(node, defnode, scope_type)
 | 
| +                self._check_late_binding_closure(node, defnode)
 | 
|                  self._loopvar_name(node, name)
 | 
|                  break
 | 
|              # mark the name as consumed if it's defined in this scope
 | 
| @@ -674,7 +774,7 @@ builtins. Remember that you should avoid to define new builtins when possible.'
 | 
|              # checks for use before assignment
 | 
|              defnode = assign_parent(to_consume[name][0])
 | 
|              if defnode is not None:
 | 
| -                self._check_late_binding_closure(node, defnode, scope_type)
 | 
| +                self._check_late_binding_closure(node, defnode)
 | 
|                  defstmt = defnode.statement()
 | 
|                  defframe = defstmt.frame()
 | 
|                  maybee0601 = True
 | 
| @@ -696,10 +796,35 @@ builtins. Remember that you should avoid to define new builtins when possible.'
 | 
|                              maybee0601 = not any(isinstance(child, astroid.Nonlocal)
 | 
|                                                   and name in child.names
 | 
|                                                   for child in defframe.get_children())
 | 
| +
 | 
| +                # Handle a couple of class scoping issues.
 | 
| +                annotation_return = False
 | 
| +                # The class reuses itself in the class scope.
 | 
| +                recursive_klass = (frame is defframe and
 | 
| +                                   defframe.parent_of(node) and
 | 
| +                                   isinstance(defframe, astroid.Class) and
 | 
| +                                   node.name == defframe.name)
 | 
|                  if (self._to_consume[-1][-1] == 'lambda' and
 | 
|                          isinstance(frame, astroid.Class)
 | 
|                          and name in frame.locals):
 | 
|                      maybee0601 = True
 | 
| +                elif (isinstance(defframe, astroid.Class) and
 | 
| +                      isinstance(frame, astroid.Function)):
 | 
| +                    # Special rule for function return annotations,
 | 
| +                    # which uses the same name as the class where
 | 
| +                    # the function lives.
 | 
| +                    if (PY3K and node is frame.returns and
 | 
| +                            defframe.parent_of(frame.returns)):
 | 
| +                        maybee0601 = annotation_return = True
 | 
| +
 | 
| +                    if (maybee0601 and defframe.name in defframe.locals and
 | 
| +                            defframe.locals[name][0].lineno < frame.lineno):
 | 
| +                        # Detect class assignments with the same
 | 
| +                        # name as the class. In this case, no warning
 | 
| +                        # should be raised.
 | 
| +                        maybee0601 = False
 | 
| +                elif recursive_klass:
 | 
| +                    maybee0601 = True
 | 
|                  else:
 | 
|                      maybee0601 = maybee0601 and stmt.fromlineno <= defstmt.fromlineno
 | 
|  
 | 
| @@ -708,8 +833,11 @@ builtins. Remember that you should avoid to define new builtins when possible.'
 | 
|                          and not are_exclusive(stmt, defstmt, ('NameError',
 | 
|                                                                'Exception',
 | 
|                                                                'BaseException'))):
 | 
| -                    if defstmt is stmt and isinstance(node, (astroid.DelName,
 | 
| -                                                             astroid.AssName)):
 | 
| +                    if recursive_klass or (defstmt is stmt and
 | 
| +                                           isinstance(node, (astroid.DelName,
 | 
| +                                                             astroid.AssName))):
 | 
| +                        self.add_message('undefined-variable', args=name, node=node)
 | 
| +                    elif annotation_return:
 | 
|                          self.add_message('undefined-variable', args=name, node=node)
 | 
|                      elif self._to_consume[-1][-1] != 'lambda':
 | 
|                          # E0601 may *not* occurs in lambda scope.
 | 
| @@ -753,7 +881,7 @@ builtins. Remember that you should avoid to define new builtins when possible.'
 | 
|          for name, _ in node.names:
 | 
|              parts = name.split('.')
 | 
|              try:
 | 
| -                module = node.infer_name_module(parts[0]).next()
 | 
| +                module = next(node.infer_name_module(parts[0]))
 | 
|              except astroid.ResolveError:
 | 
|                  continue
 | 
|              self._check_module_attrs(node, module, parts[1:])
 | 
| @@ -765,10 +893,7 @@ builtins. Remember that you should avoid to define new builtins when possible.'
 | 
|          level = getattr(node, 'level', None)
 | 
|          try:
 | 
|              module = node.root().import_module(name_parts[0], level=level)
 | 
| -        except AstroidBuildingException:
 | 
| -            return
 | 
| -        except Exception, exc:
 | 
| -            print 'Unhandled exception in VariablesChecker:', exc
 | 
| +        except Exception: # pylint: disable=broad-except
 | 
|              return
 | 
|          module = self._check_module_attrs(node, module, name_parts[1:])
 | 
|          if not module:
 | 
| @@ -799,6 +924,11 @@ builtins. Remember that you should avoid to define new builtins when possible.'
 | 
|          """
 | 
|          if infered is astroid.YES:
 | 
|              return
 | 
| +        if (isinstance(infered.parent, astroid.Arguments) and
 | 
| +                isinstance(node.value, astroid.Name) and
 | 
| +                node.value.name == infered.parent.vararg):
 | 
| +            # Variable-length argument, we can't determine the length.
 | 
| +            return
 | 
|          if isinstance(infered, (astroid.Tuple, astroid.List)):
 | 
|              # attempt to check unpacking is properly balanced
 | 
|              values = infered.itered()
 | 
| @@ -841,7 +971,7 @@ builtins. Remember that you should avoid to define new builtins when possible.'
 | 
|                  module = None
 | 
|                  break
 | 
|              try:
 | 
| -                module = module.getattr(name)[0].infer().next()
 | 
| +                module = next(module.getattr(name)[0].infer())
 | 
|                  if module is astroid.YES:
 | 
|                      return None
 | 
|              except astroid.NotFoundError:
 | 
| 
 |