Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion Include/internal/pycore_global_strings.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -204,7 +204,6 @@ struct _Py_global_strings{
STRUCT_FOR_ID(__trunc__)
STRUCT_FOR_ID(__typing_is_unpacked_typevartuple__)
STRUCT_FOR_ID(__typing_prepare_subst__)
STRUCT_FOR_ID(__typing_subst__)
STRUCT_FOR_ID(__typing_unpacked_tuple_args__)
STRUCT_FOR_ID(__warningregistry__)
STRUCT_FOR_ID(__weaklistoffset__)
Expand Down
1 change: 0 additions & 1 deletion Include/internal/pycore_runtime_init.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -826,7 +826,6 @@ extern "C"{
INIT_ID(__trunc__), \
INIT_ID(__typing_is_unpacked_typevartuple__), \
INIT_ID(__typing_prepare_subst__), \
INIT_ID(__typing_subst__), \
INIT_ID(__typing_unpacked_tuple_args__), \
INIT_ID(__warningregistry__), \
INIT_ID(__weaklistoffset__), \
Expand Down
41 changes: 22 additions & 19 deletions Lib/test/test_typing.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -448,16 +448,18 @@ def test_no_bivariant(self):

def test_var_substitution(self):
T = TypeVar('T')
subst = T.__typing_subst__
self.assertIs(subst(int), int)
self.assertEqual(subst(list[int]), list[int])
self.assertEqual(subst(List[int]), List[int])
self.assertEqual(subst(List), List)
self.assertIs(subst(Any), Any)
self.assertIs(subst(None), type(None))
self.assertIs(subst(T), T)
self.assertEqual(subst(int|str), int|str)
self.assertEqual(subst(Union[int, str]), Union[int, str])
self.assertEqual(T.__parameters__, (T,))
self.assertIs(T.__parameters__[0], T)
self.assertIs(T[int,], int)
self.assertEqual(T[list[int],], list[int])
self.assertEqual(T[List[int],], List[int])
self.assertEqual(T[List,], List)
self.assertIs(T[Any,], Any)
self.assertIs(T[None,], type(None))
self.assertIs(T[T,], T)
self.assertIs(T[(int,)], int)
self.assertEqual(T[int|str,], int|str)
self.assertEqual(T[Union[int, str],], Union[int, str])

def test_bad_var_substitution(self):
T = TypeVar('T')
Expand All@@ -470,7 +472,7 @@ def test_bad_var_substitution(self):
for arg in bad_args:
with self.subTest(arg=arg):
with self.assertRaises(TypeError):
T.__typing_subst__(arg)
T[arg,]
with self.assertRaises(TypeError):
List[T][arg]
with self.assertRaises(TypeError):
Expand DownExpand Up@@ -6819,13 +6821,14 @@ class X(Generic[P, P2]):
def test_var_substitution(self):
T = TypeVar("T")
P = ParamSpec("P")
subst = P.__typing_subst__
self.assertEqual(subst((int, str)), (int, str))
self.assertEqual(subst([int, str]), (int, str))
self.assertEqual(subst([None]), (type(None),))
self.assertIs(subst(...), ...)
self.assertIs(subst(P), P)
self.assertEqual(subst(Concatenate[int, P]), Concatenate[int, P])
self.assertEqual(P.__parameters__, (P,))
self.assertIs(P.__parameters__[0], P)
self.assertEqual(P[(int, str),], (int, str))
self.assertEqual(P[[int, str],], (int, str))
self.assertEqual(P[[None],], (type(None),))
self.assertIs(P[...,], ...)
self.assertIs(P[P,], P)
self.assertEqual(P[Concatenate[int, P],], Concatenate[int, P])

def test_bad_var_substitution(self):
T = TypeVar('T')
Expand All@@ -6834,7 +6837,7 @@ def test_bad_var_substitution(self):
for arg in bad_args:
with self.subTest(arg=arg):
with self.assertRaises(TypeError):
P.__typing_subst__(arg)
P[arg,]
with self.assertRaises(TypeError):
typing.Callable[P, T][arg, str]
with self.assertRaises(TypeError):
Expand Down
51 changes: 27 additions & 24 deletions Lib/typing.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -250,13 +250,9 @@ def _collect_parameters(args):
"""
parameters = []
for t in args:
if hasattr(t, '__typing_subst__'):
if t not in parameters:
parameters.append(t)
else:
for x in getattr(t, '__parameters__', ()):
if x not in parameters:
parameters.append(x)
for x in getattr(t, '__parameters__', ()):
if x not in parameters:
parameters.append(x)
return tuple(parameters)


Expand DownExpand Up@@ -954,6 +950,9 @@ def __repr__(self):
prefix = '~'
return prefix + self.__name__

@property
def __parameters__(self):
return (self,)

class TypeVar(_Final, _Immutable, _BoundVarianceMixin, _PickleUsingNameMixin,
_root=True):
Expand DownExpand Up@@ -1014,7 +1013,9 @@ def __init__(self, name, *constraints, bound=None,
if def_mod != 'typing':
self.__module__ = def_mod

def __typing_subst__(self, arg):
def __getitem__(self, arg):
if isinstance(arg, tuple) and len(arg) == 1:
arg, = arg
msg = "Parameters to generic types must be types."
arg = _type_check(arg, msg, is_argument=True)
if ((isinstance(arg, _GenericAlias) and arg.__origin__ is Unpack) or
Expand DownExpand Up@@ -1062,9 +1063,13 @@ def __iter__(self):
def __repr__(self):
return self.__name__

def __typing_subst__(self, arg):
def __getitem__(self, arg):
raise TypeError("Substitution of bare TypeVarTuple is not supported")

@property
def __parameters__(self):
return (self,)

def __typing_prepare_subst__(self, alias, args):
params = alias.__parameters__
typevartuple_index = params.index(self)
Expand DownExpand Up@@ -1212,7 +1217,9 @@ def __init__(self, name, *, bound=None, covariant=False, contravariant=False):
if def_mod != 'typing':
self.__module__ = def_mod

def __typing_subst__(self, arg):
def __getitem__(self, arg):
if isinstance(arg, tuple) and len(arg) == 1:
arg, = arg
if isinstance(arg, (list, tuple)):
arg = tuple(_type_check(a, "Expected a type.") for a in arg)
elif not _is_param_expr(arg):
Expand DownExpand Up@@ -1420,21 +1427,17 @@ def _determine_new_args(self, args):
new_args = []
for old_arg in self.__args__:

substfunc = getattr(old_arg, '__typing_subst__', None)
if substfunc:
new_arg = substfunc(new_arg_by_param[old_arg])
subparams = getattr(old_arg, '__parameters__', ())
if not subparams:
new_arg = old_arg
else:
subparams = getattr(old_arg, '__parameters__', ())
if not subparams:
new_arg = old_arg
else:
subargs = []
for x in subparams:
if isinstance(x, TypeVarTuple):
subargs.extend(new_arg_by_param[x])
else:
subargs.append(new_arg_by_param[x])
new_arg = old_arg[tuple(subargs)]
subargs = []
for x in subparams:
if isinstance(x, TypeVarTuple):
subargs.extend(new_arg_by_param[x])
else:
subargs.append(new_arg_by_param[x])
new_arg = old_arg[tuple(subargs)]

if self.__origin__ == collections.abc.Callable and isinstance(new_arg, tuple):
# Consider the following `Callable`.
Expand Down
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
Add ``__parameters__`` and ``__getitem__`` in :class:`~typing.TypeVar` and
:class:`~ParamSpec`.
61 changes: 18 additions & 43 deletions Objects/genericaliasobject.c
Original file line numberDiff line numberDiff line change
Expand Up@@ -218,40 +218,29 @@ _Py_make_parameters(PyObject *args)
Py_ssize_t iparam = 0;
for (Py_ssize_t iarg = 0; iarg < nargs; iarg++){
PyObject *t = PyTuple_GET_ITEM(args, iarg);
PyObject *subst;
if (_PyObject_LookupAttr(t, &_Py_ID(__typing_subst__), &subst) < 0){
PyObject *subparams;
if (_PyObject_LookupAttr(t, &_Py_ID(__parameters__),
&subparams) < 0){
Py_DECREF(parameters);
return NULL;
}
if (subst){
iparam += tuple_add(parameters, iparam, t);
Py_DECREF(subst);
}
else{
PyObject *subparams;
if (_PyObject_LookupAttr(t, &_Py_ID(__parameters__),
&subparams) < 0){
Py_DECREF(parameters);
return NULL;
}
if (subparams && PyTuple_Check(subparams)){
Py_ssize_t len2 = PyTuple_GET_SIZE(subparams);
Py_ssize_t needed = len2 - 1 - (iarg - iparam);
if (needed > 0){
len += needed;
if (_PyTuple_Resize(&parameters, len) < 0){
Py_DECREF(subparams);
Py_DECREF(parameters);
return NULL;
}
}
for (Py_ssize_t j = 0; j < len2; j++){
PyObject *t2 = PyTuple_GET_ITEM(subparams, j);
iparam += tuple_add(parameters, iparam, t2);
if (subparams && PyTuple_Check(subparams)){
Py_ssize_t len2 = PyTuple_GET_SIZE(subparams);
Py_ssize_t needed = len2 - 1 - (iarg - iparam);
if (needed > 0){
len += needed;
if (_PyTuple_Resize(&parameters, len) < 0){
Py_DECREF(subparams);
Py_DECREF(parameters);
return NULL;
}
}
Py_XDECREF(subparams);
for (Py_ssize_t j = 0; j < len2; j++){
PyObject *t2 = PyTuple_GET_ITEM(subparams, j);
iparam += tuple_add(parameters, iparam, t2);
}
}
Py_XDECREF(subparams);
}
if (iparam < len){
if (_PyTuple_Resize(&parameters, iparam) < 0){
Expand DownExpand Up@@ -460,21 +449,7 @@ _Py_subs_parameters(PyObject *self, PyObject *args, PyObject *parameters, PyObje
Py_DECREF(item);
return NULL;
}
PyObject *subst;
if (_PyObject_LookupAttr(arg, &_Py_ID(__typing_subst__), &subst) < 0){
Py_DECREF(newargs);
Py_DECREF(item);
return NULL;
}
if (subst){
Py_ssize_t iparam = tuple_index(parameters, nparams, arg);
assert(iparam >= 0);
arg = PyObject_CallOneArg(subst, argitems[iparam]);
Py_DECREF(subst);
}
else{
arg = subs_tvars(arg, parameters, argitems, nitems);
}
arg = subs_tvars(arg, parameters, argitems, nitems);
if (arg == NULL){
Py_DECREF(newargs);
Py_DECREF(item);
Expand Down