Repository: astral-sh/ruff
Pull request #17047: [red-knot] Allow `CallableTypeFromFunction` to display the signatures of callable types that are not function literals
Author: login: AlexWaygood
Labels: name: ty
description: Multi-file analysis & type inference
Created: 2025-03-28T19:29:47Z | Merged: 2025-03-28T20:23:05Z

I found this helpful for understanding some of the stuff that was going on in https://github.com/astral-sh/ruff/pull/17005. It means the name of the special form is perhaps no longer ideal; I could possibly rename it if we agree that this is a useful feature.


Diff:
diff --git a/crates/red_knot_python_semantic/resources/mdtest/type_api.md b/crates/red_knot_python_semantic/resources/mdtest/type_api.md
index 94018eb5bed2ad..1bd7a0ca8b8f14 100644
--- a/crates/red_knot_python_semantic/resources/mdtest/type_api.md
+++ b/crates/red_knot_python_semantic/resources/mdtest/type_api.md
@@ -398,16 +398,16 @@ def f(x: TypeOf) -> None:
     reveal_type(x)  # revealed: Unknown
 ```
 
-## `CallableTypeFromFunction`
+## `CallableTypeOf`
 
-The `CallableTypeFromFunction` special form can be used to extract the type of a function literal as
-a callable type. This can be used to get the externally-visibly signature of the function, which can
-then be used to test various type properties.
+The `CallableTypeOf` special form can be used to extract the `Callable` structural type inhabited by
+a given callable object. This can be used to get the externally visibly signature of the object,
+which can then be used to test various type properties.
 
-It accepts a single type parameter which is expected to be a function literal.
+It accepts a single type parameter which is expected to be a callable object.
 
 ```py
-from knot_extensions import CallableTypeFromFunction
+from knot_extensions import CallableTypeOf
 
 def f1():
     return
@@ -418,25 +418,41 @@ def f2() -> int:
 def f3(x: int, y: str) -> None:
     return
 
-# error: [invalid-type-form] "Special form `knot_extensions.CallableTypeFromFunction` expected exactly one type parameter"
-c1: CallableTypeFromFunction[f1, f2]
-# error: [invalid-type-form] "Expected the first argument to `knot_extensions.CallableTypeFromFunction` to be a function literal, but got `Literal[int]`"
-c2: CallableTypeFromFunction[int]
+# error: [invalid-type-form] "Special form `knot_extensions.CallableTypeOf` expected exactly one type parameter"
+c1: CallableTypeOf[f1, f2]
 
-# error: [invalid-type-form] "`knot_extensions.CallableTypeFromFunction` requires exactly one argument when used in a type expression"
-def f(x: CallableTypeFromFunction) -> None:
+# error: [invalid-type-form] "Expected the first argument to `knot_extensions.CallableTypeOf` to be a callable object, but got an object of type `Literal["foo"]`"
+c2: CallableTypeOf["foo"]
+
+# error: [invalid-type-form] "`knot_extensions.CallableTypeOf` requires exactly one argument when used in a type expression"
+def f(x: CallableTypeOf) -> None:
     reveal_type(x)  # revealed: Unknown
 ```
 
-Using it in annotation to reveal the signature of the function:
+Using it in annotation to reveal the signature of the callable object:
 
 ```py
+class Foo:
+    def __init__(self, x: int) -> None:
+        pass
+
+    def __call__(self, x: int) -> str:
+        return "foo"
+
 def _(
-    c1: CallableTypeFromFunction[f1],
-    c2: CallableTypeFromFunction[f2],
-    c3: CallableTypeFromFunction[f3],
+    c1: CallableTypeOf[f1],
+    c2: CallableTypeOf[f2],
+    c3: CallableTypeOf[f3],
+    c4: CallableTypeOf[Foo],
+    c5: CallableTypeOf[Foo(42).__call__],
 ) -> None:
     reveal_type(c1)  # revealed: () -> Unknown
     reveal_type(c2)  # revealed: () -> int
     reveal_type(c3)  # revealed: (x: int, y: str) -> None
+
+    # TODO: should be `(x: int) -> Foo`
+    reveal_type(c4)  # revealed: (...) -> Foo
+
+    # TODO: `self` is bound here; this should probably be `(x: int) -> str`?
+    reveal_type(c5)  #  revealed: (self, x: int) -> str
 ```
diff --git a/crates/red_knot_python_semantic/resources/mdtest/type_properties/is_assignable_to.md b/crates/red_knot_python_semantic/resources/mdtest/type_properties/is_assignable_to.md
index e6693d11da7e27..3937d2ee08ab0a 100644
--- a/crates/red_knot_python_semantic/resources/mdtest/type_properties/is_assignable_to.md
+++ b/crates/red_knot_python_semantic/resources/mdtest/type_properties/is_assignable_to.md
@@ -402,7 +402,7 @@ are covered in the [subtyping tests](./is_subtype_of.md#callable).
 ### Return type
 
 ```py
-from knot_extensions import CallableTypeFromFunction, Unknown, static_assert, is_assignable_to
+from knot_extensions import CallableTypeOf, Unknown, static_assert, is_assignable_to
 from typing import Any, Callable
 
 static_assert(is_assignable_to(Callable[[], Any], Callable[[], int]))
@@ -432,7 +432,7 @@ A `Callable` which uses the gradual form (`...`) for the parameter types is cons
 input signature.
 
 ```py
-from knot_extensions import CallableTypeFromFunction, static_assert, is_assignable_to
+from knot_extensions import CallableTypeOf, static_assert, is_assignable_to
 from typing import Any, Callable
 
 static_assert(is_assignable_to(Callable[[], None], Callable[..., None]))
@@ -450,12 +450,12 @@ def keyword_only(*, a: int, b: int) -> None: ...
 def keyword_variadic(**kwargs: int) -> None: ...
 def mixed(a: int, /, b: int, *args: int, c: int, **kwargs: int) -> None: ...
 
-static_assert(is_assignable_to(CallableTypeFromFunction[positional_only], Callable[..., None]))
-static_assert(is_assignable_to(CallableTypeFromFunction[positional_or_keyword], Callable[..., None]))
-static_assert(is_assignable_to(CallableTypeFromFunction[variadic], Callable[..., None]))
-static_assert(is_assignable_to(CallableTypeFromFunction[keyword_only], Callable[..., None]))
-static_assert(is_assignable_to(CallableTypeFromFunction[keyword_variadic], Callable[..., None]))
-static_assert(is_assignable_to(CallableTypeFromFunction[mixed], Callable[..., None]))
+static_assert(is_assignable_to(CallableTypeOf[positional_only], Callable[..., None]))
+static_assert(is_assignable_to(CallableTypeOf[positional_or_keyword], Callable[..., None]))
+static_assert(is_assignable_to(CallableTypeOf[variadic], Callable[..., None]))
+static_assert(is_assignable_to(CallableTypeOf[keyword_only], Callable[..., None]))
+static_assert(is_assignable_to(CallableTypeOf[keyword_variadic], Callable[..., None]))
+static_assert(is_assignable_to(CallableTypeOf[mixed], Callable[..., None]))
 ```
 
 And, even if the parameters are unannotated.
@@ -468,12 +468,12 @@ def keyword_only(*, a, b) -> None: ...
 def keyword_variadic(**kwargs) -> None: ...
 def mixed(a, /, b, *args, c, **kwargs) -> None: ...
 
-static_assert(is_assignable_to(CallableTypeFromFunction[positional_only], Callable[..., None]))
-static_assert(is_assignable_to(CallableTypeFromFunction[positional_or_keyword], Callable[..., None]))
-static_assert(is_assignable_to(CallableTypeFromFunction[variadic], Callable[..., None]))
-static_assert(is_assignable_to(CallableTypeFromFunction[keyword_only], Callable[..., None]))
-static_assert(is_assignable_to(CallableTypeFromFunction[keyword_variadic], Callable[..., None]))
-static_assert(is_assignable_to(CallableTypeFromFunction[mixed], Callable[..., None]))
+static_assert(is_assignable_to(CallableTypeOf[positional_only], Callable[..., None]))
+static_assert(is_assignable_to(CallableTypeOf[positional_or_keyword], Callable[..., None]))
+static_assert(is_assignable_to(CallableTypeOf[variadic], Callable[..., None]))
+static_assert(is_assignable_to(CallableTypeOf[keyword_only], Callable[..., None]))
+static_assert(is_assignable_to(CallableTypeOf[keyword_variadic], Callable[..., None]))
+static_assert(is_assignable_to(CallableTypeOf[mixed], Callable[..., None]))
 ```
 
 [typing documentation]: https://typing.readthedocs.io/en/latest/spec/concepts.html#the-assignable-to-or-consistent-subtyping-relation
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 7aa76be99cd551..9a7538f0f51207 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
@@ -127,13 +127,13 @@ the parameter in one of the callable has a default value then the corresponding
 other callable should also have a default value.
 
 ```py
-from knot_extensions import CallableTypeFromFunction, is_equivalent_to, static_assert
+from knot_extensions import CallableTypeOf, is_equivalent_to, static_assert
 from typing import Callable
 
 def f1(a: int = 1) -> None: ...
 def f2(a: int = 2) -> None: ...
 
-static_assert(is_equivalent_to(CallableTypeFromFunction[f1], CallableTypeFromFunction[f2]))
+static_assert(is_equivalent_to(CallableTypeOf[f1], CallableTypeOf[f2]))
 ```
 
 The names of the positional-only, variadic and keyword-variadic parameters does not need to be the
@@ -143,7 +143,7 @@ same.
 def f3(a1: int, /, *args1: int, **kwargs2: int) -> None: ...
 def f4(a2: int, /, *args2: int, **kwargs1: int) -> None: ...
 
-static_assert(is_equivalent_to(CallableTypeFromFunction[f3], CallableTypeFromFunction[f4]))
+static_assert(is_equivalent_to(CallableTypeOf[f3], CallableTypeOf[f4]))
 ```
 
 Putting it all together, the following two callables are equivalent:
@@ -152,7 +152,7 @@ Putting it all together, the following two callables are equivalent:
 def f5(a1: int, /, b: float, c: bool = False, *args1: int, d: int = 1, e: str, **kwargs1: float) -> None: ...
 def f6(a2: int, /, b: float, c: bool = True, *args2: int, d: int = 2, e: str, **kwargs2: float) -> None: ...
 
-static_assert(is_equivalent_to(CallableTypeFromFunction[f5], CallableTypeFromFunction[f6]))
+static_assert(is_equivalent_to(CallableTypeOf[f5], CallableTypeOf[f6]))
 ```
 
 ### Not equivalent
@@ -160,7 +160,7 @@ static_assert(is_equivalent_to(CallableTypeFromFunction[f5], CallableTypeFromFun
 There are multiple cases when two callable types are not equivalent which are enumerated below.
 
 ```py
-from knot_extensions import CallableTypeFromFunction, is_equivalent_to, static_assert
+from knot_extensions import CallableTypeOf, is_equivalent_to, static_assert
 from typing import Callable
 ```
 
@@ -170,7 +170,7 @@ When the number of parameters is different:
 def f1(a: int) -> None: ...
 def f2(a: int, b: int) -> None: ...
 
-static_assert(not is_equivalent_to(CallableTypeFromFunction[f1], CallableTypeFromFunction[f2]))
+static_assert(not is_equivalent_to(CallableTypeOf[f1], CallableTypeOf[f2]))
 ```
 
 When either of the callable types uses a gradual form for the parameters:
@@ -187,9 +187,9 @@ def f3(): ...
 def f4() -> None: ...
 
 static_assert(not is_equivalent_to(Callable[[], int], Callable[[], None]))
-static_assert(not is_equivalent_to(CallableTypeFromFunction[f3], CallableTypeFromFunction[f3]))
-static_assert(not is_equivalent_to(CallableTypeFromFunction[f3], CallableTypeFromFunction[f4]))
-static_assert(not is_equivalent_to(CallableTypeFromFunction[f4], CallableTypeFromFunction[f3]))
+static_assert(not is_equivalent_to(CallableTypeOf[f3], CallableTypeOf[f3]))
+static_assert(not is_equivalent_to(CallableTypeOf[f3], CallableTypeOf[f4]))
+static_assert(not is_equivalent_to(CallableTypeOf[f4], CallableTypeOf[f3]))
 ```
 
 When the parameter names are different:
@@ -198,13 +198,13 @@ When the parameter names are different:
 def f5(a: int) -> None: ...
 def f6(b: int) -> None: ...
 
-static_assert(not is_equivalent_to(CallableTypeFromFunction[f5], CallableTypeFromFunction[f6]))
+static_assert(not is_equivalent_to(CallableTypeOf[f5], CallableTypeOf[f6]))
 ```
 
 When only one of the callable types has parameter names:
 
 ```py
-static_assert(not is_equivalent_to(CallableTypeFromFunction[f5], Callable[[int], None]))
+static_assert(not is_equivalent_to(CallableTypeOf[f5], Callable[[int], None]))
 ```
 
 When the parameter kinds are different:
@@ -213,7 +213,7 @@ When the parameter kinds are different:
 def f7(a: int, /) -> None: ...
 def f8(a: int) -> None: ...
 
-static_assert(not is_equivalent_to(CallableTypeFromFunction[f7], CallableTypeFromFunction[f8]))
+static_assert(not is_equivalent_to(CallableTypeOf[f7], CallableTypeOf[f8]))
 ```
 
 When the annotated types of the parameters are not equivalent or absent in one or both of the
@@ -224,10 +224,10 @@ def f9(a: int) -> None: ...
 def f10(a: str) -> None: ...
 def f11(a) -> None: ...
 
-static_assert(not is_equivalent_to(CallableTypeFromFunction[f9], CallableTypeFromFunction[f10]))
-static_assert(not is_equivalent_to(CallableTypeFromFunction[f10], CallableTypeFromFunction[f11]))
-static_assert(not is_equivalent_to(CallableTypeFromFunction[f11], CallableTypeFromFunction[f10]))
-static_assert(not is_equivalent_to(CallableTypeFromFunction[f11], CallableTypeFromFunction[f11]))
+static_assert(not is_equivalent_to(CallableTypeOf[f9], CallableTypeOf[f10]))
+static_assert(not is_equivalent_to(CallableTypeOf[f10], CallableTypeOf[f11]))
+static_assert(not is_equivalent_to(CallableTypeOf[f11], CallableTypeOf[f10]))
+static_assert(not is_equivalent_to(CallableTypeOf[f11], CallableTypeOf[f11]))
 ```
 
 When the default value for a parameter is present only in one of the callable type:
@@ -236,8 +236,8 @@ When the default value for a parameter is present only in one of the callable ty
 def f12(a: int) -> None: ...
 def f13(a: int = 2) -> None: ...
 
-static_assert(not is_equivalent_to(CallableTypeFromFunction[f12], CallableTypeFromFunction[f13]))
-static_assert(not is_equivalent_to(CallableTypeFromFunction[f13], CallableTypeFromFunction[f12]))
+static_assert(not is_equivalent_to(CallableTypeOf[f12], CallableTypeOf[f13]))
+static_assert(not is_equivalent_to(CallableTypeOf[f13], CallableTypeOf[f12]))
 ```
 
 [the equivalence relation]: https://typing.readthedocs.io/en/latest/spec/glossary.html#term-equivalent
diff --git a/crates/red_knot_python_semantic/resources/mdtest/type_properties/is_fully_static.md b/crates/red_knot_python_semantic/resources/mdtest/type_properties/is_fully_static.md
index 66fad1c13644a7..8af5af41545ad2 100644
--- a/crates/red_knot_python_semantic/resources/mdtest/type_properties/is_fully_static.md
+++ b/crates/red_knot_python_semantic/resources/mdtest/type_properties/is_fully_static.md
@@ -80,7 +80,7 @@ Using function literals, we can check more variations of callable types as it al
 parameters without annotations and no return type.
 
 ```py
-from knot_extensions import CallableTypeFromFunction, is_fully_static, static_assert
+from knot_extensions import CallableTypeOf, is_fully_static, static_assert
 
 def f00() -> None: ...
 def f01(a: int, b: str) -> None: ...
@@ -90,12 +90,12 @@ def f13(a, b: int): ...
 def f14(a, b: int) -> None: ...
 def f15(a, b) -> None: ...
 
-static_assert(is_fully_static(CallableTypeFromFunction[f00]))
-static_assert(is_fully_static(CallableTypeFromFunction[f01]))
+static_assert(is_fully_static(CallableTypeOf[f00]))
+static_assert(is_fully_static(CallableTypeOf[f01]))
 
-static_assert(not is_fully_static(CallableTypeFromFunction[f11]))
-static_assert(not is_fully_static(CallableTypeFromFunction[f12]))
-static_assert(not is_fully_static(CallableTypeFromFunction[f13]))
-static_assert(not is_fully_static(CallableTypeFromFunction[f14]))
-static_assert(not is_fully_static(CallableTypeFromFunction[f15]))
+static_assert(not is_fully_static(CallableTypeOf[f11]))
+static_assert(not is_fully_static(CallableTypeOf[f12]))
+static_assert(not is_fully_static(CallableTypeOf[f13]))
+static_assert(not is_fully_static(CallableTypeOf[f14]))
+static_assert(not is_fully_static(CallableTypeOf[f15]))
 ```
diff --git a/crates/red_knot_python_semantic/resources/mdtest/type_properties/is_gradual_equivalent_to.md b/crates/red_knot_python_semantic/resources/mdtest/type_properties/is_gradual_equivalent_to.md
index a45687d4ad9dd0..6d96420e3377b0 100644
--- a/crates/red_knot_python_semantic/resources/mdtest/type_properties/is_gradual_equivalent_to.md
+++ b/crates/red_knot_python_semantic/resources/mdtest/type_properties/is_gradual_equivalent_to.md
@@ -73,7 +73,7 @@ gradual types. The cases with fully static types and using different combination
 are covered in the [equivalence tests](./is_equivalent_to.md#callable).
 
 ```py
-from knot_extensions import Unknown, CallableTypeFromFunction, is_gradual_equivalent_to, static_assert
+from knot_extensions import Unknown, CallableTypeOf, is_gradual_equivalent_to, static_assert
 from typing import Any, Callable
 
 static_assert(is_gradual_equivalent_to(Callable[..., int], Callable[..., int]))
@@ -92,7 +92,7 @@ type of `Any`.
 def f1():
     return
 
-static_assert(is_gradual_equivalent_to(CallableTypeFromFunction[f1], Callable[[], Any]))
+static_assert(is_gradual_equivalent_to(CallableTypeOf[f1], Callable[[], Any]))
 ```
 
 And, similarly for parameters with no annotations.
@@ -101,7 +101,7 @@ And, similarly for parameters with no annotations.
 def f2(a, b, /) -> None:
     return
 
-static_assert(is_gradual_equivalent_to(CallableTypeFromFunction[f2], Callable[[Any, Any], None]))
+static_assert(is_gradual_equivalent_to(CallableTypeOf[f2], Callable[[Any, Any], None]))
 ```
 
 Additionally, as per the spec, a function definition that includes both `*args` and `**kwargs`
@@ -115,8 +115,8 @@ def variadic_without_annotation(*args, **kwargs):
 def variadic_with_annotation(*args: Any, **kwargs: Any) -> Any:
     return
 
-static_assert(is_gradual_equivalent_to(CallableTypeFromFunction[variadic_without_annotation], Callable[..., Any]))
-static_assert(is_gradual_equivalent_to(CallableTypeFromFunction[variadic_with_annotation], Callable[..., Any]))
+static_assert(is_gradual_equivalent_to(CallableTypeOf[variadic_without_annotation], Callable[..., Any]))
+static_assert(is_gradual_equivalent_to(CallableTypeOf[variadic_with_annotation], Callable[..., Any]))
 ```
 
 But, a function with either `*args` or `**kwargs` (and not both) is not gradual equivalent to a
@@ -129,8 +129,8 @@ def variadic_args(*args):
 def variadic_kwargs(**kwargs):
     return
 
-static_assert(not is_gradual_equivalent_to(CallableTypeFromFunction[variadic_args], Callable[..., Any]))
-static_assert(not is_gradual_equivalent_to(CallableTypeFromFunction[variadic_kwargs], Callable[..., Any]))
+static_assert(not is_gradual_equivalent_to(CallableTypeOf[variadic_args], Callable[..., Any]))
+static_assert(not is_gradual_equivalent_to(CallableTypeOf[variadic_kwargs], Callable[..., Any]))
 ```
 
 Parameter names, default values, and it's kind should also be considered when checking for gradual
@@ -140,18 +140,18 @@ equivalence.
 def f1(a): ...
 def f2(b): ...
 
-static_assert(not is_gradual_equivalent_to(CallableTypeFromFunction[f1], CallableTypeFromFunction[f2]))
+static_assert(not is_gradual_equivalent_to(CallableTypeOf[f1], CallableTypeOf[f2]))
 
 def f3(a=1): ...
 def f4(a=2): ...
 def f5(a): ...
 
-static_assert(is_gradual_equivalent_to(CallableTypeFromFunction[f3], CallableTypeFromFunction[f4]))
-static_assert(not is_gradual_equivalent_to(CallableTypeFromFunction[f3], CallableTypeFromFunction[f5]))
+static_assert(is_gradual_equivalent_to(CallableTypeOf[f3], CallableTypeOf[f4]))
+static_assert(not is_gradual_equivalent_to(CallableTypeOf[f3], CallableTypeOf[f5]))
 
 def f6(a, /): ...
 
-static_assert(not is_gradual_equivalent_to(CallableTypeFromFunction[f1], CallableTypeFromFunction[f6]))
+static_assert(not is_gradual_equivalent_to(CallableTypeOf[f1], CallableTypeOf[f6]))
 ```
 
 [materializations]: https://typing.readthedocs.io/en/latest/spec/glossary.html#term-materialize
diff --git a/crates/red_knot_python_semantic/resources/mdtest/type_properties/is_subtype_of.md b/crates/red_knot_python_semantic/resources/mdtest/type_properties/is_subtype_of.md
index 5944536880b0ed..38e7a50426fed8 100644
--- a/crates/red_knot_python_semantic/resources/mdtest/type_properties/is_subtype_of.md
+++ b/crates/red_knot_python_semantic/resources/mdtest/type_properties/is_subtype_of.md
@@ -530,13 +530,13 @@ Parameter types are contravariant.
 
 ```py
 from typing import Callable
-from knot_extensions import CallableTypeFromFunction, is_subtype_of, static_assert, TypeOf
+from knot_extensions import CallableTypeOf, is_subtype_of, static_assert, TypeOf
 
 def float_param(a: float, /) -> None: ...
 def int_param(a: int, /) -> None: ...
 
-static_assert(is_subtype_of(CallableTypeFromFunction[float_param], CallableTypeFromFunction[int_param]))
-static_assert(not is_subtype_of(CallableTypeFromFunction[int_param], CallableTypeFromFunction[float_param]))
+static_assert(is_subtype_of(CallableTypeOf[float_param], CallableTypeOf[int_param]))
+static_assert(not is_subtype_of(CallableTypeOf[int_param], CallableTypeOf[float_param]))
 
 static_assert(is_subtype_of(TypeOf[int_param], Callable[[int], None]))
 static_assert(is_subtype_of(TypeOf[float_param], Callable[[float], None]))
@@ -550,8 +550,8 @@ Parameter name is not required to be the same for positional-only parameters at
 ```py
 def int_param_different_name(b: int, /) -> None: ...
 
-static_assert(is_subtype_of(CallableTypeFromFunction[int_param], CallableTypeFromFunction[int_param_different_name]))
-static_assert(is_subtype_of(CallableTypeFromFunction[int_param_different_name], CallableTypeFromFunction[int_param]))
+static_assert(is_subtype_of(CallableTypeOf[int_param], CallableTypeOf[int_param_different_name]))
+static_assert(is_subtype_of(CallableTypeOf[int_param_different_name], CallableTypeOf[int_param]))
 ```
 
 Multiple positional-only parameters are checked in order:
@@ -560,8 +560,8 @@ Multiple positional-only parameters are checked in order:
 def multi_param1(a: float, b: int, c: str, /) -> None: ...
 def multi_param2(b: int, c: bool, a: str, /) -> None: ...
 
-static_assert(is_subtype_of(CallableTypeFromFunction[multi_param1], CallableTypeFromFunction[multi_param2]))
-static_assert(not is_subtype_of(CallableTypeFromFunction[multi_param2], CallableTypeFromFunction[multi_param1]))
+static_assert(is_subtype_of(CallableTypeOf[multi_param1], CallableTypeOf[multi_param2]))
+static_assert(not is_subtype_of(CallableTypeOf[multi_param2], CallableTypeOf[multi_param1]))
 
 static_assert(is_subtype_of(TypeOf[multi_param1], Callable[[float, int, str], None]))
 
@@ -575,17 +575,17 @@ corresponding position in the supertype does not need to have a default value.
 
 ```py
 from typing import Callable
-from knot_extensions import CallableTypeFromFunction, is_subtype_of, static_assert, TypeOf
+from knot_extensions import CallableTypeOf, is_subtype_of, static_assert, TypeOf
 
 def float_with_default(a: float = 1, /) -> None: ...
 def int_with_default(a: int = 1, /) -> None: ...
 def int_without_default(a: int, /) -> None: ...
 
-static_assert(is_subtype_of(CallableTypeFromFunction[float_with_default], CallableTypeFromFunction[int_with_default]))
-static_assert(not is_subtype_of(CallableTypeFromFunction[int_with_default], CallableTypeFromFunction[float_with_default]))
+static_assert(is_subtype_of(CallableTypeOf[float_with_default], CallableTypeOf[int_with_default]))
+static_assert(not is_subtype_of(CallableTypeOf[int_with_default], CallableTypeOf[float_with_default]))
 
-static_assert(is_subtype_of(CallableTypeFromFunction[int_with_default], CallableTypeFromFunction[int_without_default]))
-static_assert(not is_subtype_of(CallableTypeFromFunction[int_without_default], CallableTypeFromFunction[int_with_default]))
+static_assert(is_subtype_of(CallableTypeOf[int_with_default], CallableTypeOf[int_without_default]))
+static_assert(not is_subtype_of(CallableTypeOf[int_without_default], CallableTypeOf[int_with_default]))
 
 static_assert(is_subtype_of(TypeOf[int_with_default], Callable[[int], None]))
 static_assert(is_subtype_of(TypeOf[int_with_default], Callable[[], None]))
@@ -600,9 +600,9 @@ As the parameter itself is optional, it can be omitted in the supertype:
 ```py
 def empty() -> None: ...
 
-static_assert(is_subtype_of(CallableTypeFromFunction[int_with_default], CallableTypeFromFunction[empty]))
-static_assert(not is_subtype_of(CallableTypeFromFunction[int_without_default], CallableTypeFromFunction[empty]))
-static_assert(not is_subtype_of(CallableTypeFromFunction[empty], CallableTypeFromFunction[int_with_default]))
+static_assert(is_subtype_of(CallableTypeOf[int_with_default], CallableTypeOf[empty]))
+static_assert(not is_subtype_of(CallableTypeOf[int_without_default], CallableTypeOf[empty]))
+static_assert(not is_subtype_of(CallableTypeOf[empty], CallableTypeOf[int_with_default]))
 ```
 
 The subtype can include any number of positional-only parameters as long as they have the default
@@ -611,8 +611,8 @@ value:
 ```py
 def multi_param(a: float = 1, b: int = 2, c: str = "3", /) -> None: ...
 
-static_assert(is_subtype_of(CallableTypeFromFunction[multi_param], CallableTypeFromFunction[empty]))
-static_assert(not is_subtype_of(CallableTypeFromFunction[empty], CallableTypeFromFunction[multi_param]))
+static_assert(is_subtype_of(CallableTypeOf[multi_param], CallableTypeOf[empty]))
+static_assert(not is_subtype_of(CallableTypeOf[empty], CallableTypeOf[multi_param]))
 ```
 
 #### Positional-only with other kinds
@@ -621,7 +621,7 @@ If a parameter is declared as positional-only, then the corresponding parameter
 cannot be any other parameter kind.
 
 ```py
-from knot_extensions import CallableTypeFromFunction, is_subtype_of, static_assert
+from knot_extensions import CallableTypeOf, is_subtype_of, static_assert
 
 def positional_only(a: int, /) -> None: ...
 def standard(a: int) -> None: ...
@@ -629,10 +629,10 @@ def keyword_only(*, a: int) -> None: ...
 def variadic(*a: int) -> None: ...
 def keyword_variadic(**a: int) -> None: ...
 
-static_assert(not is_subtype_of(CallableTypeFromFunction[positional_only], CallableTypeFromFunction[standard]))
-static_assert(not is_subtype_of(CallableTypeFromFunction[positional_only], CallableTypeFromFunction[keyword_only]))
-static_assert(not is_subtype_of(CallableTypeFromFunction[positional_only], CallableTypeFromFunction[variadic]))
-static_assert(not is_subtype_of(CallableTypeFromFunction[positional_only], CallableTypeFromFunction[keyword_variadic]))
+static_assert(not is_subtype_of(CallableTypeOf[positional_only], CallableTypeOf[standard]))
+static_assert(not is_subtype_of(CallableTypeOf[positional_only], CallableTypeOf[keyword_only]))
+static_assert(not is_subtype_of(CallableTypeOf[positional_only], CallableTypeOf[variadic]))
+static_assert(not is_subtype_of(CallableTypeOf[positional_only], CallableTypeOf[keyword_variadic]))
 ```
 
 #### Standard
@@ -642,13 +642,13 @@ A standard parameter is either a positional or a keyword parameter.
 Unlike positional-only parameters, standard parameters should have the same name in the subtype.
 
 ```py
-from knot_extensions import CallableTypeFromFunction, is_subtype_of, static_assert
+from knot_extensions import CallableTypeOf, is_subtype_of, static_assert
 
 def int_param_a(a: int) -> None: ...
 def int_param_b(b: int) -> None: ...
 
-static_assert(not is_subtype_of(CallableTypeFromFunction[int_param_a], CallableTypeFromFunction[int_param_b]))
-static_assert(not is_subtype_of(CallableTypeFromFunction[int_param_b], CallableTypeFromFunction[int_param_a]))
+static_assert(not is_subtype_of(CallableTypeOf[int_param_a], CallableTypeOf[int_param_b]))
+static_assert(not is_subtype_of(CallableTypeOf[int_param_b], CallableTypeOf[int_param_a]))
 ```
 
 Apart from the name, it behaves the same as positional-only parameters.
@@ -657,8 +657,8 @@ Apart from the name, it behaves the same as positional-only parameters.
 def float_param(a: float) -> None: ...
 def int_param(a: int) -> None: ...
 
-static_assert(is_subtype_of(CallableTypeFromFunction[float_param], CallableTypeFromFunction[int_param]))
-static_assert(not is_subtype_of(CallableTypeFromFunction[int_param], CallableTypeFromFunction[float_param]))
+static_assert(is_subtype_of(CallableTypeOf[float_param], CallableTypeOf[int_param]))
+static_assert(not is_subtype_of(CallableTypeOf[int_param], CallableTypeOf[float_param]))
 ```
 
 With the same rules for default values as well.
@@ -668,14 +668,14 @@ def float_with_default(a: float = 1) -> None: ...
 def int_with_default(a: int = 1) -> None: ...
 def empty() -> None: ...
 
-static_assert(is_subtype_of(CallableTypeFromFunction[float_with_default], CallableTypeFromFunction[int_with_default]))
-static_assert(not is_subtype_of(CallableTypeFromFunction[int_with_default], CallableTypeFromFunction[float_with_default]))
+static_assert(is_subtype_of(CallableTypeOf[float_with_default], CallableTypeOf[int_with_default]))
+static_assert(not is_subtype_of(CallableTypeOf[int_with_default], CallableTypeOf[float_with_default]))
 
-static_assert(is_subtype_of(CallableTypeFromFunction[int_with_default], CallableTypeFromFunction[int_param]))
-static_assert(not is_subtype_of(CallableTypeFromFunction[int_param], CallableTypeFromFunction[int_with_default]))
+static_assert(is_subtype_of(CallableTypeOf[int_with_default], CallableTypeOf[int_param]))
+static_assert(not is_subtype_of(CallableTypeOf[int_param], CallableTypeOf[int_with_default]))
 
-static_assert(is_subtype_of(CallableTypeFromFunction[int_with_default], CallableTypeFromFunction[empty]))
-static_assert(not is_subtype_of(CallableTypeFromFunction[empty], CallableTypeFromFunction[int_with_default]))
+static_assert(is_subtype_of(CallableTypeOf[int_with_default], CallableTypeOf[empty]))
+static_assert(not is_subtype_of(CallableTypeOf[empty], CallableTypeOf[int_with_default]))
 ```
 
 Multiple standard parameters are checked in order along with their names:
@@ -684,8 +684,8 @@ Multiple standard parameters are checked in order along with their names:
 def multi_param1(a: float, b: int, c: str) -> None: ...
 def multi_param2(a: int, b: bool, c: str) -> None: ...
 
-static_assert(is_subtype_of(CallableTypeFromFunction[multi_param1], CallableTypeFromFunction[multi_param2]))
-static_assert(not is_subtype_of(CallableTypeFromFunction[multi_param2], CallableTypeFromFunction[multi_param1]))
+static_assert(is_subtype_of(CallableTypeOf[multi_param1], CallableTypeOf[multi_param2]))
+static_assert(not is_subtype_of(CallableTypeOf[multi_param2], CallableTypeOf[multi_param1]))
 ```
 
 The subtype can include as many standard parameters as long as they have the default value:
@@ -693,8 +693,8 @@ The subtype can include as many standard parameters as long as they have the def
 ```py
 def multi_param_default(a: float = 1, b: int = 2, c: str = "s") -> None: ...
 
-static_assert(is_subtype_of(CallableTypeFromFunction[multi_param_default], CallableTypeFromFunction[empty]))
-static_assert(not is_subtype_of(CallableTypeFromFunction[empty], CallableTypeFromFunction[multi_param_default]))
+static_assert(is_subtype_of(CallableTypeOf[multi_param_default], CallableTypeOf[empty]))
+static_assert(not is_subtype_of(CallableTypeOf[empty], CallableTypeOf[multi_param_default]))
 ```
 
 #### Standard with keyword-only
@@ -704,26 +704,26 @@ parameter in the subtype with the same name. This is because a standard paramete
 than a keyword-only parameter.
 
 ```py
-from knot_extensions import CallableTypeFromFunction, is_subtype_of, static_assert
+from knot_extensions import CallableTypeOf, is_subtype_of, static_assert
 
 def standard_a(a: int) -> None: ...
 def keyword_b(*, b: int) -> None: ...
 
 # The name of the parameters are different
-static_assert(not is_subtype_of(CallableTypeFromFunction[standard_a], CallableTypeFromFunction[keyword_b]))
+static_assert(not is_subtype_of(CallableTypeOf[standard_a], CallableTypeOf[keyword_b]))
 
 def standard_float(a: float) -> None: ...
 def keyword_int(*, a: int) -> None: ...
 
 # Here, the name of the parameters are the same
-static_assert(is_subtype_of(CallableTypeFromFunction[standard_float], CallableTypeFromFunction[keyword_int]))
+static_assert(is_subtype_of(CallableTypeOf[standard_float], CallableTypeOf[keyword_int]))
 
 def standard_with_default(a: int = 1) -> None: ...
 def keyword_with_default(*, a: int = 1) -> None: ...
 def empty() -> None: ...
 
-static_assert(is_subtype_of(CallableTypeFromFunction[standard_with_default], CallableTypeFromFunction[keyword_with_default]))
-static_assert(is_subtype_of(CallableTypeFromFunction[standard_with_default], CallableTypeFromFunction[empty]))
+static_assert(is_subtype_of(CallableTypeOf[standard_with_default], CallableTypeOf[keyword_with_default]))
+static_assert(is_subtype_of(CallableTypeOf[standard_with_default], CallableTypeOf[empty]))
 ```
 
 The position of the keyword-only parameters does not matter:
@@ -732,7 +732,7 @@ The position of the keyword-only parameters does not matter:
 def multi_standard(a: float, b: int, c: str) -> None: ...
 def multi_keyword(*, b: bool, c: str, a: int) -> None: ...
 
-static_assert(is_subtype_of(CallableTypeFromFunction[multi_standard], CallableTypeFromFunction[multi_keyword]))
+static_assert(is_subtype_of(CallableTypeOf[multi_standard], CallableTypeOf[multi_keyword]))
 ```
 
 #### Standard with positional-only
@@ -742,25 +742,25 @@ parameter in the subtype at the same position. This is because a standard parame
 than a positional-only parameter.
 
 ```py
-from knot_extensions import CallableTypeFromFunction, is_subtype_of, static_assert
+from knot_extensions import CallableTypeOf, is_subtype_of, static_assert
 
 def standard_a(a: int) -> None: ...
 def positional_b(b: int, /) -> None: ...
 
 # The names are not important in this context
-static_assert(is_subtype_of(CallableTypeFromFunction[standard_a], CallableTypeFromFunction[positional_b]))
+static_assert(is_subtype_of(CallableTypeOf[standard_a], CallableTypeOf[positional_b]))
 
 def standard_float(a: float) -> None: ...
 def positional_int(a: int, /) -> None: ...
 
-static_assert(is_subtype_of(CallableTypeFromFunction[standard_float], CallableTypeFromFunction[positional_int]))
+static_assert(is_subtype_of(CallableTypeOf[standard_float], CallableTypeOf[positional_int]))
 
 def standard_with_default(a: int = 1) -> None: ...
 def positional_with_default(a: int = 1, /) -> None: ...
 def empty() -> None: ...
 
-static_assert(is_subtype_of(CallableTypeFromFunction[standard_with_default], CallableTypeFromFunction[positional_with_default]))
-static_assert(is_subtype_of(CallableTypeFromFunction[standard_with_default], CallableTypeFromFunction[empty]))
+static_assert(is_subtype_of(CallableTypeOf[standard_with_default], CallableTypeOf[positional_with_default]))
+static_assert(is_subtype_of(CallableTypeOf[standard_with_default], CallableTypeOf[empty]))
 ```
 
 The position of the positional-only parameters matter:
@@ -772,8 +772,8 @@ def multi_positional1(b: int, c: bool, a: str, /) -> None: ...
 # Here, the type of the parameter `a` makes the subtype relation invalid
 def multi_positional2(b: int, a: float, c: str, /) -> None: ...
 
-static_assert(is_subtype_of(CallableTypeFromFunction[multi_standard], CallableTypeFromFunction[multi_positional1]))
-static_assert(not is_subtype_of(CallableTypeFromFunction[multi_standard], CallableTypeFromFunction[multi_positional2]))
+static_assert(is_subtype_of(CallableTypeOf[multi_standard], CallableTypeOf[multi_positional1]))
+static_assert(not is_subtype_of(CallableTypeOf[multi_standard], CallableTypeOf[multi_positional2]))
 ```
 
 #### Standard with variadic
@@ -782,14 +782,14 @@ A variadic or keyword-variadic parameter in the supertype cannot be substituted
 parameter in the subtype.
 
 ```py
-from knot_extensions import CallableTypeFromFunction, is_subtype_of, static_assert
+from knot_extensions import CallableTypeOf, is_subtype_of, static_assert
 
 def standard(a: int) -> None: ...
 def variadic(*a: int) -> None: ...
 def keyword_variadic(**a: int) -> None: ...
 
-static_assert(not is_subtype_of(CallableTypeFromFunction[standard], CallableTypeFromFunction[variadic]))
-static_assert(not is_subtype_of(CallableTypeFromFunction[standard], CallableTypeFromFunction[keyword_variadic]))
+static_assert(not is_subtype_of(CallableTypeOf[standard], CallableTypeOf[variadic]))
+static_assert(not is_subtype_of(CallableTypeOf[standard], CallableTypeOf[keyword_variadic]))
 ```
 
 #### Variadic
@@ -797,13 +797,13 @@ static_assert(not is_subtype_of(CallableTypeFromFunction[standard], CallableType
 The name of the variadic parameter does not need to be the same in the subtype.
 
 ```py
-from knot_extensions import CallableTypeFromFunction, is_subtype_of, static_assert
+from knot_extensions import CallableTypeOf, is_subtype_of, static_assert
 
 def variadic_float(*args2: float) -> None: ...
 def variadic_int(*args1: int) -> None: ...
 
-static_assert(is_subtype_of(CallableTypeFromFunction[variadic_float], CallableTypeFromFunction[variadic_int]))
-static_assert(not is_subtype_of(CallableTypeFromFunction[variadic_int], CallableTypeFromFunction[variadic_float]))
+static_assert(is_subtype_of(CallableTypeOf[variadic_float], CallableTypeOf[variadic_int]))
+static_assert(not is_subtype_of(CallableTypeOf[variadic_int], CallableTypeOf[variadic_float]))
 ```
 
 The variadic parameter does not need to be present in the supertype:
@@ -811,8 +811,8 @@ The variadic parameter does not need to be present in the supertype:
 ```py
 def empty() -> None: ...
 
-static_assert(is_subtype_of(CallableTypeFromFunction[variadic_int], CallableTypeFromFunction[empty]))
-static_assert(not is_subtype_of(CallableTypeFromFunction[empty], CallableTypeFromFunction[variadic_int]))
+static_assert(is_subtype_of(CallableTypeOf[variadic_int], CallableTypeOf[empty]))
+static_assert(not is_subtype_of(CallableTypeOf[empty], CallableTypeOf[variadic_int]))
 ```
 
 #### Variadic with positional-only
@@ -821,7 +821,7 @@ If the subtype has a variadic parameter then any unmatched positional-only param
 supertype should be checked against the variadic parameter.
 
 ```py
-from knot_extensions import CallableTypeFromFunction, is_subtype_of, static_assert
+from knot_extensions import CallableTypeOf, is_subtype_of, static_assert
 
 def variadic(a: int, /, *args: float) -> None: ...
 
@@ -831,8 +831,8 @@ def positional_only(a: int, b: float, c: int, /) -> None: ...
 # Here, the parameter `b` is unmatched and there's also a variadic parameter
 def positional_variadic(a: int, b: float, /, *args: int) -> None: ...
 
-static_assert(is_subtype_of(CallableTypeFromFunction[variadic], CallableTypeFromFunction[positional_only]))
-static_assert(is_subtype_of(CallableTypeFromFunction[variadic], CallableTypeFromFunction[positional_variadic]))
+static_assert(is_subtype_of(CallableTypeOf[variadic], CallableTypeOf[positional_only]))
+static_assert(is_subtype_of(CallableTypeOf[variadic], CallableTypeOf[positional_variadic]))
 ```
 
 #### Variadic with other kinds
@@ -841,7 +841,7 @@ Variadic parameter in a subtype can only be used to match against an unmatched p
 parameters from the supertype, not any other parameter kind.
 
 ```py
-from knot_extensions import CallableTypeFromFunction, is_subtype_of, static_assert
+from knot_extensions import CallableTypeOf, is_subtype_of, static_assert
 
 def variadic(*args: int) -> None: ...
 
@@ -853,9 +853,9 @@ def standard(a: int, b: float, /, c: int) -> None: ...
 def keyword_only(a: int, /, *, b: int) -> None: ...
 def keyword_variadic(a: int, /, **kwargs: int) -> None: ...
 
-static_assert(not is_subtype_of(CallableTypeFromFunction[variadic], CallableTypeFromFunction[standard]))
-static_assert(not is_subtype_of(CallableTypeFromFunction[variadic], CallableTypeFromFunction[keyword_only]))
-static_assert(not is_subtype_of(CallableTypeFromFunction[variadic], CallableTypeFromFunction[keyword_variadic]))
+static_assert(not is_subtype_of(CallableTypeOf[variadic], CallableTypeOf[standard]))
+static_assert(not is_subtype_of(CallableTypeOf[variadic], CallableTypeOf[keyword_only]))
+static_assert(not is_subtype_of(CallableTypeOf[variadic], CallableTypeOf[keyword_variadic]))
 ```
 
 #### Keyword-only
@@ -863,15 +863,15 @@ static_assert(not is_subtype_of(CallableTypeFromFunction[variadic], CallableType
 For keyword-only parameters, the name should be the same:
 
 ```py
-from knot_extensions import CallableTypeFromFunction, is_subtype_of, static_assert
+from knot_extensions import CallableTypeOf, is_subtype_of, static_assert
 
 def keyword_int(*, a: int) -> None: ...
 def keyword_float(*, a: float) -> None: ...
 def keyword_b(*, b: int) -> None: ...
 
-static_assert(is_subtype_of(CallableTypeFromFunction[keyword_float], CallableTypeFromFunction[keyword_int]))
-static_assert(not is_subtype_of(CallableTypeFromFunction[keyword_int], CallableTypeFromFunction[keyword_float]))
-static_assert(not is_subtype_of(CallableTypeFromFunction[keyword_int], CallableTypeFromFunction[keyword_b]))
+static_assert(is_subtype_of(CallableTypeOf[keyword_float], CallableTypeOf[keyword_int]))
+static_assert(not is_subtype_of(CallableTypeOf[keyword_int], CallableTypeOf[keyword_float]))
+static_assert(not is_subtype_of(CallableTypeOf[keyword_int], CallableTypeOf[keyword_b]))
 ```
 
 But, the order of the keyword-only parameters is not required to be the same:
@@ -880,28 +880,28 @@ But, the order of the keyword-only parameters is not required to be the same:
 def keyword_ab(*, a: float, b: float) -> None: ...
 def keyword_ba(*, b: int, a: int) -> None: ...
 
-static_assert(is_subtype_of(CallableTypeFromFunction[keyword_ab], CallableTypeFromFunction[keyword_ba]))
-static_assert(not is_subtype_of(CallableTypeFromFunction[keyword_ba], CallableTypeFromFunction[keyword_ab]))
+static_assert(is_subtype_of(CallableTypeOf[keyword_ab], CallableTypeOf[keyword_ba]))
+static_assert(not is_subtype_of(CallableTypeOf[keyword_ba], CallableTypeOf[keyword_ab]))
 ```
 
 #### Keyword-only with default
 
 ```py
-from knot_extensions import CallableTypeFromFunction, is_subtype_of, static_assert
+from knot_extensions import CallableTypeOf, is_subtype_of, static_assert
 
 def float_with_default(*, a: float = 1) -> None: ...
 def int_with_default(*, a: int = 1) -> None: ...
 def int_keyword(*, a: int) -> None: ...
 def empty() -> None: ...
 
-static_assert(is_subtype_of(CallableTypeFromFunction[float_with_default], CallableTypeFromFunction[int_with_default]))
-static_assert(not is_subtype_of(CallableTypeFromFunction[int_with_default], CallableTypeFromFunction[float_with_default]))
+static_assert(is_subtype_of(CallableTypeOf[float_with_default], CallableTypeOf[int_with_default]))
+static_assert(not is_subtype_of(CallableTypeOf[int_with_default], CallableTypeOf[float_with_default]))
 
-static_assert(is_subtype_of(CallableTypeFromFunction[int_with_default], CallableTypeFromFunction[int_keyword]))
-static_assert(not is_subtype_of(CallableTypeFromFunction[int_keyword], CallableTypeFromFunction[int_with_default]))
+static_assert(is_subtype_of(CallableTypeOf[int_with_default], CallableTypeOf[int_keyword]))
+static_assert(not is_subtype_of(CallableTypeOf[int_keyword], CallableTypeOf[int_with_default]))
 
-static_assert(is_subtype_of(CallableTypeFromFunction[int_with_default], CallableTypeFromFunction[empty]))
-static_assert(not is_subtype_of(CallableTypeFromFunction[empty], CallableTypeFromFunction[int_with_default]))
+static_assert(is_subtype_of(CallableTypeOf[int_with_default], CallableTypeOf[empty]))
+static_assert(not is_subtype_of(CallableTypeOf[empty], CallableTypeOf[int_with_default]))
 ```
 
 Keyword-only parameters with default values can be mixed with the ones without default values in any
@@ -911,20 +911,20 @@ order:
 # A keyword-only parameter with a default value follows the one without a default value (it's valid)
 def mixed(*, b: int = 1, a: int) -> None: ...
 
-static_assert(is_subtype_of(CallableTypeFromFunction[mixed], CallableTypeFromFunction[int_keyword]))
-static_assert(not is_subtype_of(CallableTypeFromFunction[int_keyword], CallableTypeFromFunction[mixed]))
+static_assert(is_subtype_of(CallableTypeOf[mixed], CallableTypeOf[int_keyword]))
+static_assert(not is_subtype_of(CallableTypeOf[int_keyword], CallableTypeOf[mixed]))
 ```
 
 #### Keyword-only with standard
 
 ```py
-from knot_extensions import CallableTypeFromFunction, is_subtype_of, static_assert
+from knot_extensions import CallableTypeOf, is_subtype_of, static_assert
 
 def keywords1(*, a: int, b: int) -> None: ...
 def standard(b: float, a: float) -> None: ...
 
-static_assert(not is_subtype_of(CallableTypeFromFunction[keywords1], CallableTypeFromFunction[standard]))
-static_assert(is_subtype_of(CallableTypeFromFunction[standard], CallableTypeFromFunction[keywords1]))
+static_assert(not is_subtype_of(CallableTypeOf[keywords1], CallableTypeOf[standard]))
+static_assert(is_subtype_of(CallableTypeOf[standard], CallableTypeOf[keywords1]))
 ```
 
 The subtype can include additional standard parameters as long as it has the default value:
@@ -933,8 +933,8 @@ The subtype can include additional standard parameters as long as it has the def
 def standard_with_default(b: float, a: float, c: float = 1) -> None: ...
 def standard_without_default(b: float, a: float, c: float) -> None: ...
 
-static_assert(not is_subtype_of(CallableTypeFromFunction[standard_without_default], CallableTypeFromFunction[keywords1]))
-static_assert(is_subtype_of(CallableTypeFromFunction[standard_with_default], CallableTypeFromFunction[keywords1]))
+static_assert(not is_subtype_of(CallableTypeOf[standard_without_default], CallableTypeOf[keywords1]))
+static_assert(is_subtype_of(CallableTypeOf[standard_with_default], CallableTypeOf[keywords1]))
 ```
 
 Here, we mix keyword-only parameters with standard parameters:
@@ -943,8 +943,8 @@ Here, we mix keyword-only parameters with standard parameters:
 def keywords2(*, a: int, c: int, b: int) -> None: ...
 def mixed(b: float, a: float, *, c: float) -> None: ...
 
-static_assert(not is_subtype_of(CallableTypeFromFunction[keywords2], CallableTypeFromFunction[mixed]))
-static_assert(is_subtype_of(CallableTypeFromFunction[mixed], CallableTypeFromFunction[keywords2]))
+static_assert(not is_subtype_of(CallableTypeOf[keywords2], CallableTypeOf[mixed]))
+static_assert(is_subtype_of(CallableTypeOf[mixed], CallableTypeOf[keywords2]))
 ```
 
 But, we shouldn't consider any unmatched positional-only parameters:
@@ -952,7 +952,7 @@ But, we shouldn't consider any unmatched positional-only parameters:
 ```py
 def mixed_positional(b: float, /, a: float, *, c: float) -> None: ...
 
-static_assert(not is_subtype_of(CallableTypeFromFunction[mixed_positional], CallableTypeFromFunction[keywords2]))
+static_assert(not is_subtype_of(CallableTypeOf[mixed_positional], CallableTypeOf[keywords2]))
 ```
 
 But, an unmatched variadic parameter is still valid:
@@ -960,7 +960,7 @@ But, an unmatched variadic parameter is still valid:
 ```py
 def mixed_variadic(*args: float, a: float, b: float, c: float, **kwargs: float) -> None: ...
 
-static_assert(is_subtype_of(CallableTypeFromFunction[mixed_variadic], CallableTypeFromFunction[keywords2]))
+static_assert(is_subtype_of(CallableTypeOf[mixed_variadic], CallableTypeOf[keywords2]))
 ```
 
 #### Keyword-variadic
@@ -968,13 +968,13 @@ static_assert(is_subtype_of(CallableTypeFromFunction[mixed_variadic], CallableTy
 The name of the keyword-variadic parameter does not need to be the same in the subtype.
 
 ```py
-from knot_extensions import CallableTypeFromFunction, is_subtype_of, static_assert
+from knot_extensions import CallableTypeOf, is_subtype_of, static_assert
 
 def kwargs_float(**kwargs2: float) -> None: ...
 def kwargs_int(**kwargs1: int) -> None: ...
 
-static_assert(is_subtype_of(CallableTypeFromFunction[kwargs_float], CallableTypeFromFunction[kwargs_int]))
-static_assert(not is_subtype_of(CallableTypeFromFunction[kwargs_int], CallableTypeFromFunction[kwargs_float]))
+static_assert(is_subtype_of(CallableTypeOf[kwargs_float], CallableTypeOf[kwargs_int]))
+static_assert(not is_subtype_of(CallableTypeOf[kwargs_int], CallableTypeOf[kwargs_float]))
 ```
 
 A variadic parameter can be omitted in the subtype:
@@ -982,8 +982,8 @@ A variadic parameter can be omitted in the subtype:
 ```py
 def empty() -> None: ...
 
-static_assert(is_subtype_of(CallableTypeFromFunction[kwargs_int], CallableTypeFromFunction[empty]))
-static_assert(not is_subtype_of(CallableTypeFromFunction[empty], CallableTypeFromFunction[kwargs_int]))
+static_assert(is_subtype_of(CallableTypeOf[kwargs_int], CallableTypeOf[empty]))
+static_assert(not is_subtype_of(CallableTypeOf[empty], CallableTypeOf[kwargs_int]))
 ```
 
 #### Keyword-variadic with keyword-only
@@ -992,14 +992,14 @@ If the subtype has a keyword-variadic parameter then any unmatched keyword-only
 supertype should be checked against the keyword-variadic parameter.
 
 ```py
-from knot_extensions import CallableTypeFromFunction, is_subtype_of, static_assert
+from knot_extensions import CallableTypeOf, is_subtype_of, static_assert
 
 def kwargs(**kwargs: float) -> None: ...
 def keyword_only(*, a: int, b: float, c: bool) -> None: ...
 def keyword_variadic(*, a: int, **kwargs: int) -> None: ...
 
-static_assert(is_subtype_of(CallableTypeFromFunction[kwargs], CallableTypeFromFunction[keyword_only]))
-static_assert(is_subtype_of(CallableTypeFromFunction[kwargs], CallableTypeFromFunction[keyword_variadic]))
+static_assert(is_subtype_of(CallableTypeOf[kwargs], CallableTypeOf[keyword_only]))
+static_assert(is_subtype_of(CallableTypeOf[kwargs], CallableTypeOf[keyword_variadic]))
 ```
 
 This is valid only for keyword-only parameters, not any other parameter kind:
@@ -1010,8 +1010,8 @@ def mixed1(a: int, *, b: int) -> None: ...
 # Same as above but with the default value
 def mixed2(a: int = 1, *, b: int) -> None: ...
 
-static_assert(not is_subtype_of(CallableTypeFromFunction[kwargs], CallableTypeFromFunction[mixed1]))
-static_assert(not is_subtype_of(CallableTypeFromFunction[kwargs], CallableTypeFromFunction[mixed2]))
+static_assert(not is_subtype_of(CallableTypeOf[kwargs], CallableTypeOf[mixed1]))
+static_assert(not is_subtype_of(CallableTypeOf[kwargs], CallableTypeOf[mixed2]))
 ```
 
 #### Empty
@@ -1020,25 +1020,25 @@ When the supertype has an empty list of parameters, then the subtype can have an
 as long as they contain the default values for non-variadic parameters.
 
 ```py
-from knot_extensions import CallableTypeFromFunction, is_subtype_of, static_assert
+from knot_extensions import CallableTypeOf, is_subtype_of, static_assert
 
 def empty() -> None: ...
 def mixed(a: int = 1, /, b: int = 2, *args: int, c: int = 3, **kwargs: int) -> None: ...
 
-static_assert(is_subtype_of(CallableTypeFromFunction[mixed], CallableTypeFromFunction[empty]))
-static_assert(not is_subtype_of(CallableTypeFromFunction[empty], CallableTypeFromFunction[mixed]))
+static_assert(is_subtype_of(CallableTypeOf[mixed], CallableTypeOf[empty]))
+static_assert(not is_subtype_of(CallableTypeOf[empty], CallableTypeOf[mixed]))
 ```
 
 #### Object
 
 ```py
-from knot_extensions import CallableTypeFromFunction, is_subtype_of, static_assert, TypeOf
+from knot_extensions import CallableTypeOf, is_subtype_of, static_assert, TypeOf
 from typing import Callable
 
 def f1(a: int, b: str, /, *c: float, d: int = 1, **e: float) -> None: ...
 
-static_assert(is_subtype_of(CallableTypeFromFunction[f1], object))
-static_assert(not is_subtype_of(object, CallableTypeFromFunction[f1]))
+static_assert(is_subtype_of(CallableTypeOf[f1], object))
+static_assert(not is_subtype_of(object, CallableTypeOf[f1]))
 
 def _(
     f3: Callable[[int, str], None],
diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs
index 164f3849f1140b..7ecd3d42dacc9b 100644
--- a/crates/red_knot_python_semantic/src/types.rs
+++ b/crates/red_knot_python_semantic/src/types.rs
@@ -3021,7 +3021,7 @@ impl<'db> Type<'db> {
                 | KnownInstanceType::TypeIs
                 | KnownInstanceType::TypeGuard
                 | KnownInstanceType::Unpack
-                | KnownInstanceType::CallableTypeFromFunction => Err(InvalidTypeExpressionError {
+                | KnownInstanceType::CallableTypeOf => Err(InvalidTypeExpressionError {
                     invalid_expressions: smallvec::smallvec![
                         InvalidTypeExpression::RequiresOneArgument(*self)
                     ],
@@ -4127,7 +4127,7 @@ impl<'db> FunctionType<'db> {
 
     /// Convert the `FunctionType` into a [`Type::Callable`].
     ///
-    /// This powers the `CallableTypeFromFunction` special form from the `knot_extensions` module.
+    /// This powers the `CallableTypeOf` special form from the `knot_extensions` module.
     pub(crate) fn into_callable_type(self, db: &'db dyn Db) -> Type<'db> {
         Type::Callable(CallableType::General(GeneralCallableType::new(
             db,
diff --git a/crates/red_knot_python_semantic/src/types/class.rs b/crates/red_knot_python_semantic/src/types/class.rs
index 156b521c20613b..85bf0a824e7832 100644
--- a/crates/red_knot_python_semantic/src/types/class.rs
+++ b/crates/red_knot_python_semantic/src/types/class.rs
@@ -1515,8 +1515,8 @@ pub enum KnownInstanceType<'db> {
     Intersection,
     /// The symbol `knot_extensions.TypeOf`
     TypeOf,
-    /// The symbol `knot_extensions.CallableTypeFromFunction`
-    CallableTypeFromFunction,
+    /// The symbol `knot_extensions.CallableTypeOf`
+    CallableTypeOf,
 
     // Various special forms, special aliases and type qualifiers that we don't yet understand
     // (all currently inferred as TODO in most contexts):
@@ -1579,7 +1579,7 @@ impl<'db> KnownInstanceType<'db> {
             | Self::Not
             | Self::Intersection
             | Self::TypeOf
-            | Self::CallableTypeFromFunction => Truthiness::AlwaysTrue,
+            | Self::CallableTypeOf => Truthiness::AlwaysTrue,
         }
     }
 
@@ -1626,7 +1626,7 @@ impl<'db> KnownInstanceType<'db> {
             Self::Not => "knot_extensions.Not",
             Self::Intersection => "knot_extensions.Intersection",
             Self::TypeOf => "knot_extensions.TypeOf",
-            Self::CallableTypeFromFunction => "knot_extensions.CallableTypeFromFunction",
+            Self::CallableTypeOf => "knot_extensions.CallableTypeOf",
         }
     }
 
@@ -1670,7 +1670,7 @@ impl<'db> KnownInstanceType<'db> {
             Self::TypeOf => KnownClass::SpecialForm,
             Self::Not => KnownClass::SpecialForm,
             Self::Intersection => KnownClass::SpecialForm,
-            Self::CallableTypeFromFunction => KnownClass::SpecialForm,
+            Self::CallableTypeOf => KnownClass::SpecialForm,
             Self::Unknown => KnownClass::Object,
             Self::AlwaysTruthy => KnownClass::Object,
             Self::AlwaysFalsy => KnownClass::Object,
@@ -1735,7 +1735,7 @@ impl<'db> KnownInstanceType<'db> {
             "Not" => Self::Not,
             "Intersection" => Self::Intersection,
             "TypeOf" => Self::TypeOf,
-            "CallableTypeFromFunction" => Self::CallableTypeFromFunction,
+            "CallableTypeOf" => Self::CallableTypeOf,
             _ => return None,
         };
 
@@ -1792,7 +1792,7 @@ impl<'db> KnownInstanceType<'db> {
             | Self::Not
             | Self::Intersection
             | Self::TypeOf
-            | Self::CallableTypeFromFunction => module.is_knot_extensions(),
+            | Self::CallableTypeOf => module.is_knot_extensions(),
         }
     }
 }
diff --git a/crates/red_knot_python_semantic/src/types/class_base.rs b/crates/red_knot_python_semantic/src/types/class_base.rs
index ab765985399777..2d4161dc335083 100644
--- a/crates/red_knot_python_semantic/src/types/class_base.rs
+++ b/crates/red_knot_python_semantic/src/types/class_base.rs
@@ -103,7 +103,7 @@ impl<'db> ClassBase<'db> {
                 | KnownInstanceType::Not
                 | KnownInstanceType::Intersection
                 | KnownInstanceType::TypeOf
-                | KnownInstanceType::CallableTypeFromFunction
+                | KnownInstanceType::CallableTypeOf
                 | KnownInstanceType::AlwaysTruthy
                 | KnownInstanceType::AlwaysFalsy => None,
                 KnownInstanceType::Unknown => Some(Self::unknown()),
diff --git a/crates/red_knot_python_semantic/src/types/infer.rs b/crates/red_knot_python_semantic/src/types/infer.rs
index e74435c22d690c..ae0a3f57e0c0bc 100644
--- a/crates/red_knot_python_semantic/src/types/infer.rs
+++ b/crates/red_knot_python_semantic/src/types/infer.rs
@@ -6769,7 +6769,7 @@ impl<'db> TypeInferenceBuilder<'db> {
                     argument_type
                 }
             },
-            KnownInstanceType::CallableTypeFromFunction => match arguments_slice {
+            KnownInstanceType::CallableTypeOf => match arguments_slice {
                 ast::Expr::Tuple(_) => {
                     self.context.report_lint(
                         &INVALID_TYPE_FORM,
@@ -6783,19 +6783,26 @@ impl<'db> TypeInferenceBuilder<'db> {
                 }
                 _ => {
                     let argument_type = self.infer_expression(arguments_slice);
-                    let Some(function_type) = argument_type.into_function_literal() else {
+                    let signatures = argument_type.signatures(db);
+
+                    // TODO overloads
+                    let Some(signature) = signatures.iter().flatten().next() else {
                         self.context.report_lint(
                             &INVALID_TYPE_FORM,
                             arguments_slice,
                             format_args!(
-                                "Expected the first argument to `{}` to be a function literal, but got `{}`",
+                                "Expected the first argument to `{}` to be a callable object, but got an object of type `{}`",
                                 known_instance.repr(db),
                                 argument_type.display(db)
                             ),
                         );
                         return Type::unknown();
                     };
-                    function_type.into_callable_type(db)
+
+                    Type::Callable(CallableType::General(GeneralCallableType::new(
+                        db,
+                        signature.clone(),
+                    )))
                 }
             },
 
diff --git a/crates/red_knot_python_semantic/src/types/type_ordering.rs b/crates/red_knot_python_semantic/src/types/type_ordering.rs
index c80bd55170526a..79f0abe1d4f7a5 100644
--- a/crates/red_knot_python_semantic/src/types/type_ordering.rs
+++ b/crates/red_knot_python_semantic/src/types/type_ordering.rs
@@ -230,8 +230,8 @@ pub(super) fn union_or_intersection_elements_ordering<'db>(
                 (KnownInstanceType::TypeOf, _) => Ordering::Less,
                 (_, KnownInstanceType::TypeOf) => Ordering::Greater,
 
-                (KnownInstanceType::CallableTypeFromFunction, _) => Ordering::Less,
-                (_, KnownInstanceType::CallableTypeFromFunction) => Ordering::Greater,
+                (KnownInstanceType::CallableTypeOf, _) => Ordering::Less,
+                (_, KnownInstanceType::CallableTypeOf) => Ordering::Greater,
 
                 (KnownInstanceType::Unpack, _) => Ordering::Less,
                 (_, KnownInstanceType::Unpack) => Ordering::Greater,
diff --git a/crates/red_knot_vendored/knot_extensions/knot_extensions.pyi b/crates/red_knot_vendored/knot_extensions/knot_extensions.pyi
index b9255fb87b3990..d7e69093d8ac0a 100644
--- a/crates/red_knot_vendored/knot_extensions/knot_extensions.pyi
+++ b/crates/red_knot_vendored/knot_extensions/knot_extensions.pyi
@@ -12,7 +12,7 @@ AlwaysFalsy = object()
 Not: _SpecialForm
 Intersection: _SpecialForm
 TypeOf: _SpecialForm
-CallableTypeFromFunction: _SpecialForm
+CallableTypeOf: _SpecialForm
 
 # Predicates on types
 #


Reviews:
user: login: carljm
body: This looks good to me! I would probably rename to `CallableTypeOf`?
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-28T19:42:19Z
user: login: dhruvmanila
body: Looks good 👍 
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-28T20:32:06Z
user: login: MatthewMckee4
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-29T13:02:57Z
user: login: carljm
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-30T18:04:36Z

Review comments:
diff_hunk: @@ -418,25 +418,41 @@ def f2() -> int:
 def f3(x: int, y: str) -> None:
     return
 
-# error: [invalid-type-form] "Special form `knot_extensions.CallableTypeFromFunction` expected exactly one type parameter"
-c1: CallableTypeFromFunction[f1, f2]
-# error: [invalid-type-form] "Expected the first argument to `knot_extensions.CallableTypeFromFunction` to be a function literal, but got `Literal[int]`"
-c2: CallableTypeFromFunction[int]
+# error: [invalid-type-form] "Special form `knot_extensions.CallableTypeOf` expected exactly one type parameter"
+c1: CallableTypeOf[f1, f2]
 
-# error: [invalid-type-form] "`knot_extensions.CallableTypeFromFunction` requires exactly one argument when used in a type expression"
-def f(x: CallableTypeFromFunction) -> None:
+# error: [invalid-type-form] "Expected the first argument to `knot_extensions.CallableTypeOf` to be a callable object, but got an object of type `Literal["foo"]`"
+c2: CallableTypeOf["foo"]
+
+# error: [invalid-type-form] "`knot_extensions.CallableTypeOf` requires exactly one argument when used in a type expression"
+def f(x: CallableTypeOf) -> None:
     reveal_type(x)  # revealed: Unknown
 ```
 
-Using it in annotation to reveal the signature of the function:
+Using it in annotation to reveal the signature of the callable object:
 
 ```py
+class Foo:
+    def __init__(self, x: int) -> None:
+        pass
+
+    def __call__(self, x: int) -> str:
+        return "foo"
+
 def _(
-    c1: CallableTypeFromFunction[f1],
-    c2: CallableTypeFromFunction[f2],
-    c3: CallableTypeFromFunction[f3],
+    c1: CallableTypeOf[f1],
+    c2: CallableTypeOf[f2],
+    c3: CallableTypeOf[f3],
+    c4: CallableTypeOf[Foo],
+    c5: CallableTypeOf[Foo(42).__call__],
 ) -> None:
     reveal_type(c1)  # revealed: () -> Unknown
     reveal_type(c2)  # revealed: () -> int
     reveal_type(c3)  # revealed: (x: int, y: str) -> None
+
+    # TODO: should be `(x: int) -> Foo`
+    reveal_type(c4)  # revealed: (...) -> Foo
+
+    # TODO: `self` is bound here; this should probably be `(x: int) -> str`?
+    reveal_type(c5)  #  revealed: (self, x: int) -> str
path: crates/red_knot_python_semantic/resources/mdtest/type_api.md
user: login: MatthewMckee4
body: Should c5 be stored as `(x: int) -> str`, or stored as `(self, x: int) -> str` and treated like `(x: int) -> str`?

@AlexWaygood @carljm 
created_at: 2025-03-29T13:02:57Z
author_association: CONTRIBUTOR
diff_hunk: @@ -418,25 +418,41 @@ def f2() -> int:
 def f3(x: int, y: str) -> None:
     return
 
-# error: [invalid-type-form] "Special form `knot_extensions.CallableTypeFromFunction` expected exactly one type parameter"
-c1: CallableTypeFromFunction[f1, f2]
-# error: [invalid-type-form] "Expected the first argument to `knot_extensions.CallableTypeFromFunction` to be a function literal, but got `Literal[int]`"
-c2: CallableTypeFromFunction[int]
+# error: [invalid-type-form] "Special form `knot_extensions.CallableTypeOf` expected exactly one type parameter"
+c1: CallableTypeOf[f1, f2]
 
-# error: [invalid-type-form] "`knot_extensions.CallableTypeFromFunction` requires exactly one argument when used in a type expression"
-def f(x: CallableTypeFromFunction) -> None:
+# error: [invalid-type-form] "Expected the first argument to `knot_extensions.CallableTypeOf` to be a callable object, but got an object of type `Literal["foo"]`"
+c2: CallableTypeOf["foo"]
+
+# error: [invalid-type-form] "`knot_extensions.CallableTypeOf` requires exactly one argument when used in a type expression"
+def f(x: CallableTypeOf) -> None:
     reveal_type(x)  # revealed: Unknown
 ```
 
-Using it in annotation to reveal the signature of the function:
+Using it in annotation to reveal the signature of the callable object:
 
 ```py
+class Foo:
+    def __init__(self, x: int) -> None:
+        pass
+
+    def __call__(self, x: int) -> str:
+        return "foo"
+
 def _(
-    c1: CallableTypeFromFunction[f1],
-    c2: CallableTypeFromFunction[f2],
-    c3: CallableTypeFromFunction[f3],
+    c1: CallableTypeOf[f1],
+    c2: CallableTypeOf[f2],
+    c3: CallableTypeOf[f3],
+    c4: CallableTypeOf[Foo],
+    c5: CallableTypeOf[Foo(42).__call__],
 ) -> None:
     reveal_type(c1)  # revealed: () -> Unknown
     reveal_type(c2)  # revealed: () -> int
     reveal_type(c3)  # revealed: (x: int, y: str) -> None
+
+    # TODO: should be `(x: int) -> Foo`
+    reveal_type(c4)  # revealed: (...) -> Foo
+
+    # TODO: `self` is bound here; this should probably be `(x: int) -> str`?
+    reveal_type(c5)  #  revealed: (self, x: int) -> str
path: crates/red_knot_python_semantic/resources/mdtest/type_api.md
user: login: carljm
body: Not entirely sure! I think this requires deeper exploration. Today we do the latter. There are a couple interesting questions that it connects to:

1) How do we handle equivalent callable types? If we aim for "equivalent callable types are always the same Salsa ID" then it should actually be stored as `(x: int) -> str`. But I don't think it's totally clear yet if that will be feasible.

2) When do we issue an error on calling a bound method with an annotation on the `self` argument that doesn't match the instance we accessed it from? Does it happen when accessing the bound method, or when calling it? The latter seems better (matches runtime better), but requires storing the bound self type and the full signature.

It seems likely that we need to support both representations and understand their equivalence. (But maybe our "general callable type" uses only the simpler representation?)
created_at: 2025-03-30T18:04:36Z
author_association: CONTRIBUTOR

PR comments:
user: login: github-actions[bot]
created_at: 2025-03-28T19:31:46Z
body: <!-- generated-comment mypy_primer -->
## `mypy_primer` results
No ecosystem changes detected ✅

author_association: CONTRIBUTOR
user: login: AlexWaygood
created_at: 2025-03-28T20:20:55Z
body: Renamed it to `CallableTypeOf`!
author_association: MEMBER