88import pytest
99
1010from astroid import builder , nodes
11+ from astroid .bases import Instance
1112from astroid .util import Uninferable
1213
1314
@@ -19,6 +20,8 @@ def common_params(node: str) -> pytest.MarkDecorator:
1920 (f"{ node } is not None" , 3 , None ),
2021 (f"{ node } " , 3 , None ),
2122 (f"not { node } " , None , 3 ),
23+ (f"isinstance({ node } , int)" , 3 , None ),
24+ (f"isinstance({ node } , (int, str))" , 3 , None ),
2225 ),
2326 )
2427
@@ -773,3 +776,134 @@ def method(self, x = {fail_val}):
773776 assert isinstance (inferred [0 ], nodes .Const )
774777 assert inferred [0 ].value == fail_val
775778 assert inferred [1 ].value is Uninferable
779+
780+
781+ def test_isinstance_equal_types () -> None :
782+ """Test constraint for an object whose type is equal to the checked type."""
783+ node = builder .extract_node (
784+ f"""
785+ class A:
786+ pass
787+
788+ x = A()
789+
790+ if isinstance(x, A):
791+ x #@
792+ """
793+ )
794+
795+ inferred = node .inferred ()
796+ assert len (inferred ) == 1
797+ assert isinstance (inferred [0 ], Instance )
798+ assert isinstance (inferred [0 ]._proxied , nodes .ClassDef )
799+ assert inferred [0 ].name == "A"
800+
801+
802+ def test_isinstance_subtype () -> None :
803+ """Test constraint for an object whose type is a strict subtype of the checked type."""
804+ node = builder .extract_node (
805+ f"""
806+ class A:
807+ pass
808+
809+ class B(A):
810+ pass
811+
812+ x = B()
813+
814+ if isinstance(x, A):
815+ x #@
816+ """
817+ )
818+
819+ inferred = node .inferred ()
820+ assert len (inferred ) == 1
821+ assert isinstance (inferred [0 ], Instance )
822+ assert isinstance (inferred [0 ]._proxied , nodes .ClassDef )
823+ assert inferred [0 ].name == "B"
824+
825+
826+ def test_isinstance_unrelated_types ():
827+ """Test constraint for an object whose type is not related to the checked type."""
828+ node = builder .extract_node (
829+ f"""
830+ class A:
831+ pass
832+
833+ class B:
834+ pass
835+
836+ x = A()
837+
838+ if isinstance(x, B):
839+ x #@
840+ """
841+ )
842+
843+ inferred = node .inferred ()
844+ assert len (inferred ) == 1
845+ assert inferred [0 ] is Uninferable
846+
847+
848+ def test_isinstance_supertype ():
849+ """Test constraint for an object whose type is a strict supertype of the checked type."""
850+ node = builder .extract_node (
851+ f"""
852+ class A:
853+ pass
854+
855+ class B(A):
856+ pass
857+
858+ x = A()
859+
860+ if isinstance(x, B):
861+ x #@
862+ """
863+ )
864+
865+ inferred = node .inferred ()
866+ assert len (inferred ) == 1
867+ assert inferred [0 ] is Uninferable
868+
869+
870+ def test_isinstance_keyword_arguments ():
871+ """Test that constraint does not apply when `isinstance` is called
872+ with keyword arguments.
873+ """
874+ n1 , n2 = builder .extract_node (
875+ f"""
876+ x = 3
877+
878+ if isinstance(object=x, classinfo=str):
879+ x #@
880+
881+ if isinstance(x, str, object=x, classinfo=str):
882+ x #@
883+ """
884+ )
885+
886+ for node in (n1 , n2 ):
887+ inferred = node .inferred ()
888+ assert len (inferred ) == 1
889+ assert isinstance (inferred [0 ], nodes .Const )
890+ assert inferred [0 ].value == 3
891+
892+
893+ def test_isinstance_extra_argument ():
894+ """Test that constraint does not apply when `isinstance` is called
895+ with more than two positional arguments.
896+ """
897+ node = builder .extract_node (
898+ f"""
899+ x = 3
900+
901+ if isinstance(x, str, bool):
902+ x #@
903+ """
904+ )
905+
906+ inferred = node .inferred ()
907+ assert len (inferred ) == 1
908+ assert isinstance (inferred [0 ], nodes .Const )
909+ assert inferred [0 ].value == 3
0 commit comments