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 index 3a234bb8a6..69efe5958a 100644 --- 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 @@ -256,6 +256,65 @@ static_assert(is_equivalent_to(int | Callable[[int | str], None], Callable[[str ### Overloads -TODO +#### One overload + +`overloaded.pyi`: + +```pyi +from typing import overload + +class Grandparent: ... +class Parent(Grandparent): ... +class Child(Parent): ... + +@overload +def overloaded(a: Child) -> None: ... +@overload +def overloaded(a: Parent) -> None: ... +@overload +def overloaded(a: Grandparent) -> None: ... +``` + +```py +from knot_extensions import CallableTypeOf, is_equivalent_to, static_assert +from overloaded import Grandparent, Parent, Child, overloaded + +def grandparent(a: Grandparent) -> None: ... + +static_assert(is_equivalent_to(CallableTypeOf[grandparent], CallableTypeOf[overloaded])) +static_assert(is_equivalent_to(CallableTypeOf[overloaded], CallableTypeOf[grandparent])) +``` + +#### Both overloads + +`overloaded.pyi`: + +```pyi +from typing import overload + +class Grandparent: ... +class Parent(Grandparent): ... +class Child(Parent): ... + +@overload +def pg(a: Parent) -> None: ... +@overload +def pg(a: Grandparent) -> None: ... + +@overload +def cpg(a: Child) -> None: ... +@overload +def cpg(a: Parent) -> None: ... +@overload +def cpg(a: Grandparent) -> None: ... +``` + +```py +from knot_extensions import CallableTypeOf, is_equivalent_to, static_assert +from overloaded import pg, cpg + +static_assert(is_equivalent_to(CallableTypeOf[pg], CallableTypeOf[cpg])) +static_assert(is_equivalent_to(CallableTypeOf[cpg], CallableTypeOf[pg])) +``` [the equivalence relation]: https://typing.python.org/en/latest/spec/glossary.html#term-equivalent diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index dc7a789939..1047e929f0 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -6984,11 +6984,22 @@ impl<'db> CallableType<'db> { fn is_equivalent_to(self, db: &'db dyn Db, other: Self) -> bool { match (&**self.signatures(db), &**other.signatures(db)) { ([self_signature], [other_signature]) => { + // Common case: both callable types contain a single signature, use the custom + // equivalence check instead of delegating it to the subtype check. self_signature.is_equivalent_to(db, other_signature) } - _ => { - // TODO: overloads - false + (self_signatures, other_signatures) => { + if !self_signatures + .iter() + .chain(other_signatures.iter()) + .all(|signature| signature.is_fully_static(db)) + { + return false; + } + if self == other { + return true; + } + self.is_subtype_of(db, other) && other.is_subtype_of(db, self) } } }