From 75510337fac1e4cd6fca4083227d11e9560ba4db Mon Sep 17 00:00:00 2001 From: James Hilton-Balfe Date: Wed, 24 Aug 2022 17:00:11 +0100 Subject: [PATCH 01/12] Fix typo in example --- pep-0696.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pep-0696.rst b/pep-0696.rst index 06bb9317296..d6f0b2c4cd6 100644 --- a/pep-0696.rst +++ b/pep-0696.rst @@ -160,7 +160,7 @@ literal "``...``". DefaultTs = TypeVarTuple("DefaultTs", default=Unpack[tuple[str, int]]) - class Foo(Generic[DefaultTs]): ... + class Foo(Generic[*DefaultTs]): ... reveal_type(Foo()) # type is Foo[str, int] reveal_type(Foo[int, bool]()) # type is Foo[int, bool] From 159f14db248a05dbac5e9c195fcc4055e97b5ffb Mon Sep 17 00:00:00 2001 From: James Hilton-Balfe Date: Fri, 26 Aug 2022 12:40:19 +0100 Subject: [PATCH 02/12] Revamp the "Using another ``TypeVarLike`` as the default" section among other things following Eric's feedback --- pep-0696.rst | 122 ++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 97 insertions(+), 25 deletions(-) diff --git a/pep-0696.rst b/pep-0696.rst index d6f0b2c4cd6..42feeb6f05c 100644 --- a/pep-0696.rst +++ b/pep-0696.rst @@ -15,7 +15,8 @@ Abstract This PEP introduces the concept of type defaults for ``TypeVarLike``\ s (``TypeVar``, ``ParamSpec`` and ``TypeVarTuple``), -which act as defaults for a type parameter when none is specified. +which act as defaults for a type parameter when none is specified or +the constraint solver isn't able to solve a type parameter to anything. Default type argument support is available in some popular languages such as C++, TypeScript, and Rust. A survey of type parameter syntax in @@ -78,15 +79,15 @@ This design pattern is common in projects like: - `NumPy `__ - the default for types like ``ndarray``'s ``dtype`` would be ``float64``. Currently it's ``Unknown`` or ``Any``. - - `TensorFlow `__ (this + - `TensorFlow `__ - this could be used for Tensor similarly to ``numpy.ndarray`` and would be - useful to simplify the definition of ``Layer``). + useful to simplify the definition of ``Layer``. Specification ------------- -Default ordering and subscription rules +Default Ordering and Subscription Rules ''''''''''''''''''''''''''''''''''''''' The order for defaults should follow the standard function parameter @@ -165,14 +166,16 @@ literal "``...``". reveal_type(Foo()) # type is Foo[str, int] reveal_type(Foo[int, bool]()) # type is Foo[int, bool] -Using another ``TypeVarLike`` as the default +Using Another ``TypeVarLike`` as ``default`` '''''''''''''''''''''''''''''''''''''''''''' -To use another ``TypeVarLike``\ s as the default they have to be of the -same type. When using another ``TypeVarLike`` (T1) as the default, the default -for the ``TypeVarLike`` (T2), T1 must be used before in the signature -of the class it appears in before T2. T2's bound must be a subtype of -T1's bound. +This allows for a value to be used again when the constraints solver +fails to solve a constraint for a type or the a type parameter to a +generic is missed out but another is specified. + +To use another ``TypeVarLike`` as a default the ``default`` and the +``TypeVarLike`` must be the same type (a ``TypeVar``'s default must be +a ``TypeVar``, etc.). `This could be used on builtins.slice `__ where the ``start`` parameter should default to ``int``, ``stop`` @@ -184,16 +187,78 @@ default to the type of ``start`` and step default to ``int | None``. StopT = TypeVar("StopT", default=StartT) StepT = TypeVar("StepT", default=int | None) - class slice(Generic[StartT, StopT, StepT]): ... # Valid + class slice(Generic[StartT, StopT, StepT]): ... + + 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, bool, timedelta]()) # type is slice[str, bool, timedelta] + +When using another ``TypeVarLike`` (``T1``) as the default, the default +for the ``TypeVarLike`` (``T2``) the following rules apply. + +Scoping Rules +~~~~~~~~~~~~~ + +``T1`` must be used before ``T2`` in the parameter list of the generic. + +.. code:: py + + DefaultT = TypeVar("DefaultT", default=T) - 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, str, timedelta]()) # type is slice[str, str, timedelta] + class Foo(Generic[T, DefaultT]): ... # Valid + def bar(x: T, y: DefaultT): ... # Valid - StartT = TypeVar("StartT", default="StopT") + StartT = TypeVar("StartT", default="StopT") # Swapped defaults around from previous example StopT = TypeVar("StopT", default=int) class slice(Generic[StartT, StopT, StepT]): ... - ^^^^^^ # Invalid: ordering does not allow StopT to bound yet + # ^^^^^^ Invalid: ordering does not allow StopT to be bound + def baz(x: DefaultT, y: T): ... + # ^^^^^^^^ Invalid: ordering does not allow DefaultT to be bound + +Bound Rules +~~~~~~~~~~~ + +``T2``'s bound must be a subtype of ``T1``'s bound. + +.. code:: py + + T = TypeVar("T", bound=float) + TypeVar("Ok", default=T, bound=int) # Valid + TypeVar("AlsoOk", default=T, bound=float) # Valid + TypeVar("Invalid", default=T, bound=str) # Invalid: str is not a subtype of float + +Constraint Rules +~~~~~~~~~~~~~~~~ + +``T2``'s constraint must either contain the bound of ``T1`` or the +constraints of ``T2`` must be a superset of the constraints of ``T1``. + +.. code:: py + + T1 = TypeVar("T1", bound=int) + TypeVar("Ok", int, str default=T1) # Valid + TypeVar("Invalid", float, str, default=T1) # Invalid: upper bound int is incompatible with constraints float or str + + T1 = TypeVar("T1", int, str) + TypeVar("AlsoOk", int, str, bool, default=T1) # Valid + TypeVar("AlsoInvalid", bool, complex, default=T1) # Invalid: {bool, complex} is not a superset of {int, str} + + +``TypeVarLike``\ s As Parameters To Generics +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +``TypeVarLike``\ s are valid as parameters to generics inside of a +``default`` when the first parameter is in scope. + +.. code:: py + + T = TypeVar("T") + ListDefaultT = TypeVar("ListDefaultT", default=list[T]) + + def foo(x: ListDefaultT): ... + def bar(y: T, x: ListDefaultT): ... + + ``Generic`` ``TypeAlias``\ es ''''''''''''''''''''''''''''' @@ -208,7 +273,7 @@ further down the line. 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[bool]()) # type is SomethingWithNoDefaults[int, bool] @@ -228,9 +293,9 @@ behave similarly to ``Generic`` ``TypeAlias``\ es. reveal_type(Bar()) # type is Bar[str] reveal_type(Bar[bool]()) # type is Bar[bool] - class Foo(SubclassMe[int]): ... + class Foo(SubclassMe[float]): ... - reveal_type(Foo()) # type is + reveal_type(Foo()) # type is Foo[str] # Invalid: Foo cannot be further subscripted @@ -239,8 +304,8 @@ behave similarly to ``Generic`` ``TypeAlias``\ es. class Spam(Baz): ... reveal_type(Spam()) # type is -Using bound and default -''''''''''''''''''''''' +Using ``bound`` and ``default`` +''''''''''''''''''''''''''''''' If both ``bound`` and ``default`` are passed ``default`` must be a subtype of ``bound``. Otherwise the type checker should generate an @@ -289,7 +354,8 @@ module. of ``TypeVar``, ``ParamSpec``, and ``TypeVarTuple``. - the type passed to default would be available as a ``__default__`` - attribute. + attribute and a sentinel would need to be added that can be + checked against to see if it was supplied. The following changes would be required to both ``GenericAlias``\ es: @@ -322,6 +388,12 @@ using the "=" operator inside of the square brackets like so: class Qux[*Ts = *tuple[int, bool]]: ... def ham[*Us = *tuple[str]](): ... + # TypeAliases + type Foo[T, U = str] = Bar[T, U] + type Baz[**P = (int, str)] = Spam[**P] + type Qux[*Ts = *tuple[str]] = Ham[*Ts] + type Rab[U, T = str] = Bar[T, U] + This functionality was included in the initial draft of :pep:`695` but was removed due to scope creep. @@ -345,7 +417,7 @@ with non-defaults can be checked at compile time. Rejected Alternatives --------------------- -Allowing the ``TypeVarLike``\ s defaults to be passed to ``type.__new__``'s ``**kwargs`` +Allowing The ``TypeVarLike``\ s Defaults To Be Passed To ``type.__new__``'s ``**kwargs`` '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' .. code:: py @@ -373,7 +445,7 @@ Ideally, if :pep:`637` wasn't rejected, the following would be acceptable: class Box(Generic[T = int]): value: T | None = None -Allowing non-defaults to follow defaults +Allowing Non-defaults To Follow Defaults '''''''''''''''''''''''''''''''''''''''' .. code:: py @@ -399,7 +471,7 @@ break a lot of codebases. This is also solvable in most cases using a Coro: TypeAlias = Coroutine[Any, Any, T] Coro[int] == Coroutine[Any, Any, int] -Having ``default`` implicitly be ``bound`` +Having ``default`` Implicitly Be ``bound`` '''''''''''''''''''''''''''''''''''''''''' In an earlier version of this PEP, the ``default`` was implicitly set From c54857ce846deaec10096f4f28ecc5fb8243924e Mon Sep 17 00:00:00 2001 From: James Hilton-Balfe Date: Mon, 29 Aug 2022 20:08:43 +0100 Subject: [PATCH 03/12] Add extra scoping rules Co-authored-by: Eric Traut --- pep-0696.rst | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/pep-0696.rst b/pep-0696.rst index 42feeb6f05c..5aaa5c2f6cb 100644 --- a/pep-0696.rst +++ b/pep-0696.rst @@ -199,14 +199,19 @@ for the ``TypeVarLike`` (``T2``) the following rules apply. 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 bound in an outer class or function scope. .. code:: py DefaultT = TypeVar("DefaultT", default=T) - class Foo(Generic[T, DefaultT]): ... # Valid - def bar(x: T, y: DefaultT): ... # Valid + class Foo(Generic[T, DefaultT]): ... # Valid + def bar(x: T, y: DefaultT): ... # Valid + class Foo(Generic[T]): + 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 StopT = TypeVar("StopT", default=int) @@ -215,6 +220,7 @@ Scoping Rules def baz(x: DefaultT, y: T): ... # ^^^^^^^^ Invalid: ordering does not allow DefaultT to be bound + Bound Rules ~~~~~~~~~~~ @@ -248,7 +254,8 @@ constraints of ``T2`` must be a superset of the constraints of ``T1``. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ``TypeVarLike``\ s are valid as parameters to generics inside of a -``default`` when the first parameter is in scope. +``default`` when the first parameter is in scope as determined by the +previous section. .. code:: py @@ -259,7 +266,6 @@ constraints of ``T2`` must be a superset of the constraints of ``T1``. def bar(y: T, x: ListDefaultT): ... - ``Generic`` ``TypeAlias``\ es ''''''''''''''''''''''''''''' From add591f03ce8981b341faa1ef1518d15864996cd Mon Sep 17 00:00:00 2001 From: James Hilton-Balfe Date: Wed, 31 Aug 2022 16:59:48 +0100 Subject: [PATCH 04/12] Fix grammar and syntax issues and improve ``TypeVarLike``\ s As Parameters To Generics section Co-authored-by: Jelle Zijlstra --- pep-0696.rst | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/pep-0696.rst b/pep-0696.rst index 5aaa5c2f6cb..4c2900b9bc2 100644 --- a/pep-0696.rst +++ b/pep-0696.rst @@ -193,14 +193,14 @@ default to the type of ``start`` and step default to ``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] -When using another ``TypeVarLike`` (``T1``) as the default, the default -for the ``TypeVarLike`` (``T2``) the following rules apply. +When using a ``TypeVarLike`` as the default to another ``TypeVarLike``. +Where ``T1`` is the default for ``T2`` the following rules apply. Scoping Rules ~~~~~~~~~~~~~ ``T1`` must be used before ``T2`` in the parameter list of the generic -or bound in an outer class or function scope. +or be bound in an outer class or function scope. .. code:: py @@ -220,7 +220,6 @@ or bound in an outer class or function scope. def baz(x: DefaultT, y: T): ... # ^^^^^^^^ Invalid: ordering does not allow DefaultT to be bound - Bound Rules ~~~~~~~~~~~ @@ -242,7 +241,7 @@ constraints of ``T2`` must be a superset of the constraints of ``T1``. .. code:: py T1 = TypeVar("T1", bound=int) - TypeVar("Ok", int, str default=T1) # Valid + TypeVar("Ok", int, str, default=T1) # Valid TypeVar("Invalid", float, str, default=T1) # Invalid: upper bound int is incompatible with constraints float or str T1 = TypeVar("T1", int, str) @@ -262,8 +261,18 @@ previous section. T = TypeVar("T") ListDefaultT = TypeVar("ListDefaultT", default=list[T]) - def foo(x: ListDefaultT): ... - def bar(y: T, x: ListDefaultT): ... + class Bar(Generic[T, ListDefaultT]): + def __init__(self, x: T, y: ListDefaultT): ... + + 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 +~~~~~~~~~~~~~~~~~~~~ + +``TypeVarLike``\ s currently cannot be further subscripted. This might +change if Higher Kinded TypeVars are implemented. ``Generic`` ``TypeAlias``\ es From 2112f5a163fcc077f2a66f26296acbff799c68c1 Mon Sep 17 00:00:00 2001 From: Gobot1234 Date: Wed, 14 Sep 2022 17:59:19 +0100 Subject: [PATCH 05/12] Textual and clarity improvements Co-authored-by: C.A.M. Gerlach --- pep-0696.rst | 81 ++++++++++++++++++++++++++++------------------------ 1 file changed, 43 insertions(+), 38 deletions(-) diff --git a/pep-0696.rst b/pep-0696.rst index 4c2900b9bc2..72b45653d17 100644 --- a/pep-0696.rst +++ b/pep-0696.rst @@ -15,7 +15,7 @@ Abstract This PEP introduces the concept of type defaults for ``TypeVarLike``\ s (``TypeVar``, ``ParamSpec`` and ``TypeVarTuple``), -which act as defaults for a type parameter when none is specified or +which act as defaults for a type parameter when one is not specified or the constraint solver isn't able to solve a type parameter to anything. Default type argument support is available in some popular languages @@ -28,7 +28,7 @@ and can be found in its Motivation ---------- -.. code:: py +.. code-block:: py T = TypeVar("T", default=int) # This means that if no type is specified T = int @@ -43,7 +43,7 @@ One place this `regularly comes up `__ is ``Generator``. I propose changing the *stub definition* to something like: -.. code:: py +.. code-block:: py YieldT = TypeVar("YieldT") SendT = TypeVar("SendT", default=None) @@ -55,7 +55,7 @@ propose changing the *stub definition* to something like: This is also useful for a ``Generic`` that is commonly over one type. -.. code:: py +.. code-block:: py class Bot: ... @@ -74,12 +74,12 @@ also helps non-typing users who rely on auto-complete to speed up their development. This design pattern is common in projects like: - - `discord.py `__ - where the + - `discord.py `__ — where the example above was taken from. - - `NumPy `__ - the default for types + - `NumPy `__ — the default for types like ``ndarray``'s ``dtype`` would be ``float64``. Currently it's ``Unknown`` or ``Any``. - - `TensorFlow `__ - this + - `TensorFlow `__ — this could be used for Tensor similarly to ``numpy.ndarray`` and would be useful to simplify the definition of ``Layer``. @@ -96,7 +96,7 @@ a ``default`` value. Doing so should ideally raise a ``TypeError`` in ``typing._GenericAlias``/``types.GenericAlias``, and a type checker should flag this an error. -.. code:: py +.. code-block:: py DefaultStrT = TypeVar("DefaultStrT", default=str) DefaultIntT = TypeVar("DefaultIntT", default=int) @@ -142,7 +142,7 @@ future, this might be possible (see `Interaction with PEP ``TypeVar`` \ s but use a ``list`` or ``tuple`` of types or an ellipsis literal "``...``". -.. code:: py +.. code-block:: py DefaultP = ParamSpec("DefaultP", default=(str, int)) @@ -157,7 +157,7 @@ literal "``...``". ``TypeVarTuple`` defaults are defined using the same syntax as ``TypeVar`` \ s but use an unpacked tuple of types instead of a single type. -.. code:: py +.. code-block:: py DefaultTs = TypeVarTuple("DefaultTs", default=Unpack[tuple[str, int]]) @@ -170,8 +170,8 @@ Using Another ``TypeVarLike`` as ``default`` '''''''''''''''''''''''''''''''''''''''''''' This allows for a value to be used again when the constraints solver -fails to solve a constraint for a type or the a type parameter to a -generic is missed out but another is specified. +fails to solve a constraint for a type, or the type parameter to a +generic is missing but another type parameter is specified. To use another ``TypeVarLike`` as a default the ``default`` and the ``TypeVarLike`` must be the same type (a ``TypeVar``'s default must be @@ -181,7 +181,7 @@ a ``TypeVar``, etc.). where the ``start`` parameter should default to ``int``, ``stop`` default to the type of ``start`` and step default to ``int | None``. -.. code:: py +.. code-block:: py StartT = TypeVar("StartT", default=int) StopT = TypeVar("StopT", default=StartT) @@ -196,13 +196,15 @@ default to the type of ``start`` and step default to ``int | None``. When using a ``TypeVarLike`` as the default to another ``TypeVarLike``. Where ``T1`` is the default for ``T2`` the following rules apply. +.. _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:: py +.. code-block:: py DefaultT = TypeVar("DefaultT", default=T) @@ -225,7 +227,7 @@ Bound Rules ``T2``'s bound must be a subtype of ``T1``'s bound. -.. code:: py +.. code-block:: py T = TypeVar("T", bound=float) TypeVar("Ok", default=T, bound=int) # Valid @@ -235,10 +237,10 @@ Bound Rules Constraint Rules ~~~~~~~~~~~~~~~~ -``T2``'s constraint must either contain the bound of ``T1`` or the +Either ``T2``'s constraint must contain the bound of ``T1``, or the constraints of ``T2`` must be a superset of the constraints of ``T1``. -.. code:: py +.. code-block:: py T1 = TypeVar("T1", bound=int) TypeVar("Ok", int, str, default=T1) # Valid @@ -249,14 +251,14 @@ constraints of ``T2`` must be a superset of the constraints of ``T1``. TypeVar("AlsoInvalid", bool, complex, default=T1) # Invalid: {bool, complex} is not a superset of {int, str} -``TypeVarLike``\ s As Parameters To Generics -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +``TypeVarLike``\s as Parameters to Generics +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ``TypeVarLike``\ s are valid as parameters to generics inside of a ``default`` when the first parameter is in scope as determined by the -previous section. +:ref:`previous section `. -.. code:: py +.. code-block:: py T = TypeVar("T") ListDefaultT = TypeVar("ListDefaultT", default=list[T]) @@ -272,7 +274,8 @@ Specialisation Rules ~~~~~~~~~~~~~~~~~~~~ ``TypeVarLike``\ s currently cannot be further subscripted. This might -change if Higher Kinded TypeVars are implemented. +change if `Higher Kinded TypeVars `__ +are implemented. ``Generic`` ``TypeAlias``\ es @@ -284,7 +287,7 @@ that hasn't been overridden it should be treated like it was substituted into the ``TypeAlias``. However, it can be specialised further down the line. -.. code:: py +.. code-block:: py class SomethingWithNoDefaults(Generic[T, T2]): ... @@ -300,7 +303,7 @@ Subclassing Subclasses of ``Generic``\ s with ``TypeVarLike``\ s that have defaults behave similarly to ``Generic`` ``TypeAlias``\ es. -.. code:: py +.. code-block:: py class SubclassMe(Generic[T, DefaultStrT]): ... @@ -326,7 +329,7 @@ If both ``bound`` and ``default`` are passed ``default`` must be a subtype of ``bound``. Otherwise the type checker should generate an error. -.. code:: py +.. code-block:: py TypeVar("Ok", bound=float, default=int) # Valid TypeVar("Invalid", bound=str, default=int) # Invalid: the bound and default are incompatible @@ -338,7 +341,7 @@ For constrained ``TypeVar``\ s, the default needs to be one of the constraints. A type checker should generate an error even if it is a subtype of one of the constraints. -.. code:: py +.. code-block:: py TypeVar("Ok", float, str, default=float) # Valid TypeVar("Invalid", float, str, default=int) # Invalid: expected one of float or str got int @@ -348,7 +351,7 @@ Function Defaults ``TypeVarLike``\ s currently can only be used where a parameter can go unsolved. -.. code:: py +.. code-block:: py def foo(a: DefaultStrT | None = None) -> DefaultStrT: ... @@ -389,7 +392,7 @@ If this PEP is accepted, the syntax proposed in :pep:`695` will be extended to introduce a way to specify defaults for type parameters using the "=" operator inside of the square brackets like so: -.. code:: py +.. code-block:: py # TypeVars class Foo[T = str]: ... @@ -423,7 +426,9 @@ Grammar Changes | '*' a=NAME d=[type_param_default] | '**' a=NAME d=[type_param_default] - type_param_default: '=' e=expression + type_param_default: + | '=' e=expression + | '=' e=starred_expression This would mean that ``TypeVarLike``\ s with defaults proceeding those with non-defaults can be checked at compile time. @@ -432,10 +437,10 @@ with non-defaults can be checked at compile time. Rejected Alternatives --------------------- -Allowing The ``TypeVarLike``\ s Defaults To Be Passed To ``type.__new__``'s ``**kwargs`` -'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' +Allowing the ``TypeVarLike``\s Defaults to Be Passed to ``type.__new__``'s ``**kwargs`` +''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' -.. code:: py +.. code-block:: py T = TypeVar("T") @@ -452,7 +457,7 @@ at runtime. Ideally, if :pep:`637` wasn't rejected, the following would be acceptable: -.. code:: py +.. code-block:: py T = TypeVar("T") @@ -460,10 +465,10 @@ Ideally, if :pep:`637` wasn't rejected, the following would be acceptable: class Box(Generic[T = int]): value: T | None = None -Allowing Non-defaults To Follow Defaults +Allowing Non-defaults to Follow Defaults '''''''''''''''''''''''''''''''''''''''' -.. code:: py +.. code-block:: py YieldT = TypeVar("YieldT", default=Any) SendT = TypeVar("SendT", default=Any) @@ -481,7 +486,7 @@ above two forms were valid. Changing the argument order now would also break a lot of codebases. This is also solvable in most cases using a ``TypeAlias``. -.. code:: py +.. code-block:: py Coro: TypeAlias = Coroutine[Any, Any, T] Coro[int] == Coroutine[Any, Any, int] @@ -494,7 +499,7 @@ to ``bound`` if no value was passed for ``default``. This while convenient, could have a ``TypeVarLike`` with no default follow a ``TypeVarLike`` with a default. Consider: -.. code:: py +.. code-block:: py T = TypeVar("T", bound=int) # default is implicitly int U = TypeVar("U") From 5248aa282e760f0f6162f90f22058d751607377a Mon Sep 17 00:00:00 2001 From: Gobot1234 Date: Sun, 16 Oct 2022 18:21:33 +0100 Subject: [PATCH 06/12] Last couple of updates Co-authored-by: Jelle Zijlstra --- pep-0696.rst | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/pep-0696.rst b/pep-0696.rst index 72b45653d17..757e2a9e43f 100644 --- a/pep-0696.rst +++ b/pep-0696.rst @@ -237,13 +237,11 @@ Bound Rules Constraint Rules ~~~~~~~~~~~~~~~~ -Either ``T2``'s constraint must contain the bound of ``T1``, or the -constraints of ``T2`` must be a superset of the constraints of ``T1``. +The constraints of ``T2`` must be a superset of the constraints of ``T1``. .. code-block:: py T1 = TypeVar("T1", bound=int) - TypeVar("Ok", int, str, default=T1) # Valid TypeVar("Invalid", float, str, default=T1) # Invalid: upper bound int is incompatible with constraints float or str T1 = TypeVar("T1", int, str) @@ -264,7 +262,7 @@ constraints of ``T2`` must be a superset of the constraints of ``T1``. ListDefaultT = TypeVar("ListDefaultT", default=list[T]) 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[int, list[str]]) # type is Bar[int, list[str]] @@ -305,7 +303,8 @@ behave similarly to ``Generic`` ``TypeAlias``\ es. .. code-block:: py - class SubclassMe(Generic[T, DefaultStrT]): ... + class SubclassMe(Generic[T, DefaultStrT]): + x: DefaultStrT class Bar(SubclassMe[int, DefaultStrT]): ... reveal_type(Bar()) # type is Bar[str] @@ -313,7 +312,7 @@ behave similarly to ``Generic`` ``TypeAlias``\ es. class Foo(SubclassMe[float]): ... - reveal_type(Foo()) # type is + reveal_type(Foo().x) # type is str Foo[str] # Invalid: Foo cannot be further subscripted From 240fb2afcc08c11794f442cf74ea38efa93bdeea Mon Sep 17 00:00:00 2001 From: Gobot1234 Date: Sun, 23 Oct 2022 22:16:13 +0100 Subject: [PATCH 07/12] Fix ParamSpecs and TypeVarTuples being missing from their respective default sections --- pep-0696.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pep-0696.rst b/pep-0696.rst index 757e2a9e43f..5df39210d08 100644 --- a/pep-0696.rst +++ b/pep-0696.rst @@ -140,7 +140,7 @@ future, this might be possible (see `Interaction with PEP ``ParamSpec`` defaults are defined using the same syntax as ``TypeVar`` \ s but use a ``list`` or ``tuple`` of types or an ellipsis -literal "``...``". +literal "``...``" or another in-scope ``ParamSpec`` (see :ref:`scoping-rules`). .. code-block:: py @@ -155,7 +155,8 @@ literal "``...``". ''''''''''''''''''''''''' ``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`). .. code-block:: py From 4f6cbd397bc6c5091cab2c815656d96df9c1f3cf Mon Sep 17 00:00:00 2001 From: Gobot1234 Date: Sun, 23 Oct 2022 22:16:37 +0100 Subject: [PATCH 08/12] Remove function defaults as they are too hard to keep track of --- pep-0696.rst | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/pep-0696.rst b/pep-0696.rst index 5df39210d08..608086f3620 100644 --- a/pep-0696.rst +++ b/pep-0696.rst @@ -346,21 +346,6 @@ subtype of one of the constraints. TypeVar("Ok", float, str, default=float) # Valid TypeVar("Invalid", float, str, default=int) # Invalid: expected one of float or str got int -Function Defaults -''''''''''''''''' - -``TypeVarLike``\ s currently can only be used where a parameter can go unsolved. - -.. code-block:: py - - def foo(a: DefaultStrT | None = None) -> DefaultStrT: ... - - reveal_type(foo(1)) # type is int - reveal_type(foo()) # type is str - -If they are used where the parameter type is known, the defaults -should just be ignored and a type checker can emit a warning. - Implementation -------------- From a420e2685e60ded5919c149962ce6b682f42f610 Mon Sep 17 00:00:00 2001 From: Gobot1234 Date: Sun, 23 Oct 2022 22:17:12 +0100 Subject: [PATCH 09/12] Remove mention of _DefaultMixin and only describe the end behaviour --- pep-0696.rst | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/pep-0696.rst b/pep-0696.rst index 608086f3620..c464fb17356 100644 --- a/pep-0696.rst +++ b/pep-0696.rst @@ -353,12 +353,10 @@ Implementation At runtime, this would involve the following changes to the ``typing`` module. -- a new class ``_DefaultMixin`` needs to be added which is a superclass - of ``TypeVar``, ``ParamSpec``, and ``TypeVarTuple``. - - - the type passed to default would be available as a ``__default__`` - attribute and a sentinel would need to be added that can be - checked against to see if it was supplied. +- The classes ``TypeVar``, ``ParamSpec``, and ``TypeVarTuple`` would + all expose the type passed to default which would be available as a + ``__default__`` attribute with the default being ``None`` if no + argument is passed and ``NoneType`` if ``default=None``. The following changes would be required to both ``GenericAlias``\ es: From e6bfc52837ea8afef964c94de586848ece956b43 Mon Sep 17 00:00:00 2001 From: Gobot1234 Date: Wed, 26 Oct 2022 23:18:45 +0100 Subject: [PATCH 10/12] I think this reads better --- pep-0696.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pep-0696.rst b/pep-0696.rst index c464fb17356..ccd40e15077 100644 --- a/pep-0696.rst +++ b/pep-0696.rst @@ -353,9 +353,9 @@ Implementation At runtime, this would involve the following changes to the ``typing`` module. -- The classes ``TypeVar``, ``ParamSpec``, and ``TypeVarTuple`` would - all expose the type passed to default which would be available as a - ``__default__`` attribute with the default being ``None`` if no +- The classes ``TypeVar``, ``ParamSpec``, and ``TypeVarTuple`` should + expose the type passed to ``default``. This would be available as + a ``__default__`` attribute with the default being ``None`` if no argument is passed and ``NoneType`` if ``default=None``. The following changes would be required to both ``GenericAlias``\ es: From 3bcb5488aa87d23b07a6ee61d256aa5c8385712e Mon Sep 17 00:00:00 2001 From: Gobot1234 Date: Fri, 28 Oct 2022 00:18:49 +0100 Subject: [PATCH 11/12] Add a note as to why function defaults are unsupported --- pep-0696.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pep-0696.rst b/pep-0696.rst index ccd40e15077..6eb9bd9538e 100644 --- a/pep-0696.rst +++ b/pep-0696.rst @@ -346,6 +346,12 @@ subtype of one of the constraints. TypeVar("Ok", float, str, default=float) # Valid TypeVar("Invalid", float, str, default=int) # Invalid: expected one of float or str got int +Function Defaults +''''''''''''''''' + +``TypeVarLike``\ s currently are not supported in the signatures of +functions as ensuring the ``default`` is returned in every code path +where the ``TypeVarLike`` can go unsolved is too hard to implement. Implementation -------------- From ad23089dea86e288bab84e53a2a4de3012255fb9 Mon Sep 17 00:00:00 2001 From: Gobot1234 Date: Fri, 28 Oct 2022 00:20:46 +0100 Subject: [PATCH 12/12] Improve readability of clause Co-authored-by: Jelle Zijlstra --- pep-0696.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pep-0696.rst b/pep-0696.rst index 6eb9bd9538e..938aa06ec19 100644 --- a/pep-0696.rst +++ b/pep-0696.rst @@ -361,8 +361,8 @@ module. - The classes ``TypeVar``, ``ParamSpec``, and ``TypeVarTuple`` should expose the type passed to ``default``. This would be available as - a ``__default__`` attribute with the default being ``None`` if no - argument is passed and ``NoneType`` if ``default=None``. + a ``__default__`` attribute, which would be ``None`` if no argument + is passed and ``NoneType`` if ``default=None``. The following changes would be required to both ``GenericAlias``\ es: