mypy icon indicating copy to clipboard operation
mypy copied to clipboard

Support PEP 696 – Type defaults for TypeVarLikes

Open intgr opened this issue 2 years ago • 8 comments

Feature

See PEP 696 – Type defaults for TypeVarLikes for details. The PEP has not yet been accepted though. Example from the PEP:

T = TypeVar("T", default=int)  # This means that if no type is specified T = int

@dataclass
class Box(Generic[T]):
    value: T | None = None

reveal_type(Box())                      # type is Box[int]
reveal_type(Box(value="Hello World!"))  # type is Box[str]

Since I did not find an existing tracking issue for this feature, I decided to open one.

The default= argument is already supported in typing_extensions version 4.4.0+ and typeshed (https://github.com/python/typeshed/pull/8821)

intgr avatar Mar 07 '23 15:03 intgr

The original example passes now with mypy 1.9.0, thanks to @cdce8p's work (https://mypy-play.net/?mypy=latest&python=3.10&gist=ca609b35e9cbf97a600343b249c6b0a9). What still needs to be done here?

JelleZijlstra avatar Mar 11 '24 15:03 JelleZijlstra

One missing feature is TypeVar(default=) referencing another TypeVar. django-stubs has been patiently waiting for this feature, it would allow us to clean up some pretty ugly hacks.

T = TypeVar("T")
U = TypeVar("U", default=T)  # If just T is specified, then U = T

class Box(Generic[T, U]):  # Box[T] would be expanded to Box[T, T]
    ...

Right now there's no warning when using this, mypy silently does the wrong thing. Playground: https://mypy-play.net/?mypy=latest&python=3.12&gist=22121ee5ef0b55d52d651a1d8636df73

intgr avatar Mar 11 '24 15:03 intgr

Ah yes, that doesn't quite work yet:

from typing import Generic, TypeVar

T = TypeVar("T")
U = TypeVar("U", default=T)  # If just T is specified, then U = T

class Box(Generic[T, U]):  # Box[T] would be expanded to Box[T, T]
    ...

x = Box[int]()
reveal_type(x)  # E: Revealed type is "__main__.Box[builtins.int, T?]"

JelleZijlstra avatar Mar 11 '24 16:03 JelleZijlstra

@cdce8p Are you interested in implementing this part of PEP 696 too -- allowing TypeVar to default to another TypeVar? Thanks for your work so far!

intgr avatar Mar 24 '24 12:03 intgr

@cdce8p Are you interested in implementing this part of PEP 696 too -- allowing TypeVar to default to another TypeVar?

I've already started working on it. See #16878. That one didn't make in time for the 1.9.0 release though. I do plan to continue the work, just not sure when I'll get to it. It might take some time.

cdce8p avatar Mar 24 '24 12:03 cdce8p

If I'm correct, this issue is required for Python 3.13 support right? What's needed to move it forward?

EwoutH avatar Aug 17 '24 16:08 EwoutH

It's required to support some new syntax in Python 3.13, but mypy will type check most code in 3.13 just fine without this, so it depends on what you count as "Python 3.13 support".

As an example (probably relevant to why you're here :) ), to make Black support 3.13 mypyc compilation, this issue isn't relevant because Black doesn't use TypeVars with defaults at the moment.

However, @cdce8p implemented much of what's needed for this feature in any case (thanks!). I don't know how much is left, but most basic use cases should already work.

JelleZijlstra avatar Aug 17 '24 17:08 JelleZijlstra

As an example (probably relevant to why you're here :) ), to make Black support 3.13 mypyc compilation, this issue isn't relevant because Black doesn't use TypeVars with defaults at the moment.

The issue you're looking for is probably https://github.com/mypyc/mypyc/issues/1056.

However, @cdce8p implemented much of what's needed for this feature in any case (thanks!). I don't know how much is left, but most basic use cases should already work.

The next step would be to add support for the new TypeVar defaults syntax in 3.13. I've started working on it already, just need to find some time soon to finish it and open a PR.

Besides that, the support for ParamSpec / TypeVarTuple defaults and recursive TypeVar defaults is still quite limited unfortunately. Although it would be great to fully support these, PEP 696 is already useable in the most common cases.

cdce8p avatar Aug 17 '24 17:08 cdce8p

Examples like the following still fail (for a dumb reason related to assert_type, that hopefully shouldn't show up for users):

from typing import Generic, assert_type
from typing_extensions import TypeVar

DefaultStrT = TypeVar("DefaultStrT", default=str)
DefaultIntT = TypeVar("DefaultIntT", default=int)

class NoNonDefaults(Generic[DefaultStrT, DefaultIntT]): ...

assert_type(NoNonDefaults, type[NoNonDefaults[str, int]])
assert_type(NoNonDefaults[str], type[NoNonDefaults[str, int]])
assert_type(NoNonDefaults[str, int], type[NoNonDefaults[str, int]])

It looks like this is due to the thing I find slightly confusing where mypy represents type objects as CallableType. One possible way to fix is to replace type variables with their defaults when comparing CallableType to TypeType and vice versa. Maybe there's a better way too

hauntsaninja avatar Nov 03 '24 06:11 hauntsaninja