|
| 1 | +# Callable |
| 2 | + |
| 3 | +References: |
| 4 | + |
| 5 | +- <https://typing.readthedocs.io/en/latest/spec/callables.html#callable> |
| 6 | + |
| 7 | +TODO: Use `collections.abc` as importing from `typing` is deprecated but this requires support for |
| 8 | +`*` imports. See: <https://docs.python.org/3/library/typing.html#deprecated-aliases>. |
| 9 | + |
| 10 | +## Invalid forms |
| 11 | + |
| 12 | +The `Callable` special form requires _exactly_ two arguments where the first argument is either a |
| 13 | +parameter type list, parameter specification, `typing.Concatenate`, or `...` and the second argument |
| 14 | +is the return type. Here, we explore various invalid forms. |
| 15 | + |
| 16 | +### Empty |
| 17 | + |
| 18 | +A bare `Callable` without any type arguments: |
| 19 | + |
| 20 | +```py |
| 21 | +from typing import Callable |
| 22 | + |
| 23 | +def _(c: Callable): |
| 24 | + reveal_type(c) # revealed: (...) -> Unknown |
| 25 | +``` |
| 26 | + |
| 27 | +### Invalid parameter type argument |
| 28 | + |
| 29 | +When it's not a list: |
| 30 | + |
| 31 | +```py |
| 32 | +from typing import Callable |
| 33 | + |
| 34 | +# error: [invalid-type-form] "The first argument to `Callable` must be either a list of types, ParamSpec, Concatenate, or `...`" |
| 35 | +def _(c: Callable[int, str]): |
| 36 | + reveal_type(c) # revealed: (...) -> Unknown |
| 37 | +``` |
| 38 | + |
| 39 | +Or, when it's a literal type: |
| 40 | + |
| 41 | +```py |
| 42 | +# error: [invalid-type-form] "The first argument to `Callable` must be either a list of types, ParamSpec, Concatenate, or `...`" |
| 43 | +def _(c: Callable[42, str]): |
| 44 | + reveal_type(c) # revealed: (...) -> Unknown |
| 45 | +``` |
| 46 | + |
| 47 | +Or, when one of the parameter type is invalid in the list: |
| 48 | + |
| 49 | +```py |
| 50 | +def _(c: Callable[[int, 42, str, False], None]): |
| 51 | + # revealed: (int, @Todo(number literal in type expression), str, @Todo(boolean literal in type expression), /) -> None |
| 52 | + reveal_type(c) |
| 53 | +``` |
| 54 | + |
| 55 | +### Missing return type |
| 56 | + |
| 57 | +Using a parameter list: |
| 58 | + |
| 59 | +```py |
| 60 | +from typing import Callable |
| 61 | + |
| 62 | +# error: [invalid-type-form] "Special form `typing.Callable` expected exactly two arguments (parameter types and return type)" |
| 63 | +def _(c: Callable[[int, str]]): |
| 64 | + reveal_type(c) # revealed: (int, str, /) -> Unknown |
| 65 | +``` |
| 66 | + |
| 67 | +Or, an ellipsis: |
| 68 | + |
| 69 | +```py |
| 70 | +# error: [invalid-type-form] "Special form `typing.Callable` expected exactly two arguments (parameter types and return type)" |
| 71 | +def _(c: Callable[...]): |
| 72 | + reveal_type(c) # revealed: (...) -> Unknown |
| 73 | +``` |
| 74 | + |
| 75 | +### More than two arguments |
| 76 | + |
| 77 | +We can't reliably infer the callable type if there are more then 2 arguments because we don't know |
| 78 | +which argument corresponds to either the parameters or the return type. |
| 79 | + |
| 80 | +```py |
| 81 | +from typing import Callable |
| 82 | + |
| 83 | +# error: [invalid-type-form] "Special form `typing.Callable` expected exactly two arguments (parameter types and return type)" |
| 84 | +def _(c: Callable[[int], str, str]): |
| 85 | + reveal_type(c) # revealed: (...) -> Unknown |
| 86 | +``` |
| 87 | + |
| 88 | +## Simple |
| 89 | + |
| 90 | +A simple `Callable` with multiple parameters and a return type: |
| 91 | + |
| 92 | +```py |
| 93 | +from typing import Callable |
| 94 | + |
| 95 | +def _(c: Callable[[int, str], int]): |
| 96 | + reveal_type(c) # revealed: (int, str, /) -> int |
| 97 | +``` |
| 98 | + |
| 99 | +## Nested |
| 100 | + |
| 101 | +A nested `Callable` as one of the parameter types: |
| 102 | + |
| 103 | +```py |
| 104 | +from typing import Callable |
| 105 | + |
| 106 | +def _(c: Callable[[Callable[[int], str]], int]): |
| 107 | + reveal_type(c) # revealed: ((int, /) -> str, /) -> int |
| 108 | +``` |
| 109 | + |
| 110 | +And, as the return type: |
| 111 | + |
| 112 | +```py |
| 113 | +def _(c: Callable[[int, str], Callable[[int], int]]): |
| 114 | + reveal_type(c) # revealed: (int, str, /) -> (int, /) -> int |
| 115 | +``` |
| 116 | + |
| 117 | +## Gradual form |
| 118 | + |
| 119 | +The `Callable` special form supports the use of `...` in place of the list of parameter types. This |
| 120 | +is a [gradual form] indicating that the type is consistent with any input signature: |
| 121 | + |
| 122 | +```py |
| 123 | +from typing import Callable |
| 124 | + |
| 125 | +def gradual_form(c: Callable[..., str]): |
| 126 | + reveal_type(c) # revealed: (...) -> str |
| 127 | +``` |
| 128 | + |
| 129 | +## Using `typing.Concatenate` |
| 130 | + |
| 131 | +Using `Concatenate` as the first argument to `Callable`: |
| 132 | + |
| 133 | +```py |
| 134 | +from typing_extensions import Callable, Concatenate |
| 135 | + |
| 136 | +def _(c: Callable[Concatenate[int, str, ...], int]): |
| 137 | + reveal_type(c) # revealed: (*args: @Todo(todo signature *args), **kwargs: @Todo(todo signature **kwargs)) -> int |
| 138 | +``` |
| 139 | + |
| 140 | +And, as one of the parameter types: |
| 141 | + |
| 142 | +```py |
| 143 | +def _(c: Callable[[Concatenate[int, str, ...], int], int]): |
| 144 | + reveal_type(c) # revealed: (*args: @Todo(todo signature *args), **kwargs: @Todo(todo signature **kwargs)) -> int |
| 145 | +``` |
| 146 | + |
| 147 | +## Using `typing.ParamSpec` |
| 148 | + |
| 149 | +Using a `ParamSpec` in a `Callable` annotation: |
| 150 | + |
| 151 | +```py |
| 152 | +from typing_extensions import Callable |
| 153 | + |
| 154 | +# TODO: Not an error; remove once `ParamSpec` is supported |
| 155 | +# error: [invalid-type-form] |
| 156 | +def _[**P1](c: Callable[P1, int]): |
| 157 | + reveal_type(c) # revealed: (...) -> Unknown |
| 158 | +``` |
| 159 | + |
| 160 | +And, using the legacy syntax: |
| 161 | + |
| 162 | +```py |
| 163 | +from typing_extensions import ParamSpec |
| 164 | + |
| 165 | +P2 = ParamSpec("P2") |
| 166 | + |
| 167 | +# TODO: Not an error; remove once `ParamSpec` is supported |
| 168 | +# error: [invalid-type-form] |
| 169 | +def _(c: Callable[P2, int]): |
| 170 | + reveal_type(c) # revealed: (...) -> Unknown |
| 171 | +``` |
| 172 | + |
| 173 | +## Using `typing.Unpack` |
| 174 | + |
| 175 | +Using the unpack operator (`*`): |
| 176 | + |
| 177 | +```py |
| 178 | +from typing_extensions import Callable, TypeVarTuple |
| 179 | + |
| 180 | +Ts = TypeVarTuple("Ts") |
| 181 | + |
| 182 | +def _(c: Callable[[int, *Ts], int]): |
| 183 | + reveal_type(c) # revealed: (*args: @Todo(todo signature *args), **kwargs: @Todo(todo signature **kwargs)) -> int |
| 184 | +``` |
| 185 | + |
| 186 | +And, using the legacy syntax using `Unpack`: |
| 187 | + |
| 188 | +```py |
| 189 | +from typing_extensions import Unpack |
| 190 | + |
| 191 | +def _(c: Callable[[int, Unpack[Ts]], int]): |
| 192 | + reveal_type(c) # revealed: (*args: @Todo(todo signature *args), **kwargs: @Todo(todo signature **kwargs)) -> int |
| 193 | +``` |
| 194 | + |
| 195 | +[gradual form]: https://typing.readthedocs.io/en/latest/spec/glossary.html#term-gradual-form |
0 commit comments