Skip to content

Commit 0e849c5

Browse files
Gobot1234cdce8p
andcommitted
Base implementation of PEP 696
Co-authored-by: Marc Mueller <[email protected]>
1 parent ced13c9 commit 0e849c5

File tree

3 files changed

+103
-51
lines changed

3 files changed

+103
-51
lines changed

Doc/library/typing.rst

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1292,6 +1292,22 @@ These are not used in annotations. They are building blocks for creating generic
12921292

12931293
c = concatenate('one', b'two') # error: type variable 'A' can be either str or bytes in a function call, but not both
12941294

1295+
If a generic type is commonly generic over just one type you can use
1296+
``default`` to specify this type::
1297+
1298+
T = TypeVar("T", default=int)
1299+
1300+
class Box(Generic[T]):
1301+
def __init__(self, value: T | None = None):
1302+
self.value = value
1303+
1304+
reveal_type(Box()) # type is Box[int]
1305+
reveal_type(Box(value="Hello World!")) # type is Box[str]
1306+
1307+
A TypeVar without a default cannot follow a TypeVar with a default.
1308+
1309+
.. TODO add more about this
1310+
12951311
At runtime, ``isinstance(x, T)`` will raise :exc:`TypeError`. In general,
12961312
:func:`isinstance` and :func:`issubclass` should not be used with types.
12971313

@@ -1395,6 +1411,8 @@ These are not used in annotations. They are building blocks for creating generic
13951411

13961412
See :pep:`646` for more details on type variable tuples.
13971413

1414+
.. TODO docs on default
1415+
13981416
.. versionadded:: 3.11
13991417

14001418
.. data:: Unpack
@@ -1423,7 +1441,7 @@ These are not used in annotations. They are building blocks for creating generic
14231441

14241442
.. versionadded:: 3.11
14251443

1426-
.. class:: ParamSpec(name, *, bound=None, covariant=False, contravariant=False)
1444+
.. class:: ParamSpec(name, *, bound=None, default=..., covariant=False, contravariant=False)
14271445

14281446
Parameter specification variable. A specialized version of
14291447
:class:`type variables <TypeVar>`.

Lib/test/test_typing.py

Lines changed: 47 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,35 @@
1-
import contextlib
1+
import abc
22
import collections
3-
from collections import defaultdict
4-
from functools import lru_cache, wraps
3+
import contextlib
54
import inspect
65
import itertools
76
import pickle
87
import re
98
import sys
10-
import warnings
11-
from unittest import TestCase, main, skipUnless, skip
12-
from unittest.mock import patch
13-
from copy import copy, deepcopy
14-
15-
from typing import Any, NoReturn, Never, assert_never
16-
from typing import overload, get_overloads, clear_overloads
17-
from typing import TypeVar, TypeVarTuple, Unpack, AnyStr
18-
from typing import T, KT, VT # Not in __all__.
19-
from typing import Union, Optional, Literal
20-
from typing import Tuple, List, Dict, MutableMapping
21-
from typing import Callable
22-
from typing import Generic, ClassVar, Final, final, Protocol
23-
from typing import assert_type, cast, runtime_checkable
24-
from typing import get_type_hints
25-
from typing import get_origin, get_args
26-
from typing import override
27-
from typing import is_typeddict
28-
from typing import reveal_type
29-
from typing import dataclass_transform
30-
from typing import no_type_check, no_type_check_decorator
31-
from typing import Type
32-
from typing import NamedTuple, NotRequired, Required, TypedDict
33-
from typing import IO, TextIO, BinaryIO
34-
from typing import Pattern, Match
35-
from typing import Annotated, ForwardRef
36-
from typing import Self, LiteralString
37-
from typing import TypeAlias
38-
from typing import ParamSpec, Concatenate, ParamSpecArgs, ParamSpecKwargs
39-
from typing import TypeGuard
40-
import abc
419
import textwrap
10+
import types
4211
import typing
12+
import warnings
4313
import weakref
44-
import types
45-
46-
from test.support import import_helper, captured_stderr, cpython_only
47-
from test import mod_generics_cache
48-
from test import _typed_dict_helper
49-
14+
from collections import defaultdict
15+
from copy import copy, deepcopy
16+
from functools import lru_cache, wraps
17+
from test import _typed_dict_helper, mod_generics_cache
18+
from test.support import captured_stderr, cpython_only, import_helper
19+
from typing import (IO, KT, VT, Annotated, Any, AnyStr, # Not in __all__.
20+
BinaryIO, Callable, ClassVar, Concatenate, Dict, Final,
21+
ForwardRef, Generic, List, Literal, LiteralString, Match,
22+
MutableMapping, NamedTuple, Never, NoReturn, NotRequired,
23+
Optional, ParamSpec, ParamSpecArgs, ParamSpecKwargs,
24+
Pattern, Protocol, Required, Self, T, TextIO, Tuple, Type,
25+
TypeAlias, TypedDict, TypeGuard, TypeVar, TypeVarTuple,
26+
Union, Unpack, assert_never, assert_type, cast,
27+
clear_overloads, dataclass_transform, final, get_args,
28+
get_origin, get_overloads, get_type_hints, is_typeddict,
29+
no_type_check, no_type_check_decorator, overload, override,
30+
reveal_type, runtime_checkable)
31+
from unittest import TestCase, main, skip, skipUnless
32+
from unittest.mock import patch
5033

