diff options
Diffstat (limited to 'checkers')
-rw-r--r-- | checkers/typecheck.py | 62 |
1 files changed, 40 insertions, 22 deletions
diff --git a/checkers/typecheck.py b/checkers/typecheck.py index 7283cb3..dc64ead 100644 --- a/checkers/typecheck.py +++ b/checkers/typecheck.py @@ -19,6 +19,7 @@ import re import shlex import types +import sys import astroid from astroid import InferenceError, NotFoundError, YES, Instance @@ -75,15 +76,20 @@ MSGS = { ('Used when a function call does not pass a mandatory' ' keyword-only argument.'), {'minversion': (3, 0)}), - 'E1126': ('List index is non-integer type %s', - 'non-integer-list-index', + 'E1126': ('Sequence index is not an int, slice, or instance with __index__', + 'invalid-sequence-index', 'Used when a list is indexed with a non-integer type'), - 'E1127': ('Slice index is invalid type %s', + '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.'), } +if sys.version_info >= (3,0): + BUILTINS = 'builtins' +else: + BUILTINS = '__builtin__' + def _determine_callable(callable_obj): # Ordering is important, since BoundMethod is a subclass of UnboundMethod, # and Function inherits Lambda. @@ -453,37 +459,47 @@ accessed. Python regular expressions are accepted.'} if defval is None and not assigned: self.add_message('missing-kwoa', node=node, args=(name, callable_name)) - @check_messages('non-integer-list-index') + @check_messages('invalid-sequence-index') def visit_index(self, node): if not node.value or not node.parent or not node.parent.value: return - # Look for index operations where the parent is a list. - # If the types can be determined, only allow indices to be ints or - # int constants. + # 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__. + + sequence_types = set('%s.%s' % (BUILTINS, s) for s in + ('str', 'unicode', 'list', 'tuple', 'bytearray', + 'xrange', 'range', 'bytes', 'memoryview')) parent_type = safe_infer(node.parent.value) - if not isinstance(parent_type, astroid.List): + if not parent_type or parent_type.pytype() not in sequence_types: return index_type = safe_infer(node) - if index_type is astroid.YES: + 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 of type int + # Instance values must be int, slice, or have an __index__ method elif isinstance(index_type, astroid.Instance): - if index_type.name == 'int': + if index_type.pytype() in (BUILTINS + '.int', BUILTINS + '.slice'): + return + + try: + index_type.getattr('__index__') + except astroid.NotFoundError: + pass + else: return # Anything else is an error - self.add_message('non-integer-list-index', node=node, - args=(index_type,)) + self.add_message('invalid-sequence-index', node=node) @check_messages('invalid-slice-index') def visit_slice(self, node): @@ -497,27 +513,29 @@ accessed. Python regular expressions are accepted.'} index_type = safe_infer(index) - if index_type is astroid.YES: + 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, types.NoneType)): + 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.name in ('int', 'None'): + if index_type.pytype() in (BUILTINS + '.int', + BUILTINS + '.NoneType'): continue - if any(method for method in index_type.body \ - if isinstance(method, astroid.Function) and \ - method.name == '__index__'): - continue + try: + index_type.getattr('__index__') + except astroid.NotFoundError: + pass + else: + return # Anything else is an error - self.add_message('invalid-slice-index', node=node, - args=(index_type,)) + self.add_message('invalid-slice-index', node=node) def register(linter): """required method to auto register this checker """ |