summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAnthony Sottile <asottile@umich.edu>2020-02-17 11:43:54 -0800
committerGitHub <noreply@github.com>2020-02-17 11:43:54 -0800
commit0af480e3351ae40b4ae7f3ce7272a46fd4265dbd (patch)
tree5b681e114fcfadd4771425295b04fce08d269293
parentc9708a18a17fbf17e0d88c1d1675ac1a926c4565 (diff)
downloadpyflakes-0af480e3351ae40b4ae7f3ce7272a46fd4265dbd.tar.gz
Warn about is comparison to tuple (#484)
-rw-r--r--pyflakes/checker.py57
-rw-r--r--pyflakes/messages.py2
-rw-r--r--pyflakes/test/test_is_literal.py22
3 files changed, 74 insertions, 7 deletions
diff --git a/pyflakes/checker.py b/pyflakes/checker.py
index c89d6fb..351ab12 100644
--- a/pyflakes/checker.py
+++ b/pyflakes/checker.py
@@ -78,6 +78,51 @@ else:
LOOP_TYPES = (ast.While, ast.For)
FUNCTION_TYPES = (ast.FunctionDef,)
+
+if PY38_PLUS:
+ def _is_singleton(node): # type: (ast.AST) -> bool
+ return (
+ isinstance(node, ast.Constant) and
+ isinstance(node.value, (bool, type(Ellipsis), type(None)))
+ )
+elif not PY2:
+ def _is_singleton(node): # type: (ast.AST) -> bool
+ return isinstance(node, (ast.NameConstant, ast.Ellipsis))
+else:
+ def _is_singleton(node): # type: (ast.AST) -> bool
+ return (
+ isinstance(node, ast.Name) and
+ node.id in {'True', 'False', 'Ellipsis', 'None'}
+ )
+
+
+def _is_tuple_constant(node): # type: (ast.AST) -> bool
+ return (
+ isinstance(node, ast.Tuple) and
+ all(_is_constant(elt) for elt in node.elts)
+ )
+
+
+if PY38_PLUS:
+ def _is_constant(node):
+ return isinstance(node, ast.Constant) or _is_tuple_constant(node)
+else:
+ _const_tps = (ast.Str, ast.Num)
+ if not PY2:
+ _const_tps += (ast.Bytes,)
+
+ def _is_constant(node):
+ return (
+ isinstance(node, _const_tps) or
+ _is_singleton(node) or
+ _is_tuple_constant(node)
+ )
+
+
+def _is_const_non_singleton(node): # type: (ast.AST) -> bool
+ return _is_constant(node) and not _is_singleton(node)
+
+
# https://github.com/python/typed_ast/blob/1.4.0/ast27/Parser/tokenizer.c#L102-L104
TYPE_COMMENT_RE = re.compile(r'^#\s*type:\s*')
# https://github.com/python/typed_ast/blob/1.4.0/ast27/Parser/tokenizer.c#L1408-L1413
@@ -2124,14 +2169,14 @@ class Checker(object):
self.handleNode(node.value, node)
def COMPARE(self, node):
- literals = (ast.Str, ast.Num)
- if not PY2:
- literals += (ast.Bytes,)
-
left = node.left
for op, right in zip(node.ops, node.comparators):
- if (isinstance(op, (ast.Is, ast.IsNot)) and
- (isinstance(left, literals) or isinstance(right, literals))):
+ if (
+ isinstance(op, (ast.Is, ast.IsNot)) and (
+ _is_const_non_singleton(left) or
+ _is_const_non_singleton(right)
+ )
+ ):
self.report(messages.IsLiteral, node)
left = right
diff --git a/pyflakes/messages.py b/pyflakes/messages.py
index cf67cf8..e93a493 100644
--- a/pyflakes/messages.py
+++ b/pyflakes/messages.py
@@ -265,7 +265,7 @@ class InvalidPrintSyntax(Message):
class IsLiteral(Message):
- message = 'use ==/!= to compare str, bytes, and int literals'
+ message = 'use ==/!= to compare constant literals (str, bytes, int, float, tuple)'
class FStringMissingPlaceholders(Message):
diff --git a/pyflakes/test/test_is_literal.py b/pyflakes/test/test_is_literal.py
index 9280cda..fbbb205 100644
--- a/pyflakes/test/test_is_literal.py
+++ b/pyflakes/test/test_is_literal.py
@@ -198,3 +198,25 @@ class Test(TestCase):
if 4 < x is 'foo':
pass
""", IsLiteral)
+
+ def test_is_tuple_constant(self):
+ self.flakes('''\
+ x = 5
+ if x is ():
+ pass
+ ''', IsLiteral)
+
+ def test_is_tuple_constant_containing_constants(self):
+ self.flakes('''\
+ x = 5
+ if x is (1, '2', True, (1.5, ())):
+ pass
+ ''', IsLiteral)
+
+ def test_is_tuple_containing_variables_ok(self):
+ # a bit nonsensical, but does not trigger a SyntaxWarning
+ self.flakes('''\
+ x = 5
+ if x is (x,):
+ pass
+ ''')