summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Shea <dshea@redhat.com>2014-07-07 14:20:47 -0400
committerDavid Shea <dshea@redhat.com>2014-07-07 14:20:47 -0400
commit2465aa80aa877497bc723caa253e0243188a035a (patch)
tree98433c98552d9b793eedeb5b7f01d56bec92819f
parenteef3e13af5c0e6fcfea055e7162e4e11b0ba3365 (diff)
downloadpylint-2465aa80aa877497bc723caa253e0243188a035a.tar.gz
Correct the sequence index check for set and delete operations.
Usage of an index does not always imply __getitem__.
-rw-r--r--checkers/typecheck.py33
-rw-r--r--test/input/func_invalid_sequence_index.py65
-rw-r--r--test/messages/func_invalid_sequence_index.txt10
3 files changed, 97 insertions, 11 deletions
diff --git a/checkers/typecheck.py b/checkers/typecheck.py
index d3a1aae..169abda 100644
--- a/checkers/typecheck.py
+++ b/checkers/typecheck.py
@@ -540,28 +540,39 @@ accessed. Python regular expressions are accepted.'}
if not parent_type:
return
- # Check if this instance has a __getitem__ method implemented
- # in a bulitin sequence type. This way we catch subclasses of
- # sequence types but skip classes that override __getitem__ and
- # which may allow non-integer indices.
+ # 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 bulitin sequence
+ # type. This way we catch subclasses of sequence types but skip classes
+ # that override __getitem__ and which may allow non-integer indices.
try:
- getitems = parent_type.getattr('__getitem__')
- if getitems is astroid.YES:
+ methods = parent_type.getattr(methodname)
+ if methods is astroid.YES:
return
- getitem = getitems[0]
+ itemmethod = methods[0]
except (astroid.NotFoundError, IndexError):
return
- if not isinstance(getitem, astroid.Function):
+ if not isinstance(itemmethod, astroid.Function):
return
- if not getitem.parent:
+ if itemmethod.root().name != BUILTINS:
return
- if getitem.root().name != BUILTINS:
+ if not itemmethod.parent:
return
- if getitem.parent.name not in sequence_types:
+ if itemmethod.parent.name not in sequence_types:
return
index_type = safe_infer(node)
diff --git a/test/input/func_invalid_sequence_index.py b/test/input/func_invalid_sequence_index.py
index c1d6e72..d0c8304 100644
--- a/test/input/func_invalid_sequence_index.py
+++ b/test/input/func_invalid_sequence_index.py
@@ -120,3 +120,68 @@ def function18():
pass
return SubTupleTest()[None] # no error
+
+# Test with set and delete statements
+
+def function19():
+ """Set with None and integer indices"""
+ TESTLIST[None] = 0
+ TESTLIST[0] = 0 # no error
+
+def function20():
+ """Delete with None and integer indicies"""
+ del TESTLIST[None]
+ del TESTLIST[0] # no error
+
+def function21():
+ """Set and delete on a subclass of list"""
+ class ListTest(list):
+ """Inherit all list get/set/del handlers"""
+ pass
+ test = ListTest()
+ test[None] = 0
+ test[0] = 0 # no error
+ del test[None]
+ del test[0] # no error
+
+def function22():
+ """Get, set, and delete on a subclass of list that overrides __setitem__"""
+ class ListTest(list):
+ """Override setitem but not get or del"""
+ def __setitem__(self, key, value):
+ pass
+ test = ListTest()
+ test[None][0] = 0
+ test[0][0] = 0 # no error
+ test[None] = 0 # no error
+ test[0] = 0 # no error
+ del test[None]
+ del test[0] # no error
+
+def function23():
+ """Get, set, and delete on a subclass of list that overrides __delitem__"""
+ class ListTest(list):
+ """Override delitem but not get or set"""
+ def __delitem__(self, key):
+ pass
+ test = ListTest()
+ test[None][0] = 0
+ test[0][0] = 0 # no error
+ test[None] = 0
+ test[0] = 0 # no error
+ del test[None] # no error
+ del test[0] # no error
+
+def function24():
+ """Get, set, and delete on a subclass of list that overrides __getitem__"""
+ class ListTest(list):
+ """Override gelitem but not del or set"""
+ def __getitem__(self, key):
+ pass
+ test = ListTest()
+ test[None][0] = 0 # no error
+ test[0][0] = 0 # no error
+ test[None] = 0
+ test[0] = 0 # no error
+ del test[None]
+ del test[0] # no error
diff --git a/test/messages/func_invalid_sequence_index.txt b/test/messages/func_invalid_sequence_index.txt
index 79a5956..cc58938 100644
--- a/test/messages/func_invalid_sequence_index.txt
+++ b/test/messages/func_invalid_sequence_index.txt
@@ -6,3 +6,13 @@ E: 68:function10: Sequence index is not an int, slice, or instance with __index_
E: 73:function11: Sequence index is not an int, slice, or instance with __index__
E: 81:function13: Sequence index is not an int, slice, or instance with __index__
E: 92:function15: Sequence index is not an int, slice, or instance with __index__
+E:128:function19: Sequence index is not an int, slice, or instance with __index__
+E:133:function20: Sequence index is not an int, slice, or instance with __index__
+E:142:function21: Sequence index is not an int, slice, or instance with __index__
+E:144:function21: Sequence index is not an int, slice, or instance with __index__
+E:154:function22: Sequence index is not an int, slice, or instance with __index__
+E:158:function22: Sequence index is not an int, slice, or instance with __index__
+E:168:function23: Sequence index is not an int, slice, or instance with __index__
+E:170:function23: Sequence index is not an int, slice, or instance with __index__
+E:184:function24: Sequence index is not an int, slice, or instance with __index__
+E:186:function24: Sequence index is not an int, slice, or instance with __index__