pyright
pyright copied to clipboard
Type checking problem with generic `__init__()` of generic class.
Describe the bug
I'm trying to write a generic container class Container[T]
, and support method do_map(self, func: Callable[[T], W]) -> Container[W]
that do a transform like map()
.
For some reasons, I want the the __init__
to accept a list[U]
and a function Callable[[U], T]
, and call map()
in the __init__
.
Then the do_map()
method just need to call the construction of the new container with type Container[W]
.
However, the following code (Container0
) is rejected by pyright, but mypy accepts it.
Although I can bypass it by a helper function (Container1
with do_map_helper
), it's not convenient, and seems like a bug.
Code or Screenshots
# container.py
from typing import Callable, Generic, TypeVar
T = TypeVar("T")
U = TypeVar("U")
W = TypeVar("W")
class Container0(Generic[T]):
def __init__(self, vals: list[U], func: Callable[[U], T]):
self._vals = list(map(func, vals))
def do_map(self, func: Callable[[T], W]) -> "Container0[W]":
return Container0(self._vals, func)
class Container1(Generic[T]):
def __init__(self, vals: list[U], func: Callable[[U], T]):
self._vals = list(map(func, vals))
def do_map(self, func: Callable[[T], W]) -> "Container1[W]":
return do_map_helper(self, func)
def do_map_helper(c: Container1[T], func: Callable[[T], W]) -> Container1[W]:
return Container1(c._vals, func)
The type info of the Container0
in the do_map()
function seems right, but the type check of argument and parameter report error:
If your code relies on symbols that are imported from a third-party library, include the associated import statements and specify which versions of those libraries you have installed.
VS Code extension or command-line This problem can be reproduced by both Pylance and pyright command-line.
$ pyright --version; mypy --version
pyright 1.1.351
mypy 1.8.0 (compiled: yes)
I'm experiencing a similar issue, not sure if it's the same issue:
1. .command
from forge.agent.protocols import CommandProvider
P = ParamSpec("P")
CO = TypeVar("CO") # command output
_CP = TypeVar("_CP", bound=CommandProvider)
class Command(Generic[P, CO]):
def __init__(
self,
method: Callable[P, CO] | Callable[Concatenate[_CP, P], CO],
):
# Methods technically have a `self` parameter, but we can ignore that
# since Python passes it internally.
self.method = cast(Callable[P, CO], method)
so far, so good
2. .decorator
from .command import CO, Command, P
_CP = TypeVar("_CP", bound=CommandProvider)
def command() -> Callable[[Callable[P, CO] | Callable[Concatenate[_CP, P], CO]], Command[P, CO]]:
def decorator(func: Callable[P, CO] | Callable[Concatenate[_CP, P], CO]) -> Command[P, CO]:
# ...
return Command(
method=func, # Error: Argument of type "((**P@decorator) -> CO@decorator) | ((_CP@decorator, **P@decorator) -> CO@decorator)" cannot be assigned to parameter "method" of type "((**P@Command) -> CO@Command) | ((_CP@__init__, **P@Command) -> CO@Command)" in function "__init__"
)
return decorator
# Error: Expression of type "(func: ((**P@decorator) -> CO@decorator) | ((_CP@decorator, **P@decorator) -> CO@decorator)) -> Command[P@decorator, CO@decorator]" cannot be assigned to return type "(((**P@command) -> CO@command) | ((_CP@command, **P@command) -> CO@command)) -> Command[P@command, CO@command]"
# Type "(func: ((**P@decorator) -> CO@decorator) | ((_CP@decorator, **P@decorator) -> CO@decorator)) -> Command[P@decorator, CO@decorator]" cannot be assigned to type "(((**P@command) -> CO@command) | ((_CP@command, **P@command) -> CO@command)) -> Command[P@command, CO@command]"
# Parameter 1: type "((**P@command) -> CO@command) | ((_CP@command, **P@command) -> CO@command)" cannot be assigned to type "((**P@decorator) -> CO@decorator) | ((_CP@decorator, **P@decorator) -> CO@decorator)"
Despite the type vars being directly imported and the type unions being the same, Pyright won't have it:
Argument of type "((**P@decorator) -> CO@decorator) | ((_CP@decorator, **P@decorator) -> CO@decorator)" cannot be assigned to parameter "method" of type "((**P@Command) -> CO@Command) | ((_CP@__init__, **P@Command) -> CO@Command)" in function "__init__"
and
Parameter 1: type "((**P@command) -> CO@command) | ((_CP@command, **P@command) -> CO@command)" cannot be assigned to type "((**P@decorator) -> CO@decorator) | ((_CP@decorator, **P@decorator) -> CO@decorator)"
Pyright doesn't have any issues with the above example if I remove either side of the union in all occurrences of Callable[P, CO] | Callable[Concatenate[_CP, P], CO]
.
I can also make the first error (on method=func
) disappear like this:
return Command[P, CO](
method=func,
)
but that doesn't resolve the error on return decorator
@Pwuts, I don't think your issue is the same. If you think you've found a different bug, please file a separate bug report. I'm not able to repro any errors with the code you've provided. However, I will note that your use of _CP
in the union is problematic because it can go unsolved (as indicated in the warning).
@erictraut, I may have stripped off a bit too much code in an effort to make a minimum example. I'll test again and submit a new issue.
You're right about the warning that _CP
is only used once. Replacing _CP
by CommandProvider
doesn't make the other errors go away though.
This is addressed in pyright 1.1.373.