diff options
Diffstat (limited to 'pylint/checkers/typecheck.py')
-rw-r--r-- | pylint/checkers/typecheck.py | 48 |
1 files changed, 48 insertions, 0 deletions
diff --git a/pylint/checkers/typecheck.py b/pylint/checkers/typecheck.py index 687dd79..e9fddbd 100644 --- a/pylint/checkers/typecheck.py +++ b/pylint/checkers/typecheck.py @@ -47,6 +47,7 @@ STR_FORMAT = "%s.str.format" % BUILTINS ITER_METHOD = '__iter__' NEXT_METHOD = 'next' if six.PY2 else '__next__' GETITEM_METHOD = '__getitem__' +CONTAINS_METHOD = '__contains__' KEYS_METHOD = 'keys' @@ -115,6 +116,9 @@ def _is_iterator(value): def _is_mapping(value): return _hasattr(value, GETITEM_METHOD) and _hasattr(value, KEYS_METHOD) +def _supports_membership_test(value): + return _hasattr(value, CONTAINS_METHOD) + def _is_inside_mixin_declaration(node): while node is not None: @@ -189,6 +193,10 @@ MSGS = { 'E1132': ('Got multiple values for keyword argument %r in function call', 'repeated-keyword', 'Emitted when a function call got multiple values for a keyword.'), + 'E1135': ("Value '%s' doesn't support membership test", + 'unsupported-membership-test', + 'Emitted when an instance in membership test expression doesn\'t' + 'implement membership protocol (__contains__/__iter__/__getitem__)'), } # builtin sequence types in Python 2 and 3. @@ -832,6 +840,46 @@ accessed. Python regular expressions are accepted.'} self.add_message('unsupported-binary-operation', args=str(error), node=node) + def _check_membership_test(self, node): + # instance supports membership test in either of those cases: + # 1. instance defines __contains__ method + # 2. instance is iterable (defines __iter__ or __getitem__) + if _is_comprehension(node) or _is_inside_mixin_declaration(node): + return + + infered = helpers.safe_infer(node) + if infered is None or infered is astroid.YES: + return + + # classes can be iterables/containers too + if isinstance(infered, astroid.ClassDef): + if not helpers.has_known_bases(infered): + return + meta = infered.metaclass() + if meta is not None: + if _supports_membership_test(meta): + return + if _is_iterable(meta): + return + + if isinstance(infered, astroid.Instance): + if not helpers.has_known_bases(infered): + return + if _supports_membership_test(infered) or _is_iterable(infered): + return + + self.add_message('unsupported-membership-test', + args=node.as_string(), + node=node) + + @check_messages('unsupported-membership-test') + def visit_compare(self, node): + if len(node.ops) != 1: + return + operator, right = node.ops[0] + if operator in ['in', 'not in']: + self._check_membership_test(right) + class IterableChecker(BaseChecker): """ |