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

Unable to infer correct type for generic TypeVars using TypeVar defaults (PEP 696) #16233

Closed
insilications opened this issue Oct 6, 2023 · 4 comments
Labels
bug mypy got something wrong topic-pep-696 TypeVar defaults

Comments

@insilications
Copy link

insilications commented Oct 6, 2023

Bug Report
Pylance is able to infer the correct type for MyClass even in the absence of explicit typing of all its generic TypeVars when using TypeVar defaults. I don't know if this is the intended behavior and who is implementing PEP 696 (https://peps.python.org/pep-0696/) correctly or fully.

To Reproduce

from typing import Generic, reveal_type
from typing_extensions import TypeVar

T1 = TypeVar('T1', default=None)
T2 = TypeVar('T2', default=None)


class MyClass(Generic[T1, T2]):
    pass


myclass1 = MyClass()
reveal_type(myclass1)
myclass2: MyClass[float] = MyClass()
reveal_type(myclass2)

Playground URL: https://mypy-play.net/?mypy=master&python=3.11&gist=f685e0af58eaceb6506ce129da4392c3

Expected Behavior
Maybe match how Pylance is implementing PEP 696 – Type defaults for TypeVarLikes

Actual Behavior

Revealed type is "test.MyClass[None, None]" [Ln 13] Mypy
Type of "myclass1" is "MyClass[None, None]" [Ln 13] Pylance

Revealed type is "test.MyClass[Any, Any]" [Ln 15] Mypy
Type of "myclass2" is "MyClass[float, None]" [Ln 15] Pylance

"MyClass" expects 2 type arguments, but 1 given [Ln 14] Mypy

Your Environment

  • Mypy version used: master branch
  • Pylance version used: Pylance v2023.10.11 (Pre-release). Needs TypeVar from typing_extensions if using TypeVar defaults (PEP 696)
  • Python version used: 3.11/3.12
@insilications insilications added the bug mypy got something wrong label Oct 6, 2023
@JelleZijlstra JelleZijlstra added the topic-pep-696 TypeVar defaults label Oct 6, 2023
@JelleZijlstra
Copy link
Member

Mypy doesn't fully support PEP 696 yet. Closing as a duplicate of #14851.

@JelleZijlstra JelleZijlstra closed this as not planned Won't fix, can't repro, duplicate, stale Oct 6, 2023
@erictraut
Copy link

@insilications, as noted in #14851, PEP 696 is still in draft form and has not yet been accepted by the steering council. That means this feature could change or be removed in the future. If you want to play around with the functionality and provide feedback on the draft PEP, that's great, but I caution against using this functionality in production code until after the PEP is approved in final form.

@insilications
Copy link
Author

@insilications, as noted in #14851, PEP 696 is still in draft form and has not yet been accepted by the steering council. That means this feature could change or be removed in the future. If you want to play around with the functionality and provide feedback on the draft PEP, that's great, but I caution against using this functionality in production code until after the PEP is approved in final form.

In your view, is this a safe pattern, using @overload, in order to avoid using PEP 696?
I was so eager to use it that I didn't see it was still a draft.

from __future__ import annotations
from typing import Generic, reveal_type, overload
from typing_extensions import TypeVar

# T1 = TypeVar('T1', default=None)
# T2 = TypeVar('T2', default=None)
T1 = TypeVar('T1')
T2 = TypeVar('T2')

class MyClass(Generic[T1, T2]):
    @overload
    def __init__(self: MyClass[None, None]) -> None:
        pass

    @overload
    def __init__(self: MyClass[T1, None], *types: *tuple[type[T1]]) -> None:
        pass

    @overload
    def __init__(self: MyClass[T1, T2], *types: *tuple[type[T1], type[T2]]) -> None:
        pass

    def __init__(self: MyClass[T1, T2], *types: *tuple[type, ...]) -> None:
        pass


myclass1 = MyClass()
reveal_type(myclass1) # Revealed type is "generic5.MyClass[None, None]"
myclass2 = MyClass(float)
reveal_type(myclass2) # Revealed type is "generic5.MyClass[builtins.float, None]"

@erictraut
Copy link

Yes, that looks fine to me and works with pyright. However, mypy doesn't yet support the unpack syntax that you're using in this example. You'd need to use Unpack and --enable-incomplete-feature=Unpack. Even with these changes, mypy crashes (an INTERNAL ERROR).

Here's a variant that works with mypy:

class MyClass(Generic[T1, T2]):
    @overload
    def __init__(self: MyClass[None, None]) -> None:
        pass

    @overload
    def __init__(self: MyClass[T1, None], type1: type[T1]) -> None:
        pass

    @overload
    def __init__(self: MyClass[T1, T2], type1: type[T1], type2: type[T2]) -> None:
        pass

    def __init__(
        self: MyClass[T1, T2],
        type1: type[T1] | None = None,
        type2: type[T2] | None = None,
    ) -> None:
        pass

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug mypy got something wrong topic-pep-696 TypeVar defaults
Projects
None yet
Development

No branches or pull requests

3 participants