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

Default value for Generic self is erased by @dataclass #14382 #4393

Closed
zmievsa opened this issue Jan 3, 2023 · 1 comment
Closed

Default value for Generic self is erased by @dataclass #14382 #4393

zmievsa opened this issue Jan 3, 2023 · 1 comment
Labels
as designed Not a bug, working as intended

Comments

@zmievsa
Copy link

zmievsa commented Jan 3, 2023

Describe the bug
In both pyright and mypy, it is possible to specify the default value for a generic self using __init__ parameter type hint or __new__ return type hint. However, when a @dataclass decorator is applied to the class, the old __init__/__new__ definition gets erased. Note that if both __new__ and __init__ are present, then pyright works correctly.

To Reproduce

from dataclasses import dataclass
from typing import Generic, Literal, TypeVar, cast

T = TypeVar("T", bound=Literal["LoggedIn", "NotLoggedIn"])


@dataclass(init=False)
class User(Generic[T]):
    name: str

    # Adding this definition will fix the problem but mypy does not require `__new__` to be added
    # def __new__(cls: "type[User]", name: str) -> "User[Literal['NotLoggedIn']]":
    #     return object.__new__(cls)


    def __init__(self: "User[Literal['NotLoggedIn']]", name: str) -> None:
        self.name = name

    def log_in(self) -> "User[Literal['LoggedIn']]":
        return cast(User[Literal["LoggedIn"]], self)

    def scream(self: "User[Literal['LoggedIn']]") -> None:
        print(f"{self.name} screams!")


f = User("admin")
reveal_type(f)

Actual behavior
reveal_type(f) outputs Type of "f" is "User[Unknown]"

Expected behavior
reveal_type(f) must output Type of "f" is "User[Literal['NotLoggedIn']]"

VS Code extension or command-line
VS Code extension: v2022.12.20

Additional context

@erictraut
Copy link
Collaborator

The technique of using a self annotation in an __init__ method to influence the type of the constructed class instance is not standard and is fraught with problems. It has never been specified anywhere, so these issues have never been explored or discussed, and it's difficult to say what its specific behavior should be and what circumstances it should or should not handle. It's something that mypy implemented and typeshed stubs adopted in a limited form to handle a specific situation where the type of a constructed object varies depending on its input parameters.

Because this mechanism is not documented or specified anywhere, I needed to make certain assumptions when implementing it in pyright. One of the assumptions I made was that it would be used only with overloads. This is a reasonable assumption given the use case that motivated the original implementation in mypy.

In your sample above, you are attempting to use this mechanism without an overload. That means the type of the constructed object does not vary depending on the input parameter types, and the only type that you can construct is User[Literal['LoggedIn']]. In such a case, it doesn't make sense to use generics because there is only one type argument that is legal here. You cannot construct an instance with any other type, so the class isn't really generic.

I'm going to close this issue because I don't think it's a bug in pyright.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
as designed Not a bug, working as intended
Projects
None yet
Development

No branches or pull requests

2 participants