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

Pyright unable to infer the type of Generic classmethod #7721

Closed
sunfkny opened this issue Apr 17, 2024 · 1 comment
Closed

Pyright unable to infer the type of Generic classmethod #7721

sunfkny opened this issue Apr 17, 2024 · 1 comment
Labels
as designed Not a bug, working as intended bug Something isn't working

Comments

@sunfkny
Copy link

sunfkny commented Apr 17, 2024

Describe the bug
Since version 1.1.354, Pyright unable to infer the type of a generic class method in certain scenarios.

Code or Screenshots

from typing import Generic, TypeVar, reveal_type

T = TypeVar("T")

class Option(Generic[T]):
    def __init__(self, value: T):
        self._val = value

    @classmethod
    def Some(cls, val: T) -> "Option[T]":
        return cls(val)

reveal_type(Option(1))           # Pyright correctly infers Option[int]
reveal_type(Option.Some(2))      # Pyright incorrectly infers Option[Unknown]

In this code, Pyright correctly infers the type of Option(1) as Option[int], but since version 1.1.354, it incorrectly infers the type of Option.Some(2) as Option[Unknown] Pyright Playground. Prior to version 1.1.354, Pyright inferred the correct type for both cases Pyright Playground.

VS Code extension or command-line
ms-python.vscode-pylance v2024.4.1

@sunfkny sunfkny added the bug Something isn't working label Apr 17, 2024
@erictraut
Copy link
Collaborator

This is by design, not a bug. This behavior was previously somewhat ambiguous in the typing spec, but the recent addition of PEP 696 (which introduces TypeVar default values) clarified how this must work.

When you call a constructor of a generic class, the specialized type of the class is not yet established and can be inferred from the arguments passed to the constructor.

By contrast, when you call a class method, the specialized type of the class is already established at the time that the method is bound to the class — prior to the call. If you don't explicitly provide type argument values, they take on their default values (as explained in PEP 696). If no default is specified, it implicitly becomes Any (i.e. Unknown).

In your example above, if you want to invoke a class method Some on Option[int], you'd need to write Option[int].Some(2). Or if you use PEP 696 to specify a default type argument for T, you could do the following:

Code sample in pyright playground

from typing import Generic, reveal_type
from typing_extensions import TypeVar

T = TypeVar("T", default=int)

class Option(Generic[T]):
    def __init__(self, value: T):
        self._val = value

    @classmethod
    def Some(cls, val: T) -> "Option[T]":
        return cls(val)

reveal_type(Option.Some(2)) # Option[int]

@erictraut erictraut closed this as not planned Won't fix, can't repro, duplicate, stale Apr 17, 2024
@erictraut erictraut added the as designed Not a bug, working as intended label Apr 17, 2024
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 bug Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants