| Index: third_party/pylint/checkers/typecheck.py
|
| ===================================================================
|
| --- third_party/pylint/checkers/typecheck.py (revision 292986)
|
| +++ third_party/pylint/checkers/typecheck.py (working copy)
|
| @@ -1,4 +1,4 @@
|
| -# Copyright (c) 2006-2010 LOGILAB S.A. (Paris, FRANCE).
|
| +# Copyright (c) 2006-2013 LOGILAB S.A. (Paris, FRANCE).
|
| # http://www.logilab.fr/ -- mailto:contact@logilab.fr
|
| #
|
| # This program is free software; you can redistribute it and/or modify it under
|
| @@ -12,59 +12,128 @@
|
| #
|
| # You should have received a copy of the GNU General Public License along with
|
| # this program; if not, write to the Free Software Foundation, Inc.,
|
| -# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
| -"""try to find more bugs in the code using astng inference capabilities
|
| +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
| +"""try to find more bugs in the code using astroid inference capabilities
|
| """
|
|
|
| import re
|
| import shlex
|
|
|
| -from logilab import astng
|
| -from logilab.astng import InferenceError, NotFoundError, YES, Instance
|
| +import astroid
|
| +from astroid import InferenceError, NotFoundError, YES, Instance
|
| +from astroid.bases import BUILTINS
|
|
|
| -from pylint.interfaces import IASTNGChecker
|
| +from pylint.interfaces import IAstroidChecker
|
| from pylint.checkers import BaseChecker
|
| from pylint.checkers.utils import safe_infer, is_super, check_messages
|
|
|
| MSGS = {
|
| 'E1101': ('%s %r has no %r member',
|
| + 'no-member',
|
| 'Used when a variable is accessed for an unexistent member.'),
|
| 'E1102': ('%s is not callable',
|
| + 'not-callable',
|
| 'Used when an object being called has been inferred to a non \
|
| callable object'),
|
| 'E1103': ('%s %r has no %r member (but some types could not be inferred)',
|
| + 'maybe-no-member',
|
| 'Used when a variable is accessed for an unexistent member, but \
|
| - astng was not able to interpret all possible types of this \
|
| + astroid was not able to interpret all possible types of this \
|
| variable.'),
|
| 'E1111': ('Assigning to function call which doesn\'t return',
|
| + 'assignment-from-no-return',
|
| 'Used when an assignment is done on a function call but the \
|
| inferred function doesn\'t return anything.'),
|
| 'W1111': ('Assigning to function call which only returns None',
|
| + 'assignment-from-none',
|
| 'Used when an assignment is done on a function call but the \
|
| inferred function returns nothing but None.'),
|
|
|
| - 'E1120': ('No value passed for parameter %s in function call',
|
| + 'E1120': ('No value for argument %s in %s call',
|
| + 'no-value-for-parameter',
|
| 'Used when a function call passes too few arguments.'),
|
| - 'E1121': ('Too many positional arguments for function call',
|
| + 'E1121': ('Too many positional arguments for %s call',
|
| + 'too-many-function-args',
|
| 'Used when a function call passes too many positional \
|
| arguments.'),
|
| - 'E1122': ('Duplicate keyword argument %r in function call',
|
| + 'E1122': ('Duplicate keyword argument %r in %s call',
|
| + 'duplicate-keyword-arg',
|
| 'Used when a function call passes the same keyword argument \
|
| - multiple times.'),
|
| - 'E1123': ('Passing unexpected keyword argument %r in function call',
|
| + multiple times.',
|
| + {'maxversion': (2, 6)}),
|
| + 'E1123': ('Unexpected keyword argument %r in %s call',
|
| + 'unexpected-keyword-arg',
|
| 'Used when a function call passes a keyword argument that \
|
| doesn\'t correspond to one of the function\'s parameter names.'),
|
| - 'E1124': ('Multiple values passed for parameter %r in function call',
|
| + 'E1124': ('Argument %r passed by position and keyword in %s call',
|
| + 'redundant-keyword-arg',
|
| 'Used when a function call would result in assigning multiple \
|
| values to a function parameter, one value from a positional \
|
| argument and one from a keyword argument.'),
|
| + 'E1125': ('Missing mandatory keyword argument %r in %s call',
|
| + 'missing-kwoa',
|
| + ('Used when a function call does not pass a mandatory'
|
| + ' keyword-only argument.'),
|
| + {'minversion': (3, 0)}),
|
| + 'E1126': ('Sequence index is not an int, slice, or instance with __index__',
|
| + 'invalid-sequence-index',
|
| + 'Used when a sequence type is indexed with an invalid type. '
|
| + 'Valid types are ints, slices, and objects with an __index__ '
|
| + 'method.'),
|
| + 'E1127': ('Slice index is not an int, None, or instance with __index__',
|
| + 'invalid-slice-index',
|
| + 'Used when a slice index is not an integer, None, or an object \
|
| + with an __index__ method.'),
|
| }
|
|
|
| +# builtin sequence types in Python 2 and 3.
|
| +SEQUENCE_TYPES = set(['str', 'unicode', 'list', 'tuple', 'bytearray',
|
| + 'xrange', 'range', 'bytes', 'memoryview'])
|
| +
|
| +def _determine_callable(callable_obj):
|
| + # Ordering is important, since BoundMethod is a subclass of UnboundMethod,
|
| + # and Function inherits Lambda.
|
| + if isinstance(callable_obj, astroid.BoundMethod):
|
| + # Bound methods have an extra implicit 'self' argument.
|
| + return callable_obj, 1, callable_obj.type
|
| + elif isinstance(callable_obj, astroid.UnboundMethod):
|
| + return callable_obj, 0, 'unbound method'
|
| + elif isinstance(callable_obj, astroid.Function):
|
| + return callable_obj, 0, callable_obj.type
|
| + elif isinstance(callable_obj, astroid.Lambda):
|
| + return callable_obj, 0, 'lambda'
|
| + elif isinstance(callable_obj, astroid.Class):
|
| + # Class instantiation, lookup __new__ instead.
|
| + # If we only find object.__new__, we can safely check __init__
|
| + # instead.
|
| + try:
|
| + # Use the last definition of __new__.
|
| + new = callable_obj.local_attr('__new__')[-1]
|
| + except astroid.NotFoundError:
|
| + new = None
|
| +
|
| + if not new or new.parent.scope().name == 'object':
|
| + try:
|
| + # Use the last definition of __init__.
|
| + callable_obj = callable_obj.local_attr('__init__')[-1]
|
| + except astroid.NotFoundError:
|
| + # do nothing, covered by no-init.
|
| + raise ValueError
|
| + else:
|
| + callable_obj = new
|
| +
|
| + if not isinstance(callable_obj, astroid.Function):
|
| + raise ValueError
|
| + # both have an extra implicit 'cls'/'self' argument.
|
| + return callable_obj, 1, 'constructor'
|
| + else:
|
| + raise ValueError
|
| +
|
| class TypeChecker(BaseChecker):
|
| """try to find bugs in the code using type inference
|
| """
|
|
|
| - __implements__ = (IASTNGChecker,)
|
| + __implements__ = (IAstroidChecker,)
|
|
|
| # configuration section name
|
| name = 'typecheck'
|
| @@ -77,8 +146,16 @@
|
| 'help' : 'Tells whether missing members accessed in mixin \
|
| class should be ignored. A mixin class is detected if its name ends with \
|
| "mixin" (case insensitive).'}
|
| - ),
|
| -
|
| + ),
|
| + ('ignored-modules',
|
| + {'default': (),
|
| + 'type': 'csv',
|
| + 'metavar': '<module names>',
|
| + 'help': 'List of module names for which member attributes \
|
| +should not be checked (useful for modules/projects where namespaces are \
|
| +manipulated during runtime and thus existing member attributes cannot be \
|
| +deduced by static analysis'},
|
| + ),
|
| ('ignored-classes',
|
| {'default' : ('SQLObject',),
|
| 'type' : 'csv',
|
| @@ -85,23 +162,22 @@
|
| 'metavar' : '<members names>',
|
| 'help' : 'List of classes names for which member attributes \
|
| should not be checked (useful for classes with attributes dynamically set).'}
|
| - ),
|
| + ),
|
|
|
| ('zope',
|
| {'default' : False, 'type' : 'yn', 'metavar': '<y_or_n>',
|
| 'help' : 'When zope mode is activated, add a predefined set \
|
| of Zope acquired attributes to generated-members.'}
|
| - ),
|
| + ),
|
| ('generated-members',
|
| - {'default' : (
|
| - 'REQUEST', 'acl_users', 'aq_parent'),
|
| + {'default' : ('REQUEST', 'acl_users', 'aq_parent'),
|
| 'type' : 'string',
|
| 'metavar' : '<members names>',
|
| 'help' : 'List of members which are set dynamically and \
|
| missed by pylint inference system, and so shouldn\'t trigger E0201 when \
|
| accessed. Python regular expressions are accepted.'}
|
| - ),
|
| - )
|
| + ),
|
| + )
|
|
|
| def open(self):
|
| # do this in open since config not fully initialized in __init__
|
| @@ -110,13 +186,13 @@
|
| self.generated_members.extend(('REQUEST', 'acl_users', 'aq_parent'))
|
|
|
| def visit_assattr(self, node):
|
| - if isinstance(node.ass_type(), astng.AugAssign):
|
| + if isinstance(node.ass_type(), astroid.AugAssign):
|
| self.visit_getattr(node)
|
|
|
| def visit_delattr(self, node):
|
| self.visit_getattr(node)
|
|
|
| - @check_messages('E1101', 'E1103')
|
| + @check_messages('no-member', 'maybe-no-member')
|
| def visit_getattr(self, node):
|
| """check that the accessed attribute exists
|
|
|
| @@ -132,6 +208,7 @@
|
| if isinstance(self.config.generated_members, str):
|
| gen = shlex.shlex(self.config.generated_members)
|
| gen.whitespace += ','
|
| + gen.wordchars += '[]-+'
|
| self.config.generated_members = tuple(tok.strip('"') for tok in gen)
|
| for pattern in self.config.generated_members:
|
| # attribute is marked as generated, stop here
|
| @@ -151,7 +228,7 @@
|
| inference_failure = True
|
| continue
|
| # skip None anyway
|
| - if isinstance(owner, astng.Const) and owner.value is None:
|
| + if isinstance(owner, astroid.Const) and owner.value is None:
|
| continue
|
| # XXX "super" / metaclass call
|
| if is_super(owner) or getattr(owner, 'type', None) == 'metaclass':
|
| @@ -163,7 +240,7 @@
|
| continue
|
| try:
|
| if not [n for n in owner.getattr(node.attrname)
|
| - if not isinstance(n.statement(), astng.AugAssign)]:
|
| + if not isinstance(n.statement(), astroid.AugAssign)]:
|
| missingattr.add((owner, name))
|
| continue
|
| except AttributeError:
|
| @@ -170,12 +247,12 @@
|
| # XXX method / function
|
| continue
|
| except NotFoundError:
|
| - if isinstance(owner, astng.Function) and owner.decorators:
|
| - continue
|
| + if isinstance(owner, astroid.Function) and owner.decorators:
|
| + continue
|
| if isinstance(owner, Instance) and owner.has_dynamic_getattr():
|
| continue
|
| - # explicit skipping of optparse'Values class
|
| - if owner.name == 'Values' and owner.root().name == 'optparse':
|
| + # explicit skipping of module member access
|
| + if owner.root().name in self.config.ignored_modules:
|
| continue
|
| missingattr.add((owner, name))
|
| continue
|
| @@ -194,55 +271,122 @@
|
| continue
|
| done.add(actual)
|
| if inference_failure:
|
| - msgid = 'E1103'
|
| + msgid = 'maybe-no-member'
|
| else:
|
| - msgid = 'E1101'
|
| + msgid = 'no-member'
|
| self.add_message(msgid, node=node,
|
| args=(owner.display_type(), name,
|
| node.attrname))
|
|
|
| -
|
| + @check_messages('assignment-from-no-return', 'assignment-from-none')
|
| def visit_assign(self, node):
|
| """check that if assigning to a function call, the function is
|
| possibly returning something valuable
|
| """
|
| - if not isinstance(node.value, astng.CallFunc):
|
| + if not isinstance(node.value, astroid.CallFunc):
|
| return
|
| function_node = safe_infer(node.value.func)
|
| # skip class, generator and incomplete function definition
|
| - if not (isinstance(function_node, astng.Function) and
|
| + if not (isinstance(function_node, astroid.Function) and
|
| function_node.root().fully_defined()):
|
| return
|
| if function_node.is_generator() \
|
| or function_node.is_abstract(pass_is_abstract=False):
|
| return
|
| - returns = list(function_node.nodes_of_class(astng.Return,
|
| - skip_klass=astng.Function))
|
| + returns = list(function_node.nodes_of_class(astroid.Return,
|
| + skip_klass=astroid.Function))
|
| if len(returns) == 0:
|
| - self.add_message('E1111', node=node)
|
| + self.add_message('assignment-from-no-return', node=node)
|
| else:
|
| for rnode in returns:
|
| - if not (isinstance(rnode.value, astng.Const)
|
| - and rnode.value.value is None):
|
| + if not (isinstance(rnode.value, astroid.Const)
|
| + and rnode.value.value is None
|
| + or rnode.value is None):
|
| break
|
| else:
|
| - self.add_message('W1111', node=node)
|
| + self.add_message('assignment-from-none', node=node)
|
|
|
| + def _check_uninferable_callfunc(self, node):
|
| + """
|
| + Check that the given uninferable CallFunc node does not
|
| + call an actual function.
|
| + """
|
| + if not isinstance(node.func, astroid.Getattr):
|
| + return
|
| +
|
| + # Look for properties. First, obtain
|
| + # the lhs of the Getattr node and search the attribute
|
| + # there. If that attribute is a property or a subclass of properties,
|
| + # then most likely it's not callable.
|
| +
|
| + # TODO: since astroid doesn't understand descriptors very well
|
| + # we will not handle them here, right now.
|
| +
|
| + expr = node.func.expr
|
| + klass = safe_infer(expr)
|
| + if (klass is None or klass is astroid.YES or
|
| + not isinstance(klass, astroid.Instance)):
|
| + return
|
| +
|
| + try:
|
| + attrs = klass._proxied.getattr(node.func.attrname)
|
| + except astroid.NotFoundError:
|
| + return
|
| +
|
| + stop_checking = False
|
| + for attr in attrs:
|
| + if attr is astroid.YES:
|
| + continue
|
| + if stop_checking:
|
| + break
|
| + if not isinstance(attr, astroid.Function):
|
| + continue
|
| +
|
| + # Decorated, see if it is decorated with a property
|
| + if not attr.decorators:
|
| + continue
|
| + for decorator in attr.decorators.nodes:
|
| + if not isinstance(decorator, astroid.Name):
|
| + continue
|
| + try:
|
| + for infered in decorator.infer():
|
| + property_like = False
|
| + if isinstance(infered, astroid.Class):
|
| + if (infered.root().name == BUILTINS and
|
| + infered.name == 'property'):
|
| + property_like = True
|
| + else:
|
| + for ancestor in infered.ancestors():
|
| + if (ancestor.name == 'property' and
|
| + ancestor.root().name == BUILTINS):
|
| + property_like = True
|
| + break
|
| + if property_like:
|
| + self.add_message('not-callable', node=node,
|
| + args=node.func.as_string())
|
| + stop_checking = True
|
| + break
|
| + except InferenceError:
|
| + pass
|
| + if stop_checking:
|
| + break
|
| +
|
| + @check_messages(*(list(MSGS.keys())))
|
| def visit_callfunc(self, node):
|
| """check that called functions/methods are inferred to callable objects,
|
| and that the arguments passed to the function match the parameters in
|
| the inferred function's definition
|
| """
|
| -
|
| # Build the set of keyword arguments, checking for duplicate keywords,
|
| # and count the positional arguments.
|
| keyword_args = set()
|
| num_positional_args = 0
|
| for arg in node.args:
|
| - if isinstance(arg, astng.Keyword):
|
| + if isinstance(arg, astroid.Keyword):
|
| keyword = arg.arg
|
| if keyword in keyword_args:
|
| - self.add_message('E1122', node=node, args=keyword)
|
| + self.add_message('duplicate-keyword-arg', node=node,
|
| + args=(keyword, 'function'))
|
| keyword_args.add(keyword)
|
| else:
|
| num_positional_args += 1
|
| @@ -250,31 +394,23 @@
|
| called = safe_infer(node.func)
|
| # only function, generator and object defining __call__ are allowed
|
| if called is not None and not called.callable():
|
| - self.add_message('E1102', node=node, args=node.func.as_string())
|
| + self.add_message('not-callable', node=node,
|
| + args=node.func.as_string())
|
|
|
| - # Note that BoundMethod is a subclass of UnboundMethod (huh?), so must
|
| - # come first in this 'if..else'.
|
| - if isinstance(called, astng.BoundMethod):
|
| - # Bound methods have an extra implicit 'self' argument.
|
| - num_positional_args += 1
|
| - elif isinstance(called, astng.UnboundMethod):
|
| - if called.decorators is not None:
|
| - for d in called.decorators.nodes:
|
| - if isinstance(d, astng.Name) and (d.name == 'classmethod'):
|
| - # Class methods have an extra implicit 'cls' argument.
|
| - num_positional_args += 1
|
| - break
|
| - elif (isinstance(called, astng.Function) or
|
| - isinstance(called, astng.Lambda)):
|
| - pass
|
| - else:
|
| + self._check_uninferable_callfunc(node)
|
| +
|
| + try:
|
| + called, implicit_args, callable_name = _determine_callable(called)
|
| + except ValueError:
|
| + # Any error occurred during determining the function type, most of
|
| + # those errors are handled by different warnings.
|
| return
|
| -
|
| + num_positional_args += implicit_args
|
| if called.args.args is None:
|
| # Built-in functions have no argument information.
|
| return
|
|
|
| - if len( called.argnames() ) != len( set( called.argnames() ) ):
|
| + if len(called.argnames()) != len(set(called.argnames())):
|
| # Duplicate parameter name (see E9801). We can't really make sense
|
| # of the function call in this case, so just return.
|
| return
|
| @@ -284,15 +420,15 @@
|
| parameters = []
|
| parameter_name_to_index = {}
|
| for i, arg in enumerate(called.args.args):
|
| - if isinstance(arg, astng.Tuple):
|
| + if isinstance(arg, astroid.Tuple):
|
| name = None
|
| # Don't store any parameter names within the tuple, since those
|
| # are not assignable from keyword arguments.
|
| else:
|
| - if isinstance(arg, astng.Keyword):
|
| + if isinstance(arg, astroid.Keyword):
|
| name = arg.arg
|
| else:
|
| - assert isinstance(arg, astng.AssName)
|
| + assert isinstance(arg, astroid.AssName)
|
| # This occurs with:
|
| # def f( (a), (b) ): pass
|
| name = arg.name
|
| @@ -303,6 +439,15 @@
|
| defval = None
|
| parameters.append([(name, defval), False])
|
|
|
| + kwparams = {}
|
| + for i, arg in enumerate(called.args.kwonlyargs):
|
| + if isinstance(arg, astroid.Keyword):
|
| + name = arg.arg
|
| + else:
|
| + assert isinstance(arg, astroid.AssName)
|
| + name = arg.name
|
| + kwparams[name] = [called.args.kw_defaults[i], False]
|
| +
|
| # Match the supplied arguments against the function parameters.
|
|
|
| # 1. Match the positional arguments.
|
| @@ -315,7 +460,8 @@
|
| break
|
| else:
|
| # Too many positional arguments.
|
| - self.add_message('E1121', node=node)
|
| + self.add_message('too-many-function-args',
|
| + node=node, args=(callable_name,))
|
| break
|
|
|
| # 2. Match the keyword arguments.
|
| @@ -324,15 +470,24 @@
|
| i = parameter_name_to_index[keyword]
|
| if parameters[i][1]:
|
| # Duplicate definition of function parameter.
|
| - self.add_message('E1124', node=node, args=keyword)
|
| + self.add_message('redundant-keyword-arg',
|
| + node=node, args=(keyword, callable_name))
|
| else:
|
| parameters[i][1] = True
|
| + elif keyword in kwparams:
|
| + if kwparams[keyword][1]: # XXX is that even possible?
|
| + # Duplicate definition of function parameter.
|
| + self.add_message('redundant-keyword-arg', node=node,
|
| + args=(keyword, callable_name))
|
| + else:
|
| + kwparams[keyword][1] = True
|
| elif called.args.kwarg is not None:
|
| # The keyword argument gets assigned to the **kwargs parameter.
|
| pass
|
| else:
|
| # Unexpected keyword argument.
|
| - self.add_message('E1123', node=node, args=keyword)
|
| + self.add_message('unexpected-keyword-arg', node=node,
|
| + args=(keyword, callable_name))
|
|
|
| # 3. Match the *args, if any. Note that Python actually processes
|
| # *args _before_ any keyword arguments, but we wait until after
|
| @@ -366,11 +521,134 @@
|
| for [(name, defval), assigned] in parameters:
|
| if (defval is None) and not assigned:
|
| if name is None:
|
| - display = '<tuple>'
|
| + display_name = '<tuple>'
|
| else:
|
| display_name = repr(name)
|
| - self.add_message('E1120', node=node, args=display_name)
|
| + self.add_message('no-value-for-parameter', node=node,
|
| + args=(display_name, callable_name))
|
|
|
| + for name in kwparams:
|
| + defval, assigned = kwparams[name]
|
| + if defval is None and not assigned:
|
| + self.add_message('missing-kwoa', node=node,
|
| + args=(name, callable_name))
|
| +
|
| + @check_messages('invalid-sequence-index')
|
| + def visit_extslice(self, node):
|
| + # Check extended slice objects as if they were used as a sequence
|
| + # index to check if the object being sliced can support them
|
| + return self.visit_index(node)
|
| +
|
| + @check_messages('invalid-sequence-index')
|
| + def visit_index(self, node):
|
| + if not node.parent or not hasattr(node.parent, "value"):
|
| + return
|
| +
|
| + # Look for index operations where the parent is a sequence type.
|
| + # If the types can be determined, only allow indices to be int,
|
| + # slice or instances with __index__.
|
| +
|
| + parent_type = safe_infer(node.parent.value)
|
| +
|
| + if not isinstance(parent_type, (astroid.Class, astroid.Instance)):
|
| + return
|
| +
|
| + # Determine what method on the parent this index will use
|
| + # The parent of this node will be a Subscript, and the parent of that
|
| + # node determines if the Subscript is a get, set, or delete operation.
|
| + operation = node.parent.parent
|
| + if isinstance(operation, astroid.Assign):
|
| + methodname = '__setitem__'
|
| + elif isinstance(operation, astroid.Delete):
|
| + methodname = '__delitem__'
|
| + else:
|
| + methodname = '__getitem__'
|
| +
|
| + # Check if this instance's __getitem__, __setitem__, or __delitem__, as
|
| + # appropriate to the statement, is implemented in a builtin sequence
|
| + # type. This way we catch subclasses of sequence types but skip classes
|
| + # that override __getitem__ and which may allow non-integer indices.
|
| + try:
|
| + methods = parent_type.getattr(methodname)
|
| + if methods is astroid.YES:
|
| + return
|
| + itemmethod = methods[0]
|
| + except (astroid.NotFoundError, IndexError):
|
| + return
|
| +
|
| + if not isinstance(itemmethod, astroid.Function):
|
| + return
|
| +
|
| + if itemmethod.root().name != BUILTINS:
|
| + return
|
| +
|
| + if not itemmethod.parent:
|
| + return
|
| +
|
| + if itemmethod.parent.name not in SEQUENCE_TYPES:
|
| + return
|
| +
|
| + # For ExtSlice objects coming from visit_extslice, no further
|
| + # inference is necessary, since if we got this far the ExtSlice
|
| + # is an error.
|
| + if isinstance(node, astroid.ExtSlice):
|
| + index_type = node
|
| + else:
|
| + index_type = safe_infer(node)
|
| +
|
| + if index_type is None or index_type is astroid.YES:
|
| + return
|
| +
|
| + # Constants must be of type int
|
| + if isinstance(index_type, astroid.Const):
|
| + if isinstance(index_type.value, int):
|
| + return
|
| + # Instance values must be int, slice, or have an __index__ method
|
| + elif isinstance(index_type, astroid.Instance):
|
| + if index_type.pytype() in (BUILTINS + '.int', BUILTINS + '.slice'):
|
| + return
|
| +
|
| + try:
|
| + index_type.getattr('__index__')
|
| + return
|
| + except astroid.NotFoundError:
|
| + pass
|
| +
|
| + # Anything else is an error
|
| + self.add_message('invalid-sequence-index', node=node)
|
| +
|
| + @check_messages('invalid-slice-index')
|
| + def visit_slice(self, node):
|
| + # Check the type of each part of the slice
|
| + for index in (node.lower, node.upper, node.step):
|
| + if index is None:
|
| + continue
|
| +
|
| + index_type = safe_infer(index)
|
| +
|
| + if index_type is None or index_type is astroid.YES:
|
| + continue
|
| +
|
| + # Constants must of type int or None
|
| + if isinstance(index_type, astroid.Const):
|
| + if isinstance(index_type.value, (int, type(None))):
|
| + continue
|
| + # Instance values must be of type int, None or an object
|
| + # with __index__
|
| + elif isinstance(index_type, astroid.Instance):
|
| + if index_type.pytype() in (BUILTINS + '.int',
|
| + BUILTINS + '.NoneType'):
|
| + continue
|
| +
|
| + try:
|
| + index_type.getattr('__index__')
|
| + return
|
| + except astroid.NotFoundError:
|
| + pass
|
| +
|
| + # Anything else is an error
|
| + self.add_message('invalid-slice-index', node=node)
|
| +
|
| def register(linter):
|
| """required method to auto register this checker """
|
| linter.register_checker(TypeChecker(linter))
|
|
|