diff --git a/crates/red_knot_python_semantic/resources/mdtest/type_properties/is_equivalent_to.md b/crates/red_knot_python_semantic/resources/mdtest/type_properties/is_equivalent_to.md new file mode 100644 index 00000000000000..e7554d5efd34b9 --- /dev/null +++ b/crates/red_knot_python_semantic/resources/mdtest/type_properties/is_equivalent_to.md @@ -0,0 +1,16 @@ +# Equivalent relation + +```py +from typing import Any +from typing_extensions import Literal +from knot_extensions import Unknown, is_equivalent_to, static_assert + +static_assert(is_equivalent_to(Literal[1, 2], Literal[1, 2])) +static_assert(is_equivalent_to(type[object], type)) + +static_assert(not is_equivalent_to(Any, Any)) +static_assert(not is_equivalent_to(Unknown, Unknown)) +static_assert(not is_equivalent_to(Any, None)) +static_assert(not is_equivalent_to(Literal[1, 2], Literal[1, 0])) +static_assert(not is_equivalent_to(Literal[1, 2], Literal[1, 2, 3])) +``` diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index 6c903decec7dbf..919dec27a64a68 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -1953,8 +1953,11 @@ impl<'db> Type<'db> { let (ty_a, ty_b) = binding .two_parameter_tys() .unwrap_or((Type::unknown(), Type::unknown())); - binding - .set_return_ty(Type::BooleanLiteral(ty_a.is_equivalent_to(db, ty_b))); + + let equivalence = + ty_a.is_equivalent_to(db, ty_b) && ty_b.is_equivalent_to(db, ty_a); + + binding.set_return_ty(Type::BooleanLiteral(equivalence)); CallOutcome::callable(binding) } Some(KnownFunction::IsSubtypeOf) => { @@ -4332,33 +4335,6 @@ pub(crate) mod tests { } } - #[test_case( - Ty::Union(vec![Ty::IntLiteral(1), Ty::IntLiteral(2)]), - Ty::Union(vec![Ty::IntLiteral(1), Ty::IntLiteral(2)]) - )] - #[test_case(Ty::SubclassOfBuiltinClass("object"), Ty::BuiltinInstance("type"))] - fn is_equivalent_to(from: Ty, to: Ty) { - let db = setup_db(); - let from = from.into_type(&db); - let to = to.into_type(&db); - assert!(from.is_equivalent_to(&db, to)); - assert!(to.is_equivalent_to(&db, from)); - } - - #[test_case(Ty::Any, Ty::Any)] - #[test_case(Ty::Any, Ty::None)] - #[test_case(Ty::Unknown, Ty::Unknown)] - #[test_case(Ty::Todo, Ty::Todo)] - #[test_case(Ty::Union(vec![Ty::IntLiteral(1), Ty::IntLiteral(2)]), Ty::Union(vec![Ty::IntLiteral(1), Ty::IntLiteral(0)]))] - #[test_case(Ty::Union(vec![Ty::IntLiteral(1), Ty::IntLiteral(2)]), Ty::Union(vec![Ty::IntLiteral(1), Ty::IntLiteral(2), Ty::IntLiteral(3)]))] - fn is_not_equivalent_to(from: Ty, to: Ty) { - let db = setup_db(); - let from = from.into_type(&db); - let to = to.into_type(&db); - assert!(!from.is_equivalent_to(&db, to)); - assert!(!to.is_equivalent_to(&db, from)); - } - #[test_case(Ty::Never, Ty::Never)] #[test_case(Ty::Never, Ty::None)] #[test_case(Ty::Never, Ty::BuiltinInstance("int"))]