diff --git a/crates/ty_python_semantic/resources/mdtest/type_properties/is_assignable_to.md b/crates/ty_python_semantic/resources/mdtest/type_properties/is_assignable_to.md index ee5ce9c8da..ab730adb68 100644 --- a/crates/ty_python_semantic/resources/mdtest/type_properties/is_assignable_to.md +++ b/crates/ty_python_semantic/resources/mdtest/type_properties/is_assignable_to.md @@ -1064,6 +1064,37 @@ static_assert(not is_assignable_to(A, Callable[[int], int])) reveal_type(A()(1)) # revealed: str ``` +### Subclass of + +#### Type of a class with constructor methods + +```py +from typing import Callable +from ty_extensions import static_assert, is_assignable_to + +class A: + def __init__(self, x: int) -> None: ... + +class B: + def __new__(cls, x: str) -> "B": + return super().__new__(cls) + +static_assert(is_assignable_to(type[A], Callable[[int], A])) +static_assert(not is_assignable_to(type[A], Callable[[str], A])) + +static_assert(is_assignable_to(type[B], Callable[[str], B])) +static_assert(not is_assignable_to(type[B], Callable[[int], B])) +``` + +#### Type with no generic parameters + +```py +from typing import Callable, Any +from ty_extensions import static_assert, is_assignable_to + +static_assert(is_assignable_to(type, Callable[..., Any])) +``` + ## Generics ### Assignability of generic types parameterized by gradual types diff --git a/crates/ty_python_semantic/resources/mdtest/type_properties/is_subtype_of.md b/crates/ty_python_semantic/resources/mdtest/type_properties/is_subtype_of.md index 7476b57c0f..51756bac64 100644 --- a/crates/ty_python_semantic/resources/mdtest/type_properties/is_subtype_of.md +++ b/crates/ty_python_semantic/resources/mdtest/type_properties/is_subtype_of.md @@ -1752,6 +1752,28 @@ static_assert(not is_subtype_of(TypeOf[F], Callable[[], str])) static_assert(not is_subtype_of(TypeOf[F], Callable[[int], F])) ``` +### Subclass of + +#### Type of a class with constructor methods + +```py +from typing import Callable +from ty_extensions import TypeOf, static_assert, is_subtype_of + +class A: + def __init__(self, x: int) -> None: ... + +class B: + def __new__(cls, x: str) -> "B": + return super().__new__(cls) + +static_assert(is_subtype_of(type[A], Callable[[int], A])) +static_assert(not is_subtype_of(type[A], Callable[[str], A])) + +static_assert(is_subtype_of(type[B], Callable[[str], B])) +static_assert(not is_subtype_of(type[B], Callable[[int], B])) +``` + ### Bound methods ```py diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index f9835e7db8..42db465d39 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -1559,6 +1559,16 @@ impl<'db> Type<'db> { .into_callable(db) .has_relation_to(db, target, relation), + // TODO: This is unsound so in future we can consider an opt-in option to disable it. + (Type::SubclassOf(subclass_of_ty), Type::Callable(_)) + if subclass_of_ty.subclass_of().into_class().is_some() => + { + let class = subclass_of_ty.subclass_of().into_class().unwrap(); + class + .into_callable(db) + .has_relation_to(db, target, relation) + } + // `Literal[str]` is a subtype of `type` because the `str` class object is an instance of its metaclass `type`. // `Literal[abc.ABC]` is a subtype of `abc.ABCMeta` because the `abc.ABC` class object // is an instance of its metaclass `abc.ABCMeta`.