diff --git a/crates/red_knot_python_semantic/resources/mdtest/comparison/identity_tests.md b/crates/red_knot_python_semantic/resources/mdtest/comparison/identity_tests.md new file mode 100644 index 0000000000..bf162efa0a --- /dev/null +++ b/crates/red_knot_python_semantic/resources/mdtest/comparison/identity_tests.md @@ -0,0 +1,40 @@ +# Identity tests + +```py +class A: ... + +def get_a() -> A: ... +def get_object() -> object: ... + +a1 = get_a() +a2 = get_a() + +n1 = None +n2 = None + +o = get_object() + +reveal_type(a1 is a1) # revealed: bool +reveal_type(a1 is a2) # revealed: bool + +reveal_type(n1 is n1) # revealed: Literal[True] +reveal_type(n1 is n2) # revealed: Literal[True] + +reveal_type(a1 is n1) # revealed: Literal[False] +reveal_type(n1 is a1) # revealed: Literal[False] + +reveal_type(a1 is o) # revealed: bool +reveal_type(n1 is o) # revealed: bool + +reveal_type(a1 is not a1) # revealed: bool +reveal_type(a1 is not a2) # revealed: bool + +reveal_type(n1 is not n1) # revealed: Literal[False] +reveal_type(n1 is not n2) # revealed: Literal[False] + +reveal_type(a1 is not n1) # revealed: Literal[True] +reveal_type(n1 is not a1) # revealed: Literal[True] + +reveal_type(a1 is not o) # revealed: bool +reveal_type(n1 is not o) # revealed: bool +``` diff --git a/crates/red_knot_python_semantic/src/types/infer.rs b/crates/red_knot_python_semantic/src/types/infer.rs index fd4b21758b..c4e0594a1f 100644 --- a/crates/red_knot_python_semantic/src/types/infer.rs +++ b/crates/red_knot_python_semantic/src/types/infer.rs @@ -3388,8 +3388,28 @@ impl<'db> TypeInferenceBuilder<'db> { ast::CmpOp::NotIn => { membership_test_comparison(MembershipTestCompareOperator::NotIn) } - ast::CmpOp::Is => Ok(KnownClass::Bool.to_instance(self.db)), - ast::CmpOp::IsNot => Ok(KnownClass::Bool.to_instance(self.db)), + ast::CmpOp::Is => { + if left.is_disjoint_from(self.db, right) { + Ok(Type::BooleanLiteral(false)) + } else if left.is_singleton(self.db) + && left.is_equivalent_to(self.db, right) + { + Ok(Type::BooleanLiteral(true)) + } else { + Ok(KnownClass::Bool.to_instance(self.db)) + } + } + ast::CmpOp::IsNot => { + if left.is_disjoint_from(self.db, right) { + Ok(Type::BooleanLiteral(true)) + } else if left.is_singleton(self.db) + && left.is_equivalent_to(self.db, right) + { + Ok(Type::BooleanLiteral(false)) + } else { + Ok(KnownClass::Bool.to_instance(self.db)) + } + } } } // TODO: handle more types