diff options
author | David Shea <dshea@redhat.com> | 2014-07-06 13:22:14 -0400 |
---|---|---|
committer | David Shea <dshea@redhat.com> | 2014-07-06 13:22:14 -0400 |
commit | 7f83dca69a0c5fd3b84e90d7a2a2c34a8cf086dc (patch) | |
tree | 217a91a279a1443eac9bea155dec04699426daac /checkers | |
parent | 424f60d61ee19fb29e1a9244a683f6869b528e05 (diff) | |
download | pylint-7f83dca69a0c5fd3b84e90d7a2a2c34a8cf086dc.tar.gz |
Expand and correct the types used in the index and slice checks.
Check for invalid index types used with all builtin sequence types.
Allow slice objects and instances with __index__ to be used as indices.
Check for __index__ implemented in bases of an instance.
Remove the astroid types from the error messages and correct the wording.
Use .pytype() instead of .name to check types to avoid problems with custom
types defining names that conflict with the bulitin types. Use type(None)
instead of types.NoneType to avoid python 3 incompatibilities.
Handle None returned by safe_infer.
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 """ |