5134
py_typing = import_helper.import_fresh_module('typing', blocked=['_typing'])
5235
c_typing = import_helper.import_fresh_module('typing', fresh=['_typing'])
@@ -447,6 +430,22 @@ def test_bound_errors(self):
447430
with self.assertRaises(TypeError):
448431
TypeVar('X', str, float, bound=Employee)
449432

433+
def test_default_error(self):
434+
with self.assertRaises(TypeError):
435+
TypeVar('X', default=Union)
436+
437+
def test_default_ordering(self):
438+
T = TypeVar("T")
439+
U = TypeVar("U", default=int)
440+
V = TypeVar("V", default=float)
441+
442+
class Foo(Generic[T, U]): ...
443+
with self.assertRaises(TypeError):
444+
class Bar(Generic[U, T]): ...
445+
446+
class Baz(Foo[V]): ...
447+
448+
450449
def test_missing__name__(self):
451450
# See bpo-39942
452451
code = ("import typing\n"
@@ -3698,22 +3697,24 @@ def test_immutability_by_copy_and_pickle(self):
36983697
TPB = TypeVar('TPB', bound=int)
36993698
TPV = TypeVar('TPV', bytes, str)
37003699
PP = ParamSpec('PP')
3701-
for X in [TP, TPB, TPV, PP,
3700+
TD = TypeVar('TD', default=int)
3701+
for X in [TP, TPB, TPV, PP, TD,
37023702
List, typing.Mapping, ClassVar, typing.Iterable,
37033703
Union, Any, Tuple, Callable]:
37043704
with self.subTest(thing=X):
37053705
self.assertIs(copy(X), X)
37063706
self.assertIs(deepcopy(X), X)
37073707
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
37083708
self.assertIs(pickle.loads(pickle.dumps(X, proto)), X)
3709-
del TP, TPB, TPV, PP
3709+
del TP, TPB, TPV, PP, TD
37103710

37113711
# Check that local type variables are copyable.
37123712
TL = TypeVar('TL')
37133713
TLB = TypeVar('TLB', bound=int)
37143714
TLV = TypeVar('TLV', bytes, str)
37153715
PL = ParamSpec('PL')
3716-
for X in [TL, TLB, TLV, PL]:
3716+
TDL = TypeVar('TDL', default=int)
3717+
for X in [TL, TLB, TLV, PL, TDL]:
37173718
with self.subTest(thing=X):
37183719
self.assertIs(copy(X), X)
37193720
self.assertIs(deepcopy(X), X)
@@ -4415,6 +4416,7 @@ def test_errors(self):
44154416
# We need this to make sure that `@no_type_check` respects `__module__` attr:
44164417
from test import ann_module8
44174418

4419+
44184420
@no_type_check
44194421
class NoTypeCheck_Outer:
44204422
Inner = ann_module8.NoTypeCheck_Outer.Inner
@@ -6981,7 +6983,7 @@ def stuff(a: BinaryIO) -> bytes:
69816983
def test_io_submodule(self):
69826984
with warnings.catch_warnings(record=True) as w:
69836985
warnings.filterwarnings("default", category=DeprecationWarning)
6984-
from typing.io import IO, TextIO, BinaryIO, __all__, __name__
6986+
from typing.io import IO, BinaryIO, TextIO, __all__, __name__
69856987
self.assertIs(IO, typing.IO)
69866988
self.assertIs(TextIO, typing.TextIO)
69876989
self.assertIs(BinaryIO, typing.BinaryIO)
@@ -8058,6 +8060,7 @@ class AllTests(BaseTestCase):
80588060

80598061
def test_all(self):
80608062
from typing import __all__ as a
8063+
80618064
# Just spot-check the first and last of every category.
80628065
self.assertIn('AbstractSet', a)
80638066
self.assertIn('ValuesView', a)

Lib/typing.py

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -250,16 +250,27 @@ def _collect_parameters(args):
250250
_collect_parameters((T, Callable[P, T])) == (T, P)
251251
"""
252252
parameters = []
253+
seen_default = False
253254
for t in args:
254255
# We don't want __parameters__ descriptor of a bare Python class.
255256
if isinstance(t, type):
256257
continue
257258
if hasattr(t, '__typing_subst__'):
258259
if t not in parameters:
260+
if t.__default__ is not None:
261+
seen_default = True
262+
elif seen_default:
263+
raise TypeError("TypeVarLike without a default follows one with a default")
264+
259265
parameters.append(t)
260266
else:
261267
for x in getattr(t, '__parameters__', ()):
262268
if x not in parameters:
269+
if x.__default__ is not None:
270+
seen_default = True
271+
elif seen_default:
272+
raise TypeError("TypeVarLike without a default follows one with a default")
273+
263274
parameters.append(x)
264275
return tuple(parameters)
265276

@@ -956,7 +967,24 @@ def __mro_entries__(self, bases):
956967
raise TypeError(f"Cannot subclass an instance of {type(self).__name__}")
957968

958969

959-
class TypeVar(_Final, _Immutable, _BoundVarianceMixin, _PickleUsingNameMixin,
970+
_marker = object()
971+
972+
973+
class _DefaultMixin:
974+
"""Mixin for TypeVarLike defaults."""
975+
976+
def __init__(self, default):
977+
if isinstance(default, (tuple, list)):
978+
self.__default__ = default.__class__(
979+
(_type_check(d, "Defaults must all be types") for d in default)
980+
)
981+
elif default != _marker:
982+
self.__default__ = _type_check(default, "Default must be a type")
983+
else:
984+
self.__default__ = None
985+
986+
987+
class TypeVar(_Final, _Immutable, _BoundVarianceMixin, _PickleUsingNameMixin, _DefaultMixin,
960988
_root=True):
961989
"""Type variable.
962990
@@ -1001,10 +1029,11 @@ def longest(x: A, y: A) -> A:
10011029
Note that only type variables defined in global scope can be pickled.
10021030
"""
10031031

1004-
def __init__(self, name, *constraints, bound=None,
1032+
def __init__(self, name, *constraints, bound=None, default=_marker,
10051033
covariant=False, contravariant=False):
10061034
self.__name__ = name
10071035
super().__init__(bound, covariant, contravariant)
1036+
_DefaultMixin.__init__(self, default)
10081037
if constraints and bound is not None:
10091038
raise TypeError("Constraints cannot be combined with bound=...")
10101039
if constraints and len(constraints) == 1:
@@ -1024,7 +1053,7 @@ def __typing_subst__(self, arg):
10241053
return arg
10251054

10261055

1027-
class TypeVarTuple(_Final, _Immutable, _PickleUsingNameMixin, _root=True):
1056+
class TypeVarTuple(_Final, _Immutable, _PickleUsingNameMixin, _DefaultMixin, _root=True):
10281057
"""Type variable tuple.
10291058
10301059
Usage:
@@ -1049,8 +1078,9 @@ class C(Generic[*Ts]): ...
10491078
Note that only TypeVarTuples defined in global scope can be pickled.
10501079
"""
10511080

1052-
def __init__(self, name):
1081+
def __init__(self, name, *, default=_marker):
10531082
self.__name__ = name
1083+
_DefaultMixin.__init__(self, default)
10541084

10551085
# Used for pickling.
10561086
def_mod = _caller()
@@ -1160,7 +1190,7 @@ def __mro_entries__(self, bases):
11601190
raise TypeError(f"Cannot subclass an instance of {type(self).__name__}")
11611191

11621192

1163-
class ParamSpec(_Final, _Immutable, _BoundVarianceMixin, _PickleUsingNameMixin,
1193+
class ParamSpec(_Final, _Immutable, _BoundVarianceMixin, _PickleUsingNameMixin, _DefaultMixin,
11641194
_root=True):
11651195
"""Parameter specification variable.
11661196
@@ -1215,9 +1245,10 @@ def args(self):
12151245
def kwargs(self):
12161246
return ParamSpecKwargs(self)
12171247

1218-
def __init__(self, name, *, bound=None, covariant=False, contravariant=False):
1248+
def __init__(self, name, *, bound=None, default=None, covariant=False, contravariant=False):
12191249
self.__name__ = name
12201250
super().__init__(bound, covariant, contravariant)
1251+
_DefaultMixin.__init__(self, default)
12211252
def_mod = _caller()
12221253
if def_mod != 'typing':
12231254
self.__module__ = def_mod

0 commit comments

Comments
 (0)