Skip to content
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

Type checking problem with generic __init__() of generic class. #7369

Open
Luffbee opened this issue Feb 29, 2024 · 3 comments
Open

Type checking problem with generic __init__() of generic class. #7369

Luffbee opened this issue Feb 29, 2024 · 3 comments
Labels
bug Something isn't working

Comments

@Luffbee
Copy link

Luffbee commented Feb 29, 2024

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)

image
image

The type info of the Container0 in the do_map() function seems right, but the type check of argument and parameter report error:
image

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)

image

@Pwuts
Copy link

Pwuts commented Jun 8, 2024

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

@erictraut
Copy link
Collaborator

@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).

@Pwuts
Copy link

Pwuts commented Jun 8, 2024

@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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

3 participants