diff options
author | David Shea <dshea@redhat.com> | 2014-07-07 14:20:47 -0400 |
---|---|---|
committer | David Shea <dshea@redhat.com> | 2014-07-07 14:20:47 -0400 |
commit | 2465aa80aa877497bc723caa253e0243188a035a (patch) | |
tree | 98433c98552d9b793eedeb5b7f01d56bec92819f | |
parent | eef3e13af5c0e6fcfea055e7162e4e11b0ba3365 (diff) | |
download | pylint-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.py | 33 | ||||
-rw-r--r-- | test/input/func_invalid_sequence_index.py | 65 | ||||
-rw-r--r-- | test/messages/func_invalid_sequence_index.txt | 10 |
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__ |