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 number Diff line number Diff 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 number Diff line number Diff 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 number Diff line number Diff 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 Down Expand 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 number Diff line number Diff 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 Down Expand 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 Down Expand 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 Down Expand 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 Down Expand 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 Down Expand 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 number Diff line number Diff 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 number Diff line number Diff 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 Down Expand 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