-
-
Notifications
You must be signed in to change notification settings - Fork 2.8k
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
TypeVar
inside ParamSpec
doesn't get inferred
#12278
Comments
Edit: I take back what I said above. This should be solvable. |
That's unfortunate. My naive expectations were informed by TypeScript, which does figure out the More concretely, I ran into the issue with code along the lines of |
I got into a very similar scenario these days, the only difference being that I was not using this double level of TypeVars (ie. So I tried to exercise this in a different lens, to see if I can understand why this is happening (because I may fall into this same use case in the future): from typing import Callable, ParamSpec, TypeVar, Generic, Final, Protocol
T = TypeVar("T")
P = ParamSpec("P")
R = TypeVar("R")
C = TypeVar("C", covariant=True)
class Builder(Protocol[P, C]):
def __call__(self, *args: P.args, **kwargs: P.kwargs) -> C: ...
class Call:
def __getitem__(self, callable: Callable[P, R]) -> Build[P, R]:
def wrapper(*args: P.args, **kwargs: P.kwargs):
nonlocal callable
return callable(*args, **kwargs)
return wrapper
call: Final = Call()
def identity(x: T) -> T:
return x
# Instead of trying to do everything at once, I tried to break the
# operation in two steps, statically (a waste, runtime-wise, I admit).
# According to the revealed type, the __getitem__ is properly generic
# (because of the `def [P, R] ...`). I'm not sure what "P`71" and "R`72" mean;
# is this the notation for a "locally" bound type variable (I suppose it is,
# and the numbers refer to the instantiations)?
reveal_type(call.__getitem__) # ==> note: Revealed type is "def [P, R] (def (*P.args, **P.kwargs) -> R`72) -> __main__.Builder[P`71, R`72]"
# Question here is: what does mypy generate when we parameterize a generic
# function with another generic function?
wrapper = call[identity]
reveal_type(wrapper) # ==> note: Revealed type is "__main__.Builder[def [T] (x: T`-1), T`-1]"
# It seems that, with a generic protocol (and maybe with other generic types
# as well), the ParamSpec is propagated as a generic, but the return type is
# dissociated from the protocol's second type parameter -- even though the
# first argument and the return type were inferred to be the same.
# Going forward, the two inferred types in Builder are effectively distinct.
# For this idiom to work, ParamSpec should be able to pass along the whole
# initial function signature, ie. including the return type, which PEP-612 does
# not seem to support.
#
# I also tried a non-protocol (ie. returning "Callable[P, R]") variant for Call.__getitem__;
# the revealed type for "wrapper" is "def (x: T`-1) -> T`-1", which is not generic.
# I'm not sure if the same reasoning could be applied in this case.
wrapper(2) # error: Argument 1 to "call" has incompatible type "int"; expected "T"
y: int = call[identity](2) # error: Incompatible types in assignment (expression has type "T", variable has type "int") @erictraut Any chance a scenario like this would be supported in the future (in a new PEP or otherwise)? |
It's probably best to ask the authors of PEP 612. They're members of the team responsible for the pyre type checker, and they might have additional insights here. cc @pradeep90 |
I'm hoping that we can support this. There seems to be no particular reason why this shouldn't work, but we may need a bit of special logic for this particular use case. |
Note that although #15896 will fix this, you will still likely need to use |
Fixes #12278 Fixes #13191 (more tricky nested use cases with optional/keyword args still don't work, but they are quite tricky to fix and may selectively fixed later) This unfortunately requires some special-casing, here is its summary: * If actual argument for `Callable[P, T]` is non-generic and non-lambda, do not put it into inference second pass. * If we are able to infer constraints for `P` without using arguments mapped to `*args: P.args` etc., do not add the constraint for `P` vs those arguments (this applies to both top-level callable constraints, and for nested callable constraints against callables that are known to have imprecise argument kinds). (Btw TODO I added is not related to this PR, I just noticed something obviously wrong)
Bug Report
To Reproduce
Expected Behavior
Code passes type checking.
Actual Behavior
Mypy emits errors.
Your Environment
The text was updated successfully, but these errors were encountered: