summaryrefslogtreecommitdiff
path: root/checkers
diff options
context:
space:
mode:
authorDavid Shea <dshea@redhat.com>2014-07-06 13:22:14 -0400
committerDavid Shea <dshea@redhat.com>2014-07-06 13:22:14 -0400
commit7f83dca69a0c5fd3b84e90d7a2a2c34a8cf086dc (patch)
tree217a91a279a1443eac9bea155dec04699426daac /checkers
parent424f60d61ee19fb29e1a9244a683f6869b528e05 (diff)
downloadpylint-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.py62
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 """