Skip to content

Commit ff4e14e

Browse files
committed
Fix #299: Warn about type(x) is/== Y
--HG-- branch : impl-299
1 parent eff0ea0 commit ff4e14e

File tree

4 files changed

+143
-1
lines changed

4 files changed

+143
-1
lines changed

ChangeLog

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@ ChangeLog for Pylint
3636
appropriate built-in is not used in an iterating context (semantics
3737
taken from 2to3).
3838

39+
* Add a new warning, 'unidiomatic-typecheck', emitted when an explicit
40+
typecheck uses type() instead of isinstance(). For example,
41+
`type(x) == Y` instead of `isinstance(x, Y)`. Patch by Chris Rebert.
42+
Closes issue #299.
43+
3944

4045
2015-01-16 -- 1.4.1
4146

pylint/checkers/stdlib.py

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,15 @@
2727
from pylint.checkers import utils
2828

2929

30+
TYPECHECK_COMPARISON_OPERATORS = frozenset(('is', 'is not', '==', '!=', 'in', 'not in'))
31+
LITERAL_NODE_TYPES = (astroid.Const, astroid.Dict, astroid.List, astroid.Set)
32+
3033
if sys.version_info >= (3, 0):
3134
OPEN_MODULE = '_io'
35+
TYPE_QNAME = 'builtins.type'
3236
else:
3337
OPEN_MODULE = '__builtin__'
38+
TYPE_QNAME = '__builtin__.type'
3439

3540

3641
def _check_mode_str(mode):
@@ -76,6 +81,15 @@ def _check_mode_str(mode):
7681
return True
7782

7883

84+
def _is_one_arg_pos_call(call):
85+
"""Is this a call with exactly 1 argument,
86+
where that argument is positional?
87+
"""
88+
return (isinstance(call, astroid.CallFunc)
89+
and len(call.args) == 1
90+
and not isinstance(call.args[0], astroid.Keyword))
91+
92+
7993
class StdlibChecker(BaseChecker):
8094
__implements__ = (IAstroidChecker,)
8195
name = 'stdlib'
@@ -99,7 +113,13 @@ class StdlibChecker(BaseChecker):
99113
'The first argument of assertTrue and assertFalse is '
100114
'a condition. If a constant is passed as parameter, that '
101115
'condition will be always true. In this case a warning '
102-
'should be emitted.')
116+
'should be emitted.'),
117+
'W1504': ('Using type() instead of isinstance() for a typecheck.',
118+
'unidiomatic-typecheck',
119+
'The idiomatic way to perform an explicit typecheck in '
120+
'Python is to use isinstance(x, Y) rather than '
121+
'type(x) == Y, type(x) is Y. Though there are unusual '
122+
'situations where these give different results.')
103123
}
104124

105125
@utils.check_messages('bad-open-mode', 'redundant-unittest-assert')
@@ -132,6 +152,14 @@ def visit_boolop(self, node):
132152
for value in node.values:
133153
self._check_datetime(value)
134154

155+
@utils.check_messages('unidiomatic-typecheck')
156+
def visit_compare(self, node):
157+
operator, right = node.ops[0]
158+
if operator in TYPECHECK_COMPARISON_OPERATORS:
159+
left = node.left
160+
if _is_one_arg_pos_call(left):
161+
self._check_type_x_is_y(node, left, operator, right)
162+
135163
def _check_redundant_assert(self, node, infer):
136164
if (isinstance(infer, astroid.BoundMethod) and
137165
node.args and isinstance(node.args[0], astroid.Const) and
@@ -168,6 +196,21 @@ def _check_open_mode(self, node):
168196
args=mode_arg.value)
169197

170198

