summaryrefslogtreecommitdiff
path: root/pylint/checkers
diff options
context:
space:
mode:
Diffstat (limited to 'pylint/checkers')
-rw-r--r--pylint/checkers/typecheck.py41
-rw-r--r--pylint/checkers/utils.py15
2 files changed, 47 insertions, 9 deletions
diff --git a/pylint/checkers/typecheck.py b/pylint/checkers/typecheck.py
index 43b0bde..75cb193 100644
--- a/pylint/checkers/typecheck.py
+++ b/pylint/checkers/typecheck.py
@@ -38,7 +38,8 @@ from pylint.checkers.utils import (
decorated_with, node_ignores_exception,
is_iterable, is_mapping, supports_membership_test,
is_comprehension, is_inside_abstract_class,
- supports_subscript,
+ supports_getitem,
+ supports_setitem,
safe_infer,
has_known_bases)
from pylint import utils
@@ -161,6 +162,10 @@ MSGS = {
'unsubscriptable-object',
"Emitted when a subscripted value doesn't support subscription"
"(i.e. doesn't define __getitem__ method)"),
+ 'E1137': ("%r does not support item assignment",
+ 'unsupported-assignment-operation',
+ "Emitted when an object does not support item assignment "
+ "(i.e. doesn't define __setitem__ method)"),
}
# builtin sequence types in Python 2 and 3.
@@ -825,18 +830,42 @@ accessed. Python regular expressions are accepted.'}
if operator in ['in', 'not in']:
self._check_membership_test(right)
+ @staticmethod
+ def _is_subscript_store_context(node):
+ statement = node.statement()
+ if isinstance(statement, astroid.Assign):
+ for target in statement.targets:
+ if target is node or target.parent_of(node):
+ return False
+ return True
+
@check_messages('unsubscriptable-object')
def visit_subscript(self, node):
if isinstance(node.value, (astroid.ListComp, astroid.DictComp)):
return
+
+ store_context = self._is_subscript_store_context(node)
if isinstance(node.value, astroid.SetComp):
- self.add_message('unsubscriptable-object',
- args=node.value.as_string(),
+ if store_context:
+ msg = 'unsubscriptable-object'
+ else:
+ msg = 'unsupported-assignment-operation'
+ self.add_message(msg, args=node.value.as_string(),
node=node.value)
- infered = safe_infer(node.value)
- if infered is None or infered is astroid.YES:
return
- if not supports_subscript(infered):
+
+ inferred = safe_infer(node.value)
+ if inferred is None or inferred is astroid.YES:
+ return
+
+ if not store_context:
+ if not supports_setitem(inferred):
+ self.add_message('unsupported-assignment-operation',
+ args=node.value.as_string(),
+ node=node.value)
+ return
+
+ if not supports_getitem(inferred):
self.add_message('unsubscriptable-object',
args=node.value.as_string(),
node=node.value)
diff --git a/pylint/checkers/utils.py b/pylint/checkers/utils.py
index e24a034..ba396b0 100644
--- a/pylint/checkers/utils.py
+++ b/pylint/checkers/utils.py
@@ -43,6 +43,7 @@ ABC_METHODS = set(('abc.abstractproperty', 'abc.abstractmethod',
ITER_METHOD = '__iter__'
NEXT_METHOD = 'next' if six.PY2 else '__next__'
GETITEM_METHOD = '__getitem__'
+SETITEM_METHOD = '__setitem__'
CONTAINS_METHOD = '__contains__'
KEYS_METHOD = 'keys'
@@ -618,10 +619,14 @@ def _supports_iteration_protocol(value):
return _hasattr(value, ITER_METHOD) or _hasattr(value, GETITEM_METHOD)
-def _supports_subscript_protocol(value):
+def _supports_getitem_protocol(value):
return _hasattr(value, GETITEM_METHOD)
+def _supports_setitem_protocol(value):
+ return _hasattr(value, SETITEM_METHOD)
+
+
def _is_abstract_class_name(name):
lname = name.lower()
is_mixin = lname.endswith('mixin')
@@ -672,8 +677,12 @@ def supports_membership_test(value):
return supported or is_iterable(value)
-def supports_subscript(value):
- return _supports_protocol(value, _supports_subscript_protocol)
+def supports_getitem(value):
+ return _supports_protocol(value, _supports_getitem_protocol)
+
+
+def supports_setitem(value):
+ return _supports_protocol(value, _supports_setitem_protocol)
# TODO(cpopa): deprecate these or leave them as aliases?