Description
Current problem
We sometimes see code like this:
def foo(*, left=None, right=None):
""" ... """
if (left is None) != (right is None):
raise ValueError('Either both left= and right= need to be provided or none should.')
It is very easy to make the mistake of writing the check as:
def foo(*, left=None, right=None):
""" ... """
if left is None != right is None:
raise ValueError('Either both left= and right= need to be provided or none should.')
This actually is a chained comparison: (left is None != right is None)
is equivalent to:
(left is None) and (None != right) and (right is None)
...which is unsatisfiable, since right is None
implies not(None != right)
.
Desired solution
According to the comparison
rule in the Python grammar (https://docs.python.org/3/reference/grammar.html),
these comparison operators have the same precedence, and would lead to chained comparisons:
in, not in, is, is not, <, <=, >, >=, !=, ==
There are three groups: {'in', 'not in'}
, {'is', 'is not'}
, {'<', '<=', '>', '>=', '!=', '=='}
.
If the linter warned about chained comparisons where one expression is part of two comparisons that belong to different groups, this would flag up checks such as "left is None != right is None".
The price to pay is that this would also trigger on code such as...:
if None is not f(x) > 0:
...
...but overall, it might be justified in those rare cases where a conditional just like that is appropriate to expect the author to silence the linter on that line.