199+
def _check_type_x_is_y(self, node, left, operator, right):
200+
"""Check for expressions like type(x) == Y."""
201+
left_func = utils.safe_infer(left.func)
202+
if isinstance(left_func, astroid.Class) and left_func.qname() == TYPE_QNAME:
203+
if operator in ('is', 'is not') and _is_one_arg_pos_call(right):
204+
right_func = utils.safe_infer(right.func)
205+
if isinstance(right_func, astroid.Class) and right_func.qname() == TYPE_QNAME:
206+
# type(x) == type(a)
207+
right_arg = utils.safe_infer(right.args[0])
208+
if not isinstance(right_arg, LITERAL_NODE_TYPES):
209+
# not e.g. type(x) == type([])
210+
return
211+
self.add_message('unidiomatic-typecheck', node=node)
212+
213+
171214
def register(linter):
172215
"""required method to auto register this checker """
173216
linter.register_checker(StdlibChecker(linter))
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
"""Warnings for using type(x) == Y or type(x) is Y instead of isinstance(x, Y)."""
2+
# pylint: disable=missing-docstring,expression-not-assigned,redefined-builtin,invalid-name
3+
4+
def simple_positives():
5+
type(42) is int # [unidiomatic-typecheck]
6+
type(42) is not int # [unidiomatic-typecheck]
7+
type(42) == int # [unidiomatic-typecheck]
8+
type(42) != int # [unidiomatic-typecheck]
9+
type(42) in [int] # [unidiomatic-typecheck]
10+
type(42) not in [int] # [unidiomatic-typecheck]
11+
12+
def simple_inference_positives():
13+
alias = type
14+
alias(42) is int # [unidiomatic-typecheck]
15+
alias(42) is not int # [unidiomatic-typecheck]
16+
alias(42) == int # [unidiomatic-typecheck]
17+
alias(42) != int # [unidiomatic-typecheck]
18+
alias(42) in [int] # [unidiomatic-typecheck]
19+
alias(42) not in [int] # [unidiomatic-typecheck]
20+
21+
def type_creation_negatives():
22+
type('Q', (object,), dict(a=1)) is int
23+
type('Q', (object,), dict(a=1)) is not int
24+
type('Q', (object,), dict(a=1)) == int
25+
type('Q', (object,), dict(a=1)) != int
26+
type('Q', (object,), dict(a=1)) in [int]
27+
type('Q', (object,), dict(a=1)) not in [int]
28+
29+
def invalid_type_call_negatives(**kwargs):
30+
type(bad=7) is int
31+
type(bad=7) is not int
32+
type(bad=7) == int
33+
type(bad=7) != int
34+
type(bad=7) in [int]
35+
type(bad=7) not in [int]
36+
type('bad', 7) is int
37+
type('bad', 7) is not int
38+
type('bad', 7) == int
39+
type('bad', 7) != int
40+
type('bad', 7) in [int]
41+
type('bad', 7) not in [int]
42+
type(**kwargs) is int
43+
type(**kwargs) is not int
44+
type(**kwargs) == int
45+
type(**kwargs) != int
46+
type(**kwargs) in [int]
47+
type(**kwargs) not in [int]
48+
49+
def local_var_shadowing_inference_negatives():
50+
type = lambda dummy: 7
51+
type(42) is int
52+
type(42) is not int
53+
type(42) == int
54+
type(42) != int
55+
type(42) in [int]
56+
type(42) not in [int]
57+
58+
def parameter_shadowing_inference_negatives(type):
59+
type(42) is int
60+
type(42) is not int
61+
type(42) == int
62+
type(42) != int
63+
type(42) in [int]
64+
type(42) not in [int]
65+
66+
def deliberate_subclass_check_negatives(b):
67+
type(42) is type(b)
68+
type(42) is not type(b)
69+
70+
def type_of_literals_positives(a):
71+
type(a) is type([]) # [unidiomatic-typecheck]
72+
type(a) is not type([]) # [unidiomatic-typecheck]
73+
type(a) is type({}) # [unidiomatic-typecheck]
74+
type(a) is not type({}) # [unidiomatic-typecheck]
75+
type(a) is type("") # [unidiomatic-typecheck]
76+
type(a) is not type("") # [unidiomatic-typecheck]
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
unidiomatic-typecheck:5:simple_positives:Using type() instead of isinstance() for a typecheck.
2+
unidiomatic-typecheck:6:simple_positives:Using type() instead of isinstance() for a typecheck.
3+
unidiomatic-typecheck:7:simple_positives:Using type() instead of isinstance() for a typecheck.
4+
unidiomatic-typecheck:8:simple_positives:Using type() instead of isinstance() for a typecheck.
5+
unidiomatic-typecheck:9:simple_positives:Using type() instead of isinstance() for a typecheck.
6+
unidiomatic-typecheck:10:simple_positives:Using type() instead of isinstance() for a typecheck.
7+
unidiomatic-typecheck:14:simple_inference_positives:Using type() instead of isinstance() for a typecheck.
8+
unidiomatic-typecheck:15:simple_inference_positives:Using type() instead of isinstance() for a typecheck.
9+
unidiomatic-typecheck:16:simple_inference_positives:Using type() instead of isinstance() for a typecheck.
10+
unidiomatic-typecheck:17:simple_inference_positives:Using type() instead of isinstance() for a typecheck.
11+
unidiomatic-typecheck:18:simple_inference_positives:Using type() instead of isinstance() for a typecheck.
12+
unidiomatic-typecheck:19:simple_inference_positives:Using type() instead of isinstance() for a typecheck.
13+
unidiomatic-typecheck:71:type_of_literals_positives:Using type() instead of isinstance() for a typecheck.
14+
unidiomatic-typecheck:72:type_of_literals_positives:Using type() instead of isinstance() for a typecheck.
15+
unidiomatic-typecheck:73:type_of_literals_positives:Using type() instead of isinstance() for a typecheck.
16+
unidiomatic-typecheck:74:type_of_literals_positives:Using type() instead of isinstance() for a typecheck.
17+
unidiomatic-typecheck:75:type_of_literals_positives:Using type() instead of isinstance() for a typecheck.
18+
unidiomatic-typecheck:76:type_of_literals_positives:Using type() instead of isinstance() for a typecheck.

0 commit comments

Comments
 (0)