PEP 696: Add further clarification and edge cases (#2902)
Co-authored-by: Eric Traut <eric@traut.com> Co-authored-by: C.A.M. Gerlach <CAM.Gerlach@Gerlach.CAM> Co-authored-by: Jelle Zijlstra <jelle.zijlstra@gmail.com>
This commit is contained in:
parent
859164d25e
commit
b32d1ce8a1
77
pep-0696.rst
77
pep-0696.rst
|
@ -119,10 +119,14 @@ should flag this an error.
|
||||||
class OneDefault(Generic[T, DefaultBoolT]): ...
|
class OneDefault(Generic[T, DefaultBoolT]): ...
|
||||||
|
|
||||||
OneDefault[float] == OneDefault[float, bool] # Valid
|
OneDefault[float] == OneDefault[float, bool] # Valid
|
||||||
|
reveal_type(OneDefault) # type is type[OneDefault[T, DefaultBoolT = bool]]
|
||||||
|
reveal_type(OneDefault[float]()) # type is OneDefault[float, bool]
|
||||||
|
|
||||||
|
|
||||||
class AllTheDefaults(Generic[T1, T2, DefaultStrT, DefaultIntT, DefaultBoolT]): ...
|
class AllTheDefaults(Generic[T1, T2, DefaultStrT, DefaultIntT, DefaultBoolT]): ...
|
||||||
|
|
||||||
|
reveal_type(AllTheDefaults) # type is type[AllTheDefaults[T1, T2, DefaultStrT = str, DefaultIntT = int, DefaultBoolT = bool]]
|
||||||
|
reveal_type(AllTheDefaults[int, complex]()) # type is AllTheDefaults[int, complex, str, int, bool]
|
||||||
AllTheDefaults[int] # Invalid: expected 2 arguments to AllTheDefaults
|
AllTheDefaults[int] # Invalid: expected 2 arguments to AllTheDefaults
|
||||||
(
|
(
|
||||||
AllTheDefaults[int, complex] ==
|
AllTheDefaults[int, complex] ==
|
||||||
|
@ -140,7 +144,7 @@ future, this might be possible (see `Interaction with PEP
|
||||||
|
|
||||||
``ParamSpec`` defaults are defined using the same syntax as
|
``ParamSpec`` defaults are defined using the same syntax as
|
||||||
``TypeVar`` \ s but use a ``list`` or ``tuple`` of types or an ellipsis
|
``TypeVar`` \ s but use a ``list`` or ``tuple`` of types or an ellipsis
|
||||||
literal "``...``" or another in-scope ``ParamSpec`` (see :ref:`scoping-rules`).
|
literal "``...``" or another in-scope ``ParamSpec`` (see :ref:`696-scoping-rules`).
|
||||||
|
|
||||||
.. code-block:: py
|
.. code-block:: py
|
||||||
|
|
||||||
|
@ -148,6 +152,7 @@ literal "``...``" or another in-scope ``ParamSpec`` (see :ref:`scoping-rules`).
|
||||||
|
|
||||||
class Foo(Generic[DefaultP]): ...
|
class Foo(Generic[DefaultP]): ...
|
||||||
|
|
||||||
|
reveal_type(Foo) # type is type[Foo[DefaultP = (str, int)]]
|
||||||
reveal_type(Foo()) # type is Foo[(str, int)]
|
reveal_type(Foo()) # type is Foo[(str, int)]
|
||||||
reveal_type(Foo[(bool, bool)]()) # type is Foo[(bool, bool)]
|
reveal_type(Foo[(bool, bool)]()) # type is Foo[(bool, bool)]
|
||||||
|
|
||||||
|
@ -156,7 +161,7 @@ literal "``...``" or another in-scope ``ParamSpec`` (see :ref:`scoping-rules`).
|
||||||
|
|
||||||
``TypeVarTuple`` defaults are defined using the same syntax as
|
``TypeVarTuple`` defaults are defined using the same syntax as
|
||||||
``TypeVar`` \ s but use an unpacked tuple of types instead of a single type
|
``TypeVar`` \ s but use an unpacked tuple of types instead of a single type
|
||||||
or another in-scope ``TypeVarTuple`` (see :ref:`scoping-rules`).
|
or another in-scope ``TypeVarTuple`` (see :ref:`696-scoping-rules`).
|
||||||
|
|
||||||
.. code-block:: py
|
.. code-block:: py
|
||||||
|
|
||||||
|
@ -164,6 +169,7 @@ or another in-scope ``TypeVarTuple`` (see :ref:`scoping-rules`).
|
||||||
|
|
||||||
class Foo(Generic[*DefaultTs]): ...
|
class Foo(Generic[*DefaultTs]): ...
|
||||||
|
|
||||||
|
reveal_type(Foo) # type is type[Foo[DefaultTs = *tuple[str, int]]]
|
||||||
reveal_type(Foo()) # type is Foo[str, int]
|
reveal_type(Foo()) # type is Foo[str, int]
|
||||||
reveal_type(Foo[int, bool]()) # type is Foo[int, bool]
|
reveal_type(Foo[int, bool]()) # type is Foo[int, bool]
|
||||||
|
|
||||||
|
@ -190,38 +196,56 @@ default to the type of ``start`` and step default to ``int | None``.
|
||||||
|
|
||||||
class slice(Generic[StartT, StopT, StepT]): ...
|
class slice(Generic[StartT, StopT, StepT]): ...
|
||||||
|
|
||||||
|
reveal_type(slice) # type is type[slice[StartT = int, StopT = StartT, StepT = int | None]]
|
||||||
reveal_type(slice()) # type is slice[int, int, int | None]
|
reveal_type(slice()) # type is slice[int, int, int | None]
|
||||||
reveal_type(slice[str]()) # type is slice[str, str, int | None]
|
reveal_type(slice[str]()) # type is slice[str, str, int | None]
|
||||||
reveal_type(slice[str, bool, timedelta]()) # type is slice[str, bool, timedelta]
|
reveal_type(slice[str, bool, timedelta]()) # type is slice[str, bool, timedelta]
|
||||||
|
|
||||||
|
T2 = TypeVar("T2", default=DefaultStrT)
|
||||||
|
|
||||||
|
class Foo(Generic[DefaultStrT, T2]):
|
||||||
|
def __init__(self, a: DefaultStrT, b: T2) -> None: ...
|
||||||
|
|
||||||
|
reveal_type(Foo(1, "")) # type is Foo[int, str]
|
||||||
|
Foo[int](1, "") # Invalid: Foo[int, str] cannot be assigned to self: Foo[int, int] in Foo.__init__
|
||||||
|
Foo[int]("", 1) # Invalid: Foo[str, int] cannot be assigned to self: Foo[int, int] in Foo.__init__
|
||||||
|
|
||||||
When using a ``TypeVarLike`` as the default to another ``TypeVarLike``.
|
When using a ``TypeVarLike`` as the default to another ``TypeVarLike``.
|
||||||
Where ``T1`` is the default for ``T2`` the following rules apply.
|
Where ``T1`` is the default for ``T2`` the following rules apply.
|
||||||
|
|
||||||
.. _scoping-rules:
|
``TypeVarTuple``\s are not supported because:
|
||||||
|
|
||||||
|
- :ref:`696-scoping-rules` does not allow usage of ``TypeVarLikes``
|
||||||
|
from outer scopes.
|
||||||
|
- Multiple ``TypeVarTuple``\s cannot appear in the type
|
||||||
|
parameter list for a single class, as specified in
|
||||||
|
:pep:`646#multiple-type-variable-tuples-not-allowed`.
|
||||||
|
- ``TypeVarLike`` defaults in functions are not supported.
|
||||||
|
|
||||||
|
These reasons leave no current valid location where a
|
||||||
|
``TypeVarTuple`` could have a default.
|
||||||
|
|
||||||
|
.. _696-scoping-rules:
|
||||||
|
|
||||||
Scoping Rules
|
Scoping Rules
|
||||||
~~~~~~~~~~~~~
|
~~~~~~~~~~~~~
|
||||||
|
|
||||||
``T1`` must be used before ``T2`` in the parameter list of the generic,
|
``T1`` must be used before ``T2`` in the parameter list of the generic.
|
||||||
or be bound in an outer class or function scope.
|
|
||||||
|
|
||||||
.. code-block:: py
|
.. code-block:: py
|
||||||
|
|
||||||
DefaultT = TypeVar("DefaultT", default=T)
|
DefaultT = TypeVar("DefaultT", default=T)
|
||||||
|
|
||||||
class Foo(Generic[T, DefaultT]): ... # Valid
|
class Foo(Generic[T, DefaultT]): ... # Valid
|
||||||
def bar(x: T, y: DefaultT): ... # Valid
|
|
||||||
class Foo(Generic[T]):
|
class Foo(Generic[T]):
|
||||||
class Bar(Generic[DefaultT]): ... # Valid
|
class Bar(Generic[DefaultT]): ... # Valid
|
||||||
def outer(x: T):
|
|
||||||
def inner(y: DefaultT): ... # Valid
|
|
||||||
|
|
||||||
StartT = TypeVar("StartT", default="StopT") # Swapped defaults around from previous example
|
StartT = TypeVar("StartT", default="StopT") # Swapped defaults around from previous example
|
||||||
StopT = TypeVar("StopT", default=int)
|
StopT = TypeVar("StopT", default=int)
|
||||||
class slice(Generic[StartT, StopT, StepT]): ...
|
class slice(Generic[StartT, StopT, StepT]): ...
|
||||||
# ^^^^^^ Invalid: ordering does not allow StopT to be bound
|
# ^^^^^^ Invalid: ordering does not allow StopT to be bound
|
||||||
def baz(x: DefaultT, y: T): ...
|
|
||||||
# ^^^^^^^^ Invalid: ordering does not allow DefaultT to be bound
|
Using a ``TypeVarLike`` from an outer scope as a default is not supported.
|
||||||
|
|
||||||
Bound Rules
|
Bound Rules
|
||||||
~~~~~~~~~~~
|
~~~~~~~~~~~
|
||||||
|
@ -255,7 +279,7 @@ The constraints of ``T2`` must be a superset of the constraints of ``T1``.
|
||||||
|
|
||||||
``TypeVarLike``\ s are valid as parameters to generics inside of a
|
``TypeVarLike``\ s are valid as parameters to generics inside of a
|
||||||
``default`` when the first parameter is in scope as determined by the
|
``default`` when the first parameter is in scope as determined by the
|
||||||
:ref:`previous section <scoping-rules>`.
|
:ref:`previous section <696-scoping-rules>`.
|
||||||
|
|
||||||
.. code-block:: py
|
.. code-block:: py
|
||||||
|
|
||||||
|
@ -265,9 +289,11 @@ The constraints of ``T2`` must be a superset of the constraints of ``T1``.
|
||||||
class Bar(Generic[T, ListDefaultT]):
|
class Bar(Generic[T, ListDefaultT]):
|
||||||
def __init__(self, x: T, y: ListDefaultT): ...
|
def __init__(self, x: T, y: ListDefaultT): ...
|
||||||
|
|
||||||
reveal_type(Bar[int]) # type is Bar[int, list[int]]
|
reveal_type(Bar) # type is type[Bar[T, ListDefaultT = list[T]]]
|
||||||
reveal_type(Bar[int, list[str]]) # type is Bar[int, list[str]]
|
reveal_type(Bar[int]) # type is type[Bar[int, list[int]]]
|
||||||
reveal_type(Bar[int, str]) # type is Bar[int, str]
|
reveal_type(Bar[int]()) # type is Bar[int, list[int]]
|
||||||
|
reveal_type(Bar[int, list[str]]()) # type is Bar[int, list[str]]
|
||||||
|
reveal_type(Bar[int, str]()) # type is Bar[int, str]
|
||||||
|
|
||||||
Specialisation Rules
|
Specialisation Rules
|
||||||
~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~
|
||||||
|
@ -291,7 +317,7 @@ further down the line.
|
||||||
class SomethingWithNoDefaults(Generic[T, T2]): ...
|
class SomethingWithNoDefaults(Generic[T, T2]): ...
|
||||||
|
|
||||||
MyAlias: TypeAlias = SomethingWithNoDefaults[int, DefaultStrT] # Valid
|
MyAlias: TypeAlias = SomethingWithNoDefaults[int, DefaultStrT] # Valid
|
||||||
reveal_type(MyAlias()) # type is SomethingWithNoDefaults[int, str]
|
reveal_type(MyAlias) # type is type[SomethingWithNoDefaults[int, DefaultStrT]]
|
||||||
reveal_type(MyAlias[bool]()) # type is SomethingWithNoDefaults[int, bool]
|
reveal_type(MyAlias[bool]()) # type is SomethingWithNoDefaults[int, bool]
|
||||||
|
|
||||||
MyAlias[bool, int] # Invalid: too many arguments passed to MyAlias
|
MyAlias[bool, int] # Invalid: too many arguments passed to MyAlias
|
||||||
|
@ -308,6 +334,7 @@ behave similarly to ``Generic`` ``TypeAlias``\ es.
|
||||||
x: DefaultStrT
|
x: DefaultStrT
|
||||||
|
|
||||||
class Bar(SubclassMe[int, DefaultStrT]): ...
|
class Bar(SubclassMe[int, DefaultStrT]): ...
|
||||||
|
reveal_type(Bar) # type is type[Bar[DefaultStrT = str]]
|
||||||
reveal_type(Bar()) # type is Bar[str]
|
reveal_type(Bar()) # type is Bar[str]
|
||||||
reveal_type(Bar[bool]()) # type is Bar[bool]
|
reveal_type(Bar[bool]()) # type is Bar[bool]
|
||||||
|
|
||||||
|
@ -346,6 +373,8 @@ subtype of one of the constraints.
|
||||||
TypeVar("Ok", float, str, default=float) # Valid
|
TypeVar("Ok", float, str, default=float) # Valid
|
||||||
TypeVar("Invalid", float, str, default=int) # Invalid: expected one of float or str got int
|
TypeVar("Invalid", float, str, default=int) # Invalid: expected one of float or str got int
|
||||||
|
|
||||||
|
.. _696-function-defaults:
|
||||||
|
|
||||||
Function Defaults
|
Function Defaults
|
||||||
'''''''''''''''''
|
'''''''''''''''''
|
||||||
|
|
||||||
|
@ -373,6 +402,8 @@ The following changes would be required to both ``GenericAlias``\ es:
|
||||||
A reference implementation of the type checker can be found at
|
A reference implementation of the type checker can be found at
|
||||||
https://github.com/Gobot1234/mypy/tree/TypeVar-defaults
|
https://github.com/Gobot1234/mypy/tree/TypeVar-defaults
|
||||||
|
|
||||||
|
Pyright currently supports this functionality.
|
||||||
|
|
||||||
|
|
||||||
Interaction with PEP 695
|
Interaction with PEP 695
|
||||||
------------------------
|
------------------------
|
||||||
|
@ -385,15 +416,12 @@ using the "=" operator inside of the square brackets like so:
|
||||||
|
|
||||||
# TypeVars
|
# TypeVars
|
||||||
class Foo[T = str]: ...
|
class Foo[T = str]: ...
|
||||||
def bar[U = int](): ...
|
|
||||||
|
|
||||||
# ParamSpecs
|
# ParamSpecs
|
||||||
class Baz[**P = (int, str)]: ...
|
class Baz[**P = (int, str)]: ...
|
||||||
def spam[**Q = (bool,)](): ...
|
|
||||||
|
|
||||||
# TypeVarTuples
|
# TypeVarTuples
|
||||||
class Qux[*Ts = *tuple[int, bool]]: ...
|
class Qux[*Ts = *tuple[int, bool]]: ...
|
||||||
def ham[*Us = *tuple[str]](): ...
|
|
||||||
|
|
||||||
# TypeAliases
|
# TypeAliases
|
||||||
type Foo[T, U = str] = Bar[T, U]
|
type Foo[T, U = str] = Bar[T, U]
|
||||||
|
@ -507,6 +535,19 @@ convenient, could have a ``TypeVarLike`` with no default follow a
|
||||||
This would have also been a breaking change for a small number of cases
|
This would have also been a breaking change for a small number of cases
|
||||||
where the code relied on ``Any`` being the implicit default.
|
where the code relied on ``Any`` being the implicit default.
|
||||||
|
|
||||||
|
Allowing ``TypeVarLike``\s with defaults to be used in function signatures
|
||||||
|
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
|
||||||
|
|
||||||
|
A previous version of this PEP allowed ``TypeVarLike``\s with defaults to be used in
|
||||||
|
function signatures. This was removed for the reasons described in
|
||||||
|
:ref:`696-function-defaults`. Hopefully, this can be added in the future if
|
||||||
|
a way to get the runtime value of a type parameter is added.
|
||||||
|
|
||||||
|
Allowing ``TypeVarLikes`` from outer scopes in ``default``
|
||||||
|
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
|
||||||
|
|
||||||
|
This was deemed too niche a feature to be worth the added complexity.
|
||||||
|
If any cases arise where this is needed, it can be added in a future PEP.
|
||||||
|
|
||||||
Acknowledgements
|
Acknowledgements
|
||||||
----------------
|
----------------
|
||||||
|
|
Loading…
Reference in New Issue