summaryrefslogtreecommitdiff
path: root/pylint/checkers/typecheck.py
diff options
context:
space:
mode:
Diffstat (limited to 'pylint/checkers/typecheck.py')
-rw-r--r--pylint/checkers/typecheck.py132
1 files changed, 66 insertions, 66 deletions
diff --git a/pylint/checkers/typecheck.py b/pylint/checkers/typecheck.py
index 83d9742..5095d72 100644
--- a/pylint/checkers/typecheck.py
+++ b/pylint/checkers/typecheck.py
@@ -43,6 +43,10 @@ _ZOPE_DEPRECATED = (
)
BUILTINS = six.moves.builtins.__name__
STR_FORMAT = "%s.str.format" % BUILTINS
+ITER_METHOD = '__iter__'
+NEXT_METHOD = 'next' if six.PY2 else '__next__'
+GETITEM_METHOD = '__getitem__'
+KEYS_METHOD = 'keys'
def _unflatten(iterable):
@@ -83,6 +87,13 @@ def _is_owner_ignored(owner, name, ignored_classes, ignored_modules):
return any(name == ignore or qname == ignore for ignore in ignored_classes)
+def _hasattr(value, attr):
+ try:
+ value.getattr(attr)
+ return True
+ except astroid.NotFoundError:
+ return False
+
def _is_comprehension(node):
comprehensions = (astroid.ListComp,
astroid.SetComp,
@@ -90,62 +101,29 @@ def _is_comprehension(node):
return isinstance(node, comprehensions)
-def _is_class_def(node):
- return isinstance(node, astroid.ClassDef)
-
-
def _is_iterable(value):
- if _is_class_def(value):
- return False
- try:
- value.getattr('__iter__')
- return True
- except astroid.NotFoundError:
- pass
- try:
- # this checks works for string types
- value.getattr('__getitem__')
- return True
- except astroid.NotFoundError:
- pass
- return False
+ # '__iter__' is for standard iterables
+ # '__getitem__' is for strings and other old-style iterables
+ return _hasattr(value, ITER_METHOD) or _hasattr(value, GETITEM_METHOD)
def _is_iterator(value):
- if _is_class_def(value):
- return False
- try:
- value.getattr('__iter__')
- if six.PY2:
- value.getattr('next')
- elif six.PY3:
- value.getattr('__next__')
- return True
- except astroid.NotFoundError:
- return False
+ return _hasattr(value, NEXT_METHOD) and _hasattr(value, ITER_METHOD)
-def _is_old_style_iterator(value):
- if _is_class_def(value):
- return False
- try:
- value.getattr('__getitem__')
- value.getattr('__len__')
- return True
- except astroid.NotFoundError:
- return False
+def _is_mapping(value):
+ return _hasattr(value, GETITEM_METHOD) and _hasattr(value, KEYS_METHOD)
-def _is_mapping(value):
- if _is_class_def(value):
- return False
- try:
- value.getattr('__getitem__')
- value.getattr('__iter__')
- value.getattr('keys')
- return True
- except astroid.NotFoundError:
- return False
+def _is_inside_mixin_declaration(node):
+ while node is not None:
+ if isinstance(node, astroid.ClassDef):
+ name = getattr(node, 'name', None)
+ if name is not None and name.lower().endswith("mixin"):
+ return True
+ node = node.parent
+ return False
+
MSGS = {
'E1101': ('%s %r has no %r member',
@@ -884,35 +862,57 @@ class IterableChecker(BaseChecker):
}
def _check_iterable(self, node, root_node):
- # for/set/dict-comprehensions can't be infered with astroid,
- # so we check for them before checking infered value
- if _is_comprehension(node):
+ # for/set/dict-comprehensions can't be infered with astroid
+ # so we have to check for them explicitly
+ if _is_comprehension(node) or _is_inside_mixin_declaration(node):
return
+
infered = helpers.safe_infer(node)
- if infered is None:
- return
- if infered is astroid.YES:
- return
- if _is_iterable(infered):
- return
- if _is_iterator(infered):
- return
- if _is_old_style_iterator(infered):
+ if infered is None or infered is astroid.YES:
return
+
+ if isinstance(infered, astroid.ClassDef):
+ if not helpers.has_known_bases(infered):
+ return
+ # classobj can only be iterable if it has an iterable metaclass
+ meta = infered.metaclass()
+ if meta is not None:
+ if _is_iterable(meta):
+ return
+ if _is_iterator(meta):
+ return
+
+ if isinstance(infered, astroid.Instance):
+ if not helpers.has_known_bases(infered):
+ return
+ if _is_iterable(infered) or _is_iterator(infered):
+ return
+
self.add_message('not-an-iterable',
args=node.as_string(),
node=root_node)
def _check_mapping(self, node, root_node):
- if isinstance(node, astroid.DictComp):
+ if isinstance(node, astroid.DictComp) or _is_inside_mixin_declaration(node):
return
+
infered = helpers.safe_infer(node)
- if infered is None:
- return
- if infered is astroid.YES:
- return
- if _is_mapping(infered):
+ if infered is None or infered is astroid.YES:
return
+
+ if isinstance(infered, astroid.ClassDef):
+ if not helpers.has_known_bases(infered):
+ return
+ meta = infered.metaclass()
+ if meta is not None and _is_mapping(meta):
+ return
+
+ if isinstance(infered, astroid.Instance):
+ if not helpers.has_known_bases(infered):
+ return
+ if _is_mapping(infered):
+ return
+
self.add_message('not-a-mapping',
args=node.as_string(),
node=root_node)