Skip to content

Commit 0af480e

Browse files
authored
Warn about is comparison to tuple (#484)
1 parent c9708a1 commit 0af480e

File tree

3 files changed

+74
-7
lines changed

3 files changed

+74
-7
lines changed

pyflakes/checker.py

Lines changed: 51 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,51 @@ def getAlternatives(n):
7878
LOOP_TYPES = (ast.While, ast.For)
7979
FUNCTION_TYPES = (ast.FunctionDef,)
8080

81+
82+
if PY38_PLUS:
83+
def _is_singleton(node): # type: (ast.AST) -> bool
84+
return (
85+
isinstance(node, ast.Constant) and
86+
isinstance(node.value, (bool, type(Ellipsis), type(None)))
87+
)
88+
elif not PY2:
89+
def _is_singleton(node): # type: (ast.AST) -> bool
90+
return isinstance(node, (ast.NameConstant, ast.Ellipsis))
91+
else:
92+
def _is_singleton(node): # type: (ast.AST) -> bool
93+
return (
94+
isinstance(node, ast.Name) and
95+
node.id in {'True', 'False', 'Ellipsis', 'None'}
96+
)
97+
98+
99+
def _is_tuple_constant(node): # type: (ast.AST) -> bool
100+
return (
101+
isinstance(node, ast.Tuple) and
102+
all(_is_constant(elt) for elt in node.elts)
103+
)
104+
105+
106+
if PY38_PLUS:
107+
def _is_constant(node):
108+
return isinstance(node, ast.Constant) or _is_tuple_constant(node)
109+
else:
110+
_const_tps = (ast.Str, ast.Num)
111+
if not PY2:
112+
_const_tps += (ast.Bytes,)
113+
114+
def _is_constant(node):
115+
return (
116+
isinstance(node, _const_tps) or
117+
_is_singleton(node) or
118+
_is_tuple_constant(node)
119+
)
120+
121+
122+
def _is_const_non_singleton(node): # type: (ast.AST) -> bool
123+
return _is_constant(node) and not _is_singleton(node)
124+
125+
81126
# https://github.com/python/typed_ast/blob/1.4.0/ast27/Parser/tokenizer.c#L102-L104
82127
TYPE_COMMENT_RE = re.compile(r'^#\s*type:\s*')
83128
# https://github.com/python/typed_ast/blob/1.4.0/ast27/Parser/tokenizer.c#L1408-L1413
@@ -2124,14 +2169,14 @@ def ANNASSIGN(self, node):
21242169
self.handleNode(node.value, node)
21252170

21262171
def COMPARE(self, node):
2127-
literals = (ast.Str, ast.Num)
2128-
if not PY2:
2129-
literals += (ast.Bytes,)
2130-
21312172
left = node.left
21322173
for op, right in zip(node.ops, node.comparators):
2133-
if (isinstance(op, (ast.Is, ast.IsNot)) and
2134-
(isinstance(left, literals) or isinstance(right, literals))):
2174+
if (
2175+
isinstance(op, (ast.Is, ast.IsNot)) and (
2176+
_is_const_non_singleton(left) or
2177+
_is_const_non_singleton(right)
2178+
)
2179+
):
21352180
self.report(messages.IsLiteral, node)
21362181
left = right
21372182

pyflakes/messages.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -265,7 +265,7 @@ class InvalidPrintSyntax(Message):
265265

266266

267267
class IsLiteral(Message):
268-
message = 'use ==/!= to compare str, bytes, and int literals'
268+
message = 'use ==/!= to compare constant literals (str, bytes, int, float, tuple)'
269269

270270

271271
class FStringMissingPlaceholders(Message):

pyflakes/test/test_is_literal.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,3 +198,25 @@ def test_chained_operators_is_str_end(self):
198198
if 4 < x is 'foo':
199199
pass
200200
""", IsLiteral)
201+
202+
def test_is_tuple_constant(self):
203+
self.flakes('''\
204+
x = 5
205+
if x is ():
206+
pass
207+
''', IsLiteral)
208+
209+
def test_is_tuple_constant_containing_constants(self):
210+
self.flakes('''\
211+
x = 5
212+
if x is (1, '2', True, (1.5, ())):
213+
pass
214+
''', IsLiteral)
215+
216+
def test_is_tuple_containing_variables_ok(self):
217+
# a bit nonsensical, but does not trigger a SyntaxWarning
218+
self.flakes('''\
219+
x = 5
220+
if x is (x,):
221+
pass
222+
''')

0 commit comments

Comments
 (0)