Skip to content
Snippets Groups Projects
Commit e1511f4c authored by Bryce Guinta's avatar Bryce Guinta Committed by Claudiu Popa
Browse files

Add inference for the builtin 'isinstance'

Close #98
parent 72e0d467
No related branches found
No related tags found
No related merge requests found
Loading
Loading
@@ -46,6 +46,10 @@ Change log for the astroid package (used to be astng)
 
Close PyCQA/pylint#1884
 
* Inference now understands the 'isinstance' builtin
Close #98
 
2017-12-15 -- 1.6.0
 
Loading
Loading
Loading
Loading
@@ -12,7 +12,8 @@ from textwrap import dedent
 
import six
from astroid import (MANAGER, UseInferenceDefault, AttributeInferenceError,
inference_tip, InferenceError, NameInferenceError)
inference_tip, InferenceError, NameInferenceError,
AstroidTypeError, AstroidError)
from astroid import arguments
from astroid.builder import AstroidBuilder
from astroid import helpers
Loading
Loading
@@ -515,6 +516,64 @@ def _infer_object__new__decorator_check(node):
return False
 
 
def infer_isinstance(callnode, context=None):
"""Infer isinstance calls
:param nodes.Call callnode: an isinstance call
:param InferenceContext: context for call
(currently unused but is a common interface for inference)
:rtype nodes.Const: Boolean Const value of isinstance call
:raises UseInferenceDefault: If the node cannot be inferred
"""
call = arguments.CallSite.from_call(callnode)
if call.keyword_arguments:
# isinstance doesn't support keyword arguments
raise UseInferenceDefault("TypeError: isinstance() takes no keyword arguments")
if len(call.positional_arguments) != 2:
raise UseInferenceDefault(
"Expected two arguments, got {count}"
.format(count=len(call.positional_arguments)))
# The left hand argument is the obj to be checked
obj_node, class_or_tuple_node = call.positional_arguments
# The right hand argument is the class(es) that the given
# obj is to be check is an instance of
try:
class_container = _class_or_tuple_to_container(
class_or_tuple_node, context=context)
except InferenceError:
raise UseInferenceDefault
try:
isinstance_bool = helpers.object_isinstance(
obj_node, class_container, context)
except AstroidTypeError as exc:
raise UseInferenceDefault("TypeError: " + str(exc))
except AstroidError:
raise UseInferenceDefault
return nodes.Const(isinstance_bool)
def _class_or_tuple_to_container(node, context=None):
# Move inferences results into container
# to simplify later logic
# raises InferenceError if any of the inferences fall through
node_infer = next(node.infer(context=context))
# arg2 MUST be a type or a TUPLE of types
# for isinstance
if isinstance(node_infer, nodes.Tuple):
class_container = [
next(node.infer(context=context))
for node in node_infer.elts
]
class_container = [
klass_node for klass_node
in class_container if klass_node is not None
]
else:
class_container = [node_infer]
return class_container
# Builtins inference
register_builtin_transform(infer_bool, 'bool')
register_builtin_transform(infer_super, 'super')
Loading
Loading
@@ -528,6 +587,7 @@ register_builtin_transform(infer_dict, 'dict')
register_builtin_transform(infer_frozenset, 'frozenset')
register_builtin_transform(infer_type, 'type')
register_builtin_transform(infer_slice, 'slice')
register_builtin_transform(infer_isinstance, 'isinstance')
 
# Infer object.__new__ calls
MANAGER.register_transform(
Loading
Loading
Loading
Loading
@@ -897,5 +897,134 @@ class SubprocessTest(unittest.TestCase):
self.assertIsInstance(next(inst.igetattr("args")), nodes.List)
 
 
class TestIsinstanceInference:
"""Test isinstance builtin inference"""
def test_type_type(self):
"""Make sure isinstance can check builtin int types"""
assert _get_result("isinstance(type, type)") == "True" == str(isinstance(type, type))
def test_object_type(self):
assert _get_result("isinstance(object, type)") == "True" == str(isinstance(object, type))
def test_type_object(self):
assert _get_result("isinstance(type, object)") == "True" == str(isinstance(type, object))
def test_isinstance_int_true(self):
"""Make sure isinstance can check builtin int types"""
assert _get_result("isinstance(1, int)") == "True" == str(isinstance(1, int))
def test_isinstance_int_false(self):
assert _get_result("isinstance('a', int)") == "False" == str(isinstance('a', int))
def test_isinstance_object_true(self):
class Bar(object):
pass
assert _get_result("""
class Bar(object):
pass
isinstance(Bar(), object)
""") == "True" == str(isinstance(Bar(), object))
def test_isinstance_object_true3(self):
class Bar(object):
pass
assert _get_result("""
class Bar(object):
pass
isinstance(Bar(), Bar)
""") == "True" == str(isinstance(Bar(), Bar))
def test_isinstance_class_false(self):
class Foo(object):
pass
class Bar(object):
pass
assert _get_result("""
class Foo(object):
pass
class Bar(object):
pass
isinstance(Bar(), Foo)
""") == "False" == str(isinstance(Bar(), Foo))
def test_isinstance_type_false(self):
class Bar(object):
pass
assert _get_result("""
class Bar(object):
pass
isinstance(Bar(), type)
""") == "False" == str(isinstance(Bar(), type))
def test_isinstance_str_true(self):
"""Make sure isinstance can check bultin str types"""
assert _get_result("isinstance('a', str)") == "True" == str(isinstance('a', str))
def test_isinstance_str_false(self):
assert _get_result("isinstance(1, str)") == "False" == str(isinstance(1, str))
def test_isinstance_tuple_argument(self):
"""obj just has to be an instance of ANY class/type on the right"""
assert _get_result("isinstance(1, (str, int))") == "True" == str(isinstance(1, (str, int)))
def test_isinstance_type_false2(self):
assert _get_result("""
isinstance(1, type)
""") == "False" == str(isinstance(1, type))
def test_isinstance_object_true2(self):
class Bar(type):
pass
mainbar = Bar("Bar", tuple(), {})
assert _get_result("""
class Bar(type):
pass
mainbar = Bar("Bar", tuple(), {})
isinstance(mainbar, object)
""") == "True" == str(isinstance(mainbar, object))
def test_isinstance_type_true(self):
class Bar(type):
pass
mainbar = Bar("Bar", tuple(), {})
assert _get_result("""
class Bar(type):
pass
mainbar = Bar("Bar", tuple(), {})
isinstance(mainbar, type)
""") == "True" == str(isinstance(mainbar, type))
def test_isinstance_edge_case(self):
"""isinstance allows bad type short-circuting"""
assert _get_result("isinstance(1, (int, 1))") == "True" == str(isinstance(1, (int, 1)))
def test_uninferable_bad_type(self):
"""The second argument must be a class or a tuple of classes"""
# Should I subclass
with pytest.raises(astroid.InferenceError):
_get_result_node("isinstance(int, 1)")
with pytest.raises(TypeError):
isinstance(int, 1)
def test_uninferable_keywords(self):
"""isinstance does not allow keywords"""
with pytest.raises(astroid.InferenceError):
_get_result_node("isinstance(1, class_or_tuple=int)")
with pytest.raises(TypeError):
isinstance(1, class_or_tuple=int)
def test_too_many_args(self):
"""isinstance must have two arguments"""
with pytest.raises(astroid.InferenceError):
_get_result_node("isinstance(1, int, str)")
with pytest.raises(TypeError):
isinstance(1, int, str)
def _get_result_node(code):
node = next(astroid.extract_node(code).infer())
return node
def _get_result(code):
return _get_result_node(code).as_string()
if __name__ == '__main__':
unittest.main()
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment