-
-
Notifications
You must be signed in to change notification settings - Fork 2.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Unbound type variables in Callable type alias are substituted with Any #13449
Comments
So, basically these two functions produce different results, but must produce the same thing: from typing import Any, Callable, TypeAlias, TypeVar
from typing_extensions import reveal_type
TCallable = TypeVar('TCallable', bound=Callable[..., Any])
TDecorator: TypeAlias = Callable[[TCallable], TCallable]
def a() -> TDecorator: ...
def b() -> Callable[[TCallable], TCallable]: ...
reveal_type(a()) # Revealed type is "Any"
reveal_type(b()) # Revealed type is "def [TCallable <: def (*Any, **Any) -> Any] (TCallable`-1) -> TCallable`-1" I will take a look! |
It is not just with from typing import TypeVar, List
from typing_extensions import reveal_type, TypeAlias
T = TypeVar('T')
TAlias: TypeAlias = List[T]
def a() -> TAlias: ...
def b() -> List[T]: ...
reveal_type(a()) # "builtins.list[Any]"
reveal_type(b()) # "builtins.list[<nothing>]" |
One more thing: function |
This is quite hard, at the moment - I have no idea how to do that properly. Lines 1635 to 1663 in 2ba6451
|
in basedmypy typevars are allowed in the bound of other typevars. |
@KotlinIsland sorry, I don't understand. Can you please clarify? |
Oh, my mistake, this is a |
(I believe everyone here knows this but) for what it's worth, I do not consider any of this to be a bug. Just generic type aliases going unapplied (think Note that if you use |
Thanks for the thoughtful discussion! :) @sobolevn In your example with
The documentation on type alias says:
Thus, def factory_with_bound_type_alias() -> TDecorator[TCallable]:
... supposed to be treated as def factory_with_bound_type_alias() -> Callable[[TCallable], TCallable]:
... But they are different. I believe that either documentation or implementation should be updated. Apart from this there is an awesome docs section on decorator factories - It's probably worth to put an advice to use I also wanted to notice that this behavior (mandatory usage of type variable) is quite counterintuitive - from my experience on their first attempt nobody manages to write correct annotation using |
I believe that this is the explanation of this issue which came up with the click 8.1.4 release. If we define F = TypeVar("F", bound=Callable[..., Any])
_Decorator: TypeAlias = Callable[[F], F] Then these two functions are treated differently def a() -> _Decorator[F]: ...
def b() -> Callable[[F], F]: ... I don't quite understand how the two are different from |
This isn't really the same as OP's case (which is mostly just use I'm pretty sympathetic to your issue, since it's more clearly a break of referential transparency. The good news is that this is already sort of fixed on master by #15287 (the implementation of which I don't yet fully understand), but is currently gated under the But to explain mypy's current behaviour, what's happening is that there's a difference in the scope of where the type variable is being bound to. In In 3.12, PEP 695 makes this scoping explicit, e.g. The way to spell this that will be clear to all type checkers is unfortunately a little verbose. Use a callback protocol (where the protocol is not generic, but its method is):
Here's a playground link that has more information on why this works and alternatives: https://mypy-play.net/?mypy=latest&python=3.11&gist=061bb59490d083e8e476dce5ba3640aa |
Ah, thanks for that clarification! It produces the same behavior in which the decorator is determined to take I don't intend to open a new issue since I'm not sure it would be productive. There are other issues (#11369 ?) which might be the same case. Thanks for the explanation of what's going on. I'm not sure I understand it, but it sounds like a fix is on its way towards a release. (Presumably |
Since click 8.1.4, mypy fails to deduce the correct types in the click type annotations for click.command. See pallets/click#2558 and python/mypy#13449. For now, workaround by ignoring the arg-type error.
I don't think @hauntsaninja I don't think we really need a new syntax. Using the new type alias syntax in PEP 695 should be enough to disambiguate 95% of currently problematic cases: type GenericDeco[F] = Callable[[F], F]
type PolymorphicDeco = Callable[[F], F] # note no F type argument on the left |
@ilevkivskyi there is some interaction with The difference is from before and after #15754. See:
re PEP 695: Yeah, I wasn't proposing new syntax, was just trying to explain that referential transparency breaks in sirosen's case because scope is different when inlined, and wanted some way to explain what scope would look like inline |
Oh wow, I know why it happened. I can actually bring it back, but I think we should not do it this way. If we want to change the default implicit type variable scope in type alias definitions before PEP 695 is widely available (say use some special logic for callable targets), it should be a conscious decision (and should be done during semantic analysis, not as a result of a hack during type checking). |
#3924 looks to capture the technical issue succinctly. I think the main problem, as seen from the pallets/click side of things, is both technical and social (and has been largely solved). The type alias was added as a good faith effort to improve annotations, but it was not obvious that something was broken until it was released. Even for a super-mainstream package like click, there hasn't been a tight enough and well-enough socialized story about how to test annotations for it to have been caught at the time it was added. That is, until recently, when assert_type became part of the stdlib, and it became possible to write @mydecorator
def foo() -> int: ...
x = foo()
assert_type(x, int) I have a lingering question which I'll take to #3924, as it seems more appropriate to ask there. |
Bug Report
A
Callable
type 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
Callable
type 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
Callable
scope), 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
Callable
in 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
The text was updated successfully, but these errors were encountered: