Bug Report
A Callable type alias is handled like usual generic, not as Callable.
This does not allow to define a type alias for a decorator.
It can be handled via callback Protocol, but in this case it is not possible to support signature transformation via ParamSpec.
To Reproduce
Run mypy on following code:
fromtypingimportAny, Callable, TypeAlias, TypeVarfromtyping_extensionsimportreveal_typeTCallable=TypeVar('TCallable', bound=Callable[..., Any])
TDecorator: TypeAlias=Callable[[TCallable], TCallable]
deffactory_with_unbound_type_alias() ->TDecorator:
...
deffactory_with_bound_type_alias() ->TDecorator[TCallable]:
...
defsome_function(request: Any) ->str:
return'Hello world'reveal_type(factory_with_unbound_type_alias()(some_function)) # note: Revealed type is "Any"reveal_type(factory_with_bound_type_alias()(some_function))
# note: error: Argument 1 has incompatible type "Callable[[Any], str]"; expected <nothing># note: Revealed type is "<nothing>"Expected Behavior
According to docs:
A parameterized generic alias is treated simply as an original type with the corresponding type variables substituted.
So def factory_with_bound_type_alias() -> TDecorator[TCallable]: should work.
But to be honest this is pretty counterintuitive.
I'd expect:
reveal_type(factory_with_unbound_type_alias()(some_function)) # Revealed type is "def (request: Any) -> builtins.str"
Generally speaking I'd expect unbound type variables in Callable type alias to be bound to its call scope, not filled with Any.
It already works this way with Callable itself:
fromtypingimportAny, Callable, Dict, TypeVarfromtyping_extensionsimportreveal_typeT=TypeVar('T')
defcallable_factory() ->Callable[[T], T]:
...
defdict_factory() ->Dict[T, T]:
...
reveal_type(callable_factory())
# note: Revealed type is "def [T] (T`-1) -> T`-1"reveal_type(dict_factory())
# note: Revealed type is "builtins.dict[<nothing>, <nothing>]"I'd expect it to work this way until alias has another non-callable generic depending on this variable (out of this Callable scope), e.g. current behavior in this snippet is fine:
fromtypingimportAny, Callable, List, Union, TypeVarfromtyping_extensionsimportreveal_typeT=TypeVar('T')
TListOrFactory=Union[List[T], Callable[[], List[T]]]
defmake_list_or_factory() ->TListOrFactory:
...
reveal_type(make_list_or_factory()) # note: Revealed type is "Union[builtins.list[Any], def () -> builtins.list[Any]]"Your Environment
gist
mypy-playground
- Mypy version used: mypy 0.971
- Python version used: 3.10
Related discussion
I've created a similar ticket in Pyright repo:
microsoft/pyright#3803
It appears that the right way to handle Callable in Pyright is by passing it a type variable:
deffactory_with_bound_type_alias() ->TDecorator[TCallable]:
...
reveal_type(factory_with_bound_type_alias()(some_function))
# pyright: Type of "factory_with_bound_type_alias()(some_function)" is "(request: Any) -> str"
I've searched for any discussions on semantics of Callable aliases, but didn't manage to find anything.
So, after all I've created a (dead) discussion in typing repo:
python/typing#1236
Bug Report
A
Callabletype alias is handled like usual generic, not asCallable.This does not allow to define a type alias for a decorator.
It can be handled via callback
Protocol, but in this case it is not possible to support signature transformation viaParamSpec.To Reproduce
Run mypy on following code:
Expected Behavior
According to docs:
So
def factory_with_bound_type_alias() -> TDecorator[TCallable]:should work.But to be honest this is pretty counterintuitive.
I'd expect:
Generally speaking I'd expect unbound type variables in
Callabletype alias to be bound to its call scope, not filled withAny.It already works this way with Callable itself:
I'd expect it to work this way until alias has another non-callable generic depending on this variable (out of this
Callablescope), e.g. current behavior in this snippet is fine:Your Environment
gist
mypy-playground
Related discussion
I've created a similar ticket in Pyright repo:
microsoft/pyright#3803
It appears that the right way to handle
Callablein Pyright is by passing it a type variable:I've searched for any discussions on semantics of Callable aliases, but didn't manage to find anything.
So, after all I've created a (dead) discussion in typing repo:
python/typing#1236