diff options
author | Laura Médioni <laura.medioni@logilab.fr> | 2015-12-11 16:56:29 +0100 |
---|---|---|
committer | Claudiu Popa <pcmanticore@gmail.com> | 2016-01-15 21:57:18 +0200 |
commit | 104a3269a04132880a9c62358a420d7fccf97fe2 (patch) | |
tree | 61da472e8294a2eb360e97acc2ff36d71827c124 | |
parent | d913fb6f33f373ecbacce67200917913396304fa (diff) | |
download | pylint-git-104a3269a04132880a9c62358a420d7fccf97fe2.tar.gz |
Don't apply unneeded-not rule on sets
Closes issue #717
-rw-r--r-- | ChangeLog | 2 | ||||
-rw-r--r-- | pylint/checkers/base.py | 57 | ||||
-rw-r--r-- | pylint/test/functional/unneeded_not.py | 11 |
3 files changed, 51 insertions, 19 deletions
@@ -8,6 +8,8 @@ ChangeLog for Pylint messages from the StringFormatChecker would have resulted in no messages at all. + * Don't apply unneeded-not over sets. + 2016-01-11 -- 1.5.3 diff --git a/pylint/checkers/base.py b/pylint/checkers/base.py index b5adddf83..edc9c1b75 100644 --- a/pylint/checkers/base.py +++ b/pylint/checkers/base.py @@ -270,6 +270,28 @@ def redefined_by_decorator(node): return True return False + +def _node_type(node): + """Return the inferred type for `node` + + If there is more than one possible type, or if inferred type is YES or None, + return None + """ + # check there is only one possible type for the assign node. Else we + # don't handle it for now + types = set() + try: + for var_type in node.infer(): + if var_type == astroid.YES or _is_none(var_type): + continue + types.add(var_type) + if len(types) > 1: + return + except InferenceError: + return + return types.pop() if types else None + + class _BasicChecker(BaseChecker): __implements__ = IAstroidChecker name = 'basic' @@ -1864,6 +1886,12 @@ class NotChecker(_BasicChecker): reverse_op = {'<': '>=', '<=': '>', '>': '<=', '>=': '<', '==': '!=', '!=': '==', 'in': 'not in', 'is': 'is not'} + # sets are not ordered, so for example "not set(LEFT_VALS) <= set(RIGHT_VALS)" is + # not equivalent to "set(LEFT_VALS) > set(RIGHT_VALS)" + skipped_nodes = (astroid.Set, ) + # 'builtins' py3, '__builtin__' py2 + skipped_classnames = ['%s.%s' % (six.moves.builtins.__name__, qname) + for qname in ('set', 'frozenset')] @check_messages('unneeded-not') def visit_unaryop(self, node): @@ -1883,12 +1911,18 @@ class NotChecker(_BasicChecker): operator, right = operand.ops[0] if operator not in self.reverse_op: return - # Ignore __ne__ as function of __eq__ frame = node.frame() if frame.name == '__ne__' and operator == '==': return - + for _type in (_node_type(left), _node_type(right)): + if not _type: + return + if isinstance(_type, self.skipped_nodes): + return + if (isinstance(_type, astroid.Instance) and + _type.qname() in self.skipped_classnames): + return suggestion = '%s %s %s' % (left.as_string(), self.reverse_op[operator], right.as_string()) @@ -1955,21 +1989,10 @@ class MultipleTypesChecker(BaseChecker): # ignore NoneType if _is_none(node): return - # check there is only one possible type for the assign node. Else we - # don't handle it for now - types = set() - try: - for var_type in node.value.infer(): - if var_type == astroid.YES or _is_none(var_type): - continue - var_type = var_type.pytype() - types.add(var_type) - if len(types) > 1: - return - except InferenceError: - return - if types: - self._assigns[-1].setdefault(target.as_string(), []).append((node, types.pop())) + _type = _node_type(node.value) + if _type: + self._assigns[-1].setdefault(target.as_string(), []).append( + (node, _type.pytype())) def register(linter): diff --git a/pylint/test/functional/unneeded_not.py b/pylint/test/functional/unneeded_not.py index e98c2e3aa..b95adc930 100644 --- a/pylint/test/functional/unneeded_not.py +++ b/pylint/test/functional/unneeded_not.py @@ -1,6 +1,6 @@ """Check exceeding negations in boolean expressions trigger warnings""" -# pylint: disable=singleton-comparison, too-many-branches, too-few-public-methods +# pylint: disable=singleton-comparison,too-many-branches,too-few-public-methods,undefined-variable def unneeded_not(): """This is not ok @@ -37,7 +37,7 @@ def unneeded_not(): pass -def not_checked(): +def tolerated_statements(): """This is ok""" bool_var = True someint = 2 @@ -49,6 +49,13 @@ def not_checked(): pass if not 2 <= someint < 3 < 4: pass + if not set('bar') <= set('foobaz'): + pass + if not set(something) <= 3: + pass + if not frozenset(something) <= 3: + pass + class Klass(object): """This is also ok""" |