bpo-41559: Implement PEP 612 - Add ParamSpec and Concatenate to typing (#23702)

This commit is contained in:
kj 2020-12-24 12:33:48 +08:00 committed by GitHub
parent cc3467a57b
commit 73607be686
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 381 additions and 75 deletions

View File

@ -416,7 +416,7 @@ class Collection(Sized, Iterable, Container):
class _CallableGenericAlias(GenericAlias):
""" Represent `Callable[argtypes, resulttype]`.
This sets ``__args__`` to a tuple containing the flattened``argtypes``
This sets ``__args__`` to a tuple containing the flattened ``argtypes``
followed by ``resulttype``.
Example: ``Callable[[int, str], float]`` sets ``__args__`` to
@ -444,7 +444,7 @@ class _CallableGenericAlias(GenericAlias):
return super().__new__(cls, origin, ga_args)
def __repr__(self):
if len(self.__args__) == 2 and self.__args__[0] is Ellipsis:
if _has_special_args(self.__args__):
return super().__repr__()
return (f'collections.abc.Callable'
f'[[{", ".join([_type_repr(a) for a in self.__args__[:-1]])}], '
@ -452,7 +452,7 @@ class _CallableGenericAlias(GenericAlias):
def __reduce__(self):
args = self.__args__
if not (len(args) == 2 and args[0] is Ellipsis):
if not _has_special_args(args):
args = list(args[:-1]), args[-1]
return _CallableGenericAlias, (Callable, args)
@ -461,12 +461,28 @@ class _CallableGenericAlias(GenericAlias):
# rather than the default types.GenericAlias object.
ga = super().__getitem__(item)
args = ga.__args__
t_result = args[-1]
t_args = args[:-1]
args = (t_args, t_result)
# args[0] occurs due to things like Z[[int, str, bool]] from PEP 612
if not isinstance(ga.__args__[0], tuple):
t_result = ga.__args__[-1]
t_args = ga.__args__[:-1]
args = (t_args, t_result)
return _CallableGenericAlias(Callable, args)
def _has_special_args(args):
"""Checks if args[0] matches either ``...``, ``ParamSpec`` or
``_ConcatenateGenericAlias`` from typing.py
"""
if len(args) != 2:
return False
obj = args[0]
if obj is Ellipsis:
return True
obj = type(obj)
names = ('ParamSpec', '_ConcatenateGenericAlias')
return obj.__module__ == 'typing' and any(obj.__name__ == name for name in names)
def _type_repr(obj):
"""Return the repr() of an object, special-casing types (internal helper).

View File

@ -369,6 +369,27 @@ class BaseTest(unittest.TestCase):
self.assertEqual(c1.__args__, c2.__args__)
self.assertEqual(hash(c1.__args__), hash(c2.__args__))
with self.subTest("Testing ParamSpec uses"):
P = typing.ParamSpec('P')
C1 = Callable[P, T]
# substitution
self.assertEqual(C1[int, str], Callable[[int], str])
self.assertEqual(C1[[int, str], str], Callable[[int, str], str])
self.assertEqual(repr(C1).split(".")[-1], "Callable[~P, ~T]")
self.assertEqual(repr(C1[int, str]).split(".")[-1], "Callable[[int], str]")
C2 = Callable[P, int]
# special case in PEP 612 where
# X[int, str, float] == X[[int, str, float]]
self.assertEqual(C2[int, str, float], C2[[int, str, float]])
self.assertEqual(repr(C2).split(".")[-1], "Callable[~P, int]")
self.assertEqual(repr(C2[int, str]).split(".")[-1], "Callable[[int, str], int]")
with self.subTest("Testing Concatenate uses"):
P = typing.ParamSpec('P')
C1 = Callable[typing.Concatenate[int, P], int]
self.assertEqual(repr(C1), "collections.abc.Callable"
"[typing.Concatenate[int, ~P], int]")
if __name__ == "__main__":
unittest.main()

View File

@ -25,6 +25,7 @@ from typing import IO, TextIO, BinaryIO
from typing import Pattern, Match
from typing import Annotated, ForwardRef
from typing import TypeAlias
from typing import ParamSpec, Concatenate
import abc
import typing
import weakref
@ -1130,10 +1131,6 @@ class ProtocolTests(BaseTestCase):
PR[int]
with self.assertRaises(TypeError):
P[int, str]
with self.assertRaises(TypeError):
PR[int, 1]
with self.assertRaises(TypeError):
PR[int, ClassVar]
class C(PR[int, T]): pass
@ -1155,8 +1152,6 @@ class ProtocolTests(BaseTestCase):
self.assertIsSubclass(P, PR)
with self.assertRaises(TypeError):
PR[int]
with self.assertRaises(TypeError):
PR[int, 1]
class P1(Protocol, Generic[T]):
def bar(self, x: T) -> str: ...
@ -1175,8 +1170,6 @@ class ProtocolTests(BaseTestCase):
return x
self.assertIsInstance(Test(), PSub)
with self.assertRaises(TypeError):
PR[int, ClassVar]
def test_init_called(self):
T = TypeVar('T')
@ -1746,8 +1739,6 @@ class GenericTests(BaseTestCase):
self.assertEqual(typing.Iterable[Tuple[T, T]][T], typing.Iterable[Tuple[T, T]])
with self.assertRaises(TypeError):
Tuple[T, int][()]
with self.assertRaises(TypeError):
Tuple[T, U][T, ...]
self.assertEqual(Union[T, int][int], int)
self.assertEqual(Union[T, U][int, Union[int, str]], Union[int, str])
@ -1759,10 +1750,6 @@ class GenericTests(BaseTestCase):
self.assertEqual(Callable[[T], T][KT], Callable[[KT], KT])
self.assertEqual(Callable[..., List[T]][int], Callable[..., List[int]])
with self.assertRaises(TypeError):
Callable[[T], U][..., int]
with self.assertRaises(TypeError):
Callable[[T], U][[], int]
def test_extended_generic_rules_repr(self):
T = TypeVar('T')
@ -4243,6 +4230,111 @@ class TypeAliasTests(BaseTestCase):
TypeAlias[int]
class ParamSpecTests(BaseTestCase):
def test_basic_plain(self):
P = ParamSpec('P')
self.assertEqual(P, P)
self.assertIsInstance(P, ParamSpec)
def test_valid_uses(self):
P = ParamSpec('P')
T = TypeVar('T')
C1 = Callable[P, int]
self.assertEqual(C1.__args__, (P, int))
self.assertEqual(C1.__parameters__, (P,))
C2 = Callable[P, T]
self.assertEqual(C2.__args__, (P, T))
self.assertEqual(C2.__parameters__, (P, T))
# Test collections.abc.Callable too.
C3 = collections.abc.Callable[P, int]
self.assertEqual(C3.__args__, (P, int))
self.assertEqual(C3.__parameters__, (P,))
C4 = collections.abc.Callable[P, T]
self.assertEqual(C4.__args__, (P, T))
self.assertEqual(C4.__parameters__, (P, T))
# ParamSpec instances should also have args and kwargs attributes.
self.assertIn('args', dir(P))
self.assertIn('kwargs', dir(P))
P.args
P.kwargs
def test_user_generics(self):
T = TypeVar("T")
P = ParamSpec("P")
P_2 = ParamSpec("P_2")
class X(Generic[T, P]):
f: Callable[P, int]
x: T
G1 = X[int, P_2]
self.assertEqual(G1.__args__, (int, P_2))
self.assertEqual(G1.__parameters__, (P_2,))
G2 = X[int, Concatenate[int, P_2]]
self.assertEqual(G2.__args__, (int, Concatenate[int, P_2]))
self.assertEqual(G2.__parameters__, (P_2,))
G3 = X[int, [int, bool]]
self.assertEqual(G3.__args__, (int, (int, bool)))
self.assertEqual(G3.__parameters__, ())
G4 = X[int, ...]
self.assertEqual(G4.__args__, (int, Ellipsis))
self.assertEqual(G4.__parameters__, ())
class Z(Generic[P]):
f: Callable[P, int]
G5 = Z[[int, str, bool]]
self.assertEqual(G5.__args__, ((int, str, bool),))
self.assertEqual(G5.__parameters__, ())
G6 = Z[int, str, bool]
self.assertEqual(G6.__args__, ((int, str, bool),))
self.assertEqual(G6.__parameters__, ())
# G5 and G6 should be equivalent according to the PEP
self.assertEqual(G5.__args__, G6.__args__)
self.assertEqual(G5.__origin__, G6.__origin__)
self.assertEqual(G5.__parameters__, G6.__parameters__)
self.assertEqual(G5, G6)
def test_var_substitution(self):
T = TypeVar("T")
P = ParamSpec("P")
C1 = Callable[P, T]
self.assertEqual(C1[int, str], Callable[[int], str])
self.assertEqual(C1[[int, str, dict], float], Callable[[int, str, dict], float])
class ConcatenateTests(BaseTestCase):
def test_basics(self):
P = ParamSpec('P')
class MyClass: ...
c = Concatenate[MyClass, P]
self.assertNotEqual(c, Concatenate)
def test_valid_uses(self):
P = ParamSpec('P')
T = TypeVar('T')
C1 = Callable[Concatenate[int, P], int]
self.assertEqual(C1.__args__, (Concatenate[int, P], int))
self.assertEqual(C1.__parameters__, (P,))
C2 = Callable[Concatenate[int, T, P], T]
self.assertEqual(C2.__args__, (Concatenate[int, T, P], T))
self.assertEqual(C2.__parameters__, (T, P))
# Test collections.abc.Callable too.
C3 = collections.abc.Callable[Concatenate[int, P], int]
self.assertEqual(C3.__args__, (Concatenate[int, P], int))
self.assertEqual(C3.__parameters__, (P,))
C4 = collections.abc.Callable[Concatenate[int, T, P], T]
self.assertEqual(C4.__args__, (Concatenate[int, T, P], T))
self.assertEqual(C4.__parameters__, (T, P))
class AllTests(BaseTestCase):
"""Tests for __all__."""

View File

@ -4,8 +4,10 @@ The typing module: Support for gradual typing as defined by PEP 484.
At large scale, the structure of the module is following:
* Imports and exports, all public names should be explicitly added to __all__.
* Internal helper functions: these should never be used in code outside this module.
* _SpecialForm and its instances (special forms): Any, NoReturn, ClassVar, Union, Optional
* Two classes whose instances can be type arguments in addition to types: ForwardRef and TypeVar
* _SpecialForm and its instances (special forms):
Any, NoReturn, ClassVar, Union, Optional, Concatenate
* Classes whose instances can be type arguments in addition to types:
ForwardRef, TypeVar and ParamSpec
* The core of internal generics API: _GenericAlias and _VariadicGenericAlias, the latter is
currently only used by Tuple and Callable. All subscripted types like X[int], Union[int, str],
etc., are instances of either of these classes.
@ -36,11 +38,13 @@ __all__ = [
'Any',
'Callable',
'ClassVar',
'Concatenate',
'Final',
'ForwardRef',
'Generic',
'Literal',
'Optional',
'ParamSpec',
'Protocol',
'Tuple',
'Type',
@ -154,7 +158,7 @@ def _type_check(arg, msg, is_argument=True):
return arg
if isinstance(arg, _SpecialForm) or arg in (Generic, Protocol):
raise TypeError(f"Plain {arg} is not valid as type argument")
if isinstance(arg, (type, TypeVar, ForwardRef, types.Union)):
if isinstance(arg, (type, TypeVar, ForwardRef, types.Union, ParamSpec)):
return arg
if not callable(arg):
raise TypeError(f"{msg} Got {arg!r:.100}.")
@ -183,14 +187,14 @@ def _type_repr(obj):
def _collect_type_vars(types):
"""Collect all type variable contained in types in order of
first appearance (lexicographic order). For example::
"""Collect all type variable-like variables contained
in types in order of first appearance (lexicographic order). For example::
_collect_type_vars((T, List[S, T])) == (T, S)
"""
tvars = []
for t in types:
if isinstance(t, TypeVar) and t not in tvars:
if isinstance(t, _TypeVarLike) and t not in tvars:
tvars.append(t)
if isinstance(t, (_GenericAlias, GenericAlias)):
tvars.extend([t for t in t.__parameters__ if t not in tvars])
@ -208,6 +212,21 @@ def _check_generic(cls, parameters, elen):
raise TypeError(f"Too {'many' if alen > elen else 'few'} parameters for {cls};"
f" actual {alen}, expected {elen}")
def _prepare_paramspec_params(cls, params):
"""Prepares the parameters for a Generic containing ParamSpec
variables (internal helper).
"""
# Special case where Z[[int, str, bool]] == Z[int, str, bool] in PEP 612.
if len(cls.__parameters__) == 1 and len(params) > 1:
return (params,)
else:
_params = []
# Convert lists to tuples to help other libraries cache the results.
for p, tvar in zip(params, cls.__parameters__):
if isinstance(tvar, ParamSpec) and isinstance(p, list):
p = tuple(p)
_params.append(p)
return tuple(_params)
def _deduplicate(params):
# Weed out strict duplicates, preserving the first of each occurrence.
@ -523,6 +542,29 @@ def TypeAlias(self, parameters):
raise TypeError(f"{self} is not subscriptable")
@_SpecialForm
def Concatenate(self, parameters):
"""Used in conjunction with ParamSpec and Callable to represent a higher
order function which adds, removes or transforms parameters of a Callable.
For example::
Callable[Concatenate[int, P], int]
See PEP 612 for detailed information.
"""
if parameters == ():
raise TypeError("Cannot take a Concatenate of no types.")
if not isinstance(parameters, tuple):
parameters = (parameters,)
if not isinstance(parameters[-1], ParamSpec):
raise TypeError("The last parameter to Concatenate should be a "
"ParamSpec variable.")
msg = "Concatenate[arg, ...]: each arg must be a type."
parameters = tuple(_type_check(p, msg) for p in parameters)
return _ConcatenateGenericAlias(self, parameters)
class ForwardRef(_Final, _root=True):
"""Internal wrapper to hold a forward reference."""
@ -585,8 +627,41 @@ class ForwardRef(_Final, _root=True):
def __repr__(self):
return f'ForwardRef({self.__forward_arg__!r})'
class _TypeVarLike:
"""Mixin for TypeVar-like types (TypeVar and ParamSpec)."""
def __init__(self, bound, covariant, contravariant):
"""Used to setup TypeVars and ParamSpec's bound, covariant and
contravariant attributes.
"""
if covariant and contravariant:
raise ValueError("Bivariant types are not supported.")
self.__covariant__ = bool(covariant)
self.__contravariant__ = bool(contravariant)
if bound:
self.__bound__ = _type_check(bound, "Bound must be a type.")
else:
self.__bound__ = None
class TypeVar(_Final, _Immutable, _root=True):
def __or__(self, right):
return Union[self, right]
def __ror__(self, right):
return Union[self, right]
def __repr__(self):
if self.__covariant__:
prefix = '+'
elif self.__contravariant__:
prefix = '-'
else:
prefix = '~'
return prefix + self.__name__
def __reduce__(self):
return self.__name__
class TypeVar( _Final, _Immutable, _TypeVarLike, _root=True):
"""Type variable.
Usage::
@ -636,20 +711,13 @@ class TypeVar(_Final, _Immutable, _root=True):
def __init__(self, name, *constraints, bound=None,
covariant=False, contravariant=False):
self.__name__ = name
if covariant and contravariant:
raise ValueError("Bivariant types are not supported.")
self.__covariant__ = bool(covariant)
self.__contravariant__ = bool(contravariant)
super().__init__(bound, covariant, contravariant)
if constraints and bound is not None:
raise TypeError("Constraints cannot be combined with bound=...")
if constraints and len(constraints) == 1:
raise TypeError("A single constraint is not allowed")
msg = "TypeVar(name, constraint, ...): constraints must be types."
self.__constraints__ = tuple(_type_check(t, msg) for t in constraints)
if bound:
self.__bound__ = _type_check(bound, "Bound must be a type.")
else:
self.__bound__ = None
try:
def_mod = sys._getframe(1).f_globals.get('__name__', '__main__') # for pickling
except (AttributeError, ValueError):
@ -657,23 +725,68 @@ class TypeVar(_Final, _Immutable, _root=True):
if def_mod != 'typing':
self.__module__ = def_mod
def __or__(self, right):
return Union[self, right]
def __ror__(self, right):
return Union[self, right]
class ParamSpec(_Final, _Immutable, _TypeVarLike, _root=True):
"""Parameter specification variable.
def __repr__(self):
if self.__covariant__:
prefix = '+'
elif self.__contravariant__:
prefix = '-'
else:
prefix = '~'
return prefix + self.__name__
Usage::
def __reduce__(self):
return self.__name__
P = ParamSpec('P')
Parameter specification variables exist primarily for the benefit of static
type checkers. They are used to forward the parameter types of one
Callable to another Callable, a pattern commonly found in higher order
functions and decorators. They are only valid when used in Concatenate, or
as the first argument to Callable, or as parameters for user-defined Generics.
See class Generic for more information on generic types. An example for
annotating a decorator::
T = TypeVar('T')
P = ParamSpec('P')
def add_logging(f: Callable[P, T]) -> Callable[P, T]:
'''A type-safe decorator to add logging to a function.'''
def inner(*args: P.args, **kwargs: P.kwargs) -> T:
logging.info(f'{f.__name__} was called')
return f(*args, **kwargs)
return inner
@add_logging
def add_two(x: float, y: float) -> float:
'''Add two numbers together.'''
return x + y
Parameter specification variables defined with covariant=True or
contravariant=True can be used to declare covariant or contravariant
generic types. These keyword arguments are valid, but their actual semantics
are yet to be decided. See PEP 612 for details.
Parameter specification variables can be introspected. e.g.:
P.__name__ == 'T'
P.__bound__ == None
P.__covariant__ == False
P.__contravariant__ == False
Note that only parameter specification variables defined in global scope can
be pickled.
"""
__slots__ = ('__name__', '__bound__', '__covariant__', '__contravariant__',
'__dict__')
args = object()
kwargs = object()
def __init__(self, name, bound=None, covariant=False, contravariant=False):
self.__name__ = name
super().__init__(bound, covariant, contravariant)
try:
def_mod = sys._getframe(1).f_globals.get('__name__', '__main__')
except (AttributeError, ValueError):
def_mod = None
if def_mod != 'typing':
self.__module__ = def_mod
def _is_dunder(attr):
@ -783,21 +896,26 @@ class _GenericAlias(_BaseGenericAlias, _root=True):
raise TypeError(f"Cannot subscript already-subscripted {self}")
if not isinstance(params, tuple):
params = (params,)
msg = "Parameters to generic types must be types."
params = tuple(_type_check(p, msg) for p in params)
params = tuple(_type_convert(p) for p in params)
if any(isinstance(t, ParamSpec) for t in self.__parameters__):
params = _prepare_paramspec_params(self, params)
_check_generic(self, params, len(self.__parameters__))
subst = dict(zip(self.__parameters__, params))
new_args = []
for arg in self.__args__:
if isinstance(arg, TypeVar):
if isinstance(arg, _TypeVarLike):
arg = subst[arg]
elif isinstance(arg, (_GenericAlias, GenericAlias)):
subparams = arg.__parameters__
if subparams:
subargs = tuple(subst[x] for x in subparams)
arg = arg[subargs]
new_args.append(arg)
# Required to flatten out the args for CallableGenericAlias
if self.__origin__ == collections.abc.Callable and isinstance(arg, tuple):
new_args.extend(arg)
else:
new_args.append(arg)
return self.copy_with(tuple(new_args))
def copy_with(self, params):
@ -884,15 +1002,18 @@ class _SpecialGenericAlias(_BaseGenericAlias, _root=True):
class _CallableGenericAlias(_GenericAlias, _root=True):
def __repr__(self):
assert self._name == 'Callable'
if len(self.__args__) == 2 and self.__args__[0] is Ellipsis:
args = self.__args__
if len(args) == 2 and (args[0] is Ellipsis
or isinstance(args[0], (ParamSpec, _ConcatenateGenericAlias))):
return super().__repr__()
return (f'typing.Callable'
f'[[{", ".join([_type_repr(a) for a in self.__args__[:-1]])}], '
f'{_type_repr(self.__args__[-1])}]')
f'[[{", ".join([_type_repr(a) for a in args[:-1]])}], '
f'{_type_repr(args[-1])}]')
def __reduce__(self):
args = self.__args__
if not (len(args) == 2 and args[0] is ...):
if not (len(args) == 2 and (args[0] is Ellipsis
or isinstance(args[0], (ParamSpec, _ConcatenateGenericAlias)))):
args = list(args[:-1]), args[-1]
return operator.getitem, (Callable, args)
@ -992,6 +1113,10 @@ class _LiteralGenericAlias(_GenericAlias, _root=True):
return hash(frozenset(_value_and_type_iter(self.__args__)))
class _ConcatenateGenericAlias(_GenericAlias, _root=True):
pass
class Generic:
"""Abstract base class for generic types.
@ -1022,18 +1147,20 @@ class Generic:
if not params and cls is not Tuple:
raise TypeError(
f"Parameter list to {cls.__qualname__}[...] cannot be empty")
msg = "Parameters to generic types must be types."
params = tuple(_type_check(p, msg) for p in params)
params = tuple(_type_convert(p) for p in params)
if cls in (Generic, Protocol):
# Generic and Protocol can only be subscripted with unique type variables.
if not all(isinstance(p, TypeVar) for p in params):
if not all(isinstance(p, _TypeVarLike) for p in params):
raise TypeError(
f"Parameters to {cls.__name__}[...] must all be type variables")
f"Parameters to {cls.__name__}[...] must all be type variables "
f"or parameter specification variables.")
if len(set(params)) != len(params):
raise TypeError(
f"Parameters to {cls.__name__}[...] must all be unique")
else:
# Subscripting a regular Generic subclass.
if any(isinstance(t, ParamSpec) for t in cls.__parameters__):
params = _prepare_paramspec_params(cls, params)
_check_generic(cls, params, len(cls.__parameters__))
return _GenericAlias(cls, params)

View File

@ -0,0 +1,2 @@
Implemented :pep:`612`: added ``ParamSpec`` and ``Concatenate`` to
:mod:`typing`. Patch by Ken Jin.

View File

@ -156,13 +156,24 @@ error:
return NULL;
}
// isinstance(obj, TypeVar) without importing typing.py.
// Returns -1 for errors.
static int
is_typevar(PyObject *obj)
/* Checks if a variable number of names are from typing.py.
* If any one of the names are found, return 1, else 0.
**/
static inline int
is_typing_name(PyObject *obj, int num, ...)
{
va_list names;
va_start(names, num);
PyTypeObject *type = Py_TYPE(obj);
if (strcmp(type->tp_name, "TypeVar") != 0) {
int hit = 0;
for (int i = 0; i < num; ++i) {
if (!strcmp(type->tp_name, va_arg(names, const char *))) {
hit = 1;
break;
}
}
if (!hit) {
return 0;
}
PyObject *module = PyObject_GetAttrString((PyObject *)type, "__module__");
@ -172,9 +183,25 @@ is_typevar(PyObject *obj)
int res = PyUnicode_Check(module)
&& _PyUnicode_EqualToASCIIString(module, "typing");
Py_DECREF(module);
va_end(names);
return res;
}
// isinstance(obj, (TypeVar, ParamSpec)) without importing typing.py.
// Returns -1 for errors.
static inline int
is_typevarlike(PyObject *obj)
{
return is_typing_name(obj, 2, "TypeVar", "ParamSpec");
}
static inline int
is_paramspec(PyObject *obj)
{
return is_typing_name(obj, 1, "ParamSpec");
}
// Index of item in self[:len], or -1 if not found (self is a tuple)
static Py_ssize_t
tuple_index(PyObject *self, Py_ssize_t len, PyObject *item)
@ -209,7 +236,7 @@ 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);
int typevar = is_typevar(t);
int typevar = is_typevarlike(t);
if (typevar < 0) {
Py_DECREF(parameters);
return NULL;
@ -279,7 +306,14 @@ subs_tvars(PyObject *obj, PyObject *params, PyObject **argitems)
if (iparam >= 0) {
arg = argitems[iparam];
}
Py_INCREF(arg);
// convert all the lists inside args to tuples to help
// with caching in other libaries
if (PyList_CheckExact(arg)) {
arg = PyList_AsTuple(arg);
}
else {
Py_INCREF(arg);
}
PyTuple_SET_ITEM(subargs, i, arg);
}
@ -314,11 +348,19 @@ ga_getitem(PyObject *self, PyObject *item)
int is_tuple = PyTuple_Check(item);
Py_ssize_t nitems = is_tuple ? PyTuple_GET_SIZE(item) : 1;
PyObject **argitems = is_tuple ? &PyTuple_GET_ITEM(item, 0) : &item;
if (nitems != nparams) {
return PyErr_Format(PyExc_TypeError,
"Too %s arguments for %R",
nitems > nparams ? "many" : "few",
self);
// A special case in PEP 612 where if X = Callable[P, int],
// then X[int, str] == X[[int, str]].
if (nparams == 1 && nitems > 1 && is_tuple &&
is_paramspec(PyTuple_GET_ITEM(alias->parameters, 0))) {
argitems = &item;
}
else {
if (nitems != nparams) {
return PyErr_Format(PyExc_TypeError,
"Too %s arguments for %R",
nitems > nparams ? "many" : "few",
self);
}
}
/* Replace all type variables (specified by alias->parameters)
with corresponding values specified by argitems.
@ -333,7 +375,7 @@ ga_getitem(PyObject *self, PyObject *item)
}
for (Py_ssize_t iarg = 0; iarg < nargs; iarg++) {
PyObject *arg = PyTuple_GET_ITEM(alias->args, iarg);
int typevar = is_typevar(arg);
int typevar = is_typevarlike(arg);
if (typevar < 0) {
Py_DECREF(newargs);
return NULL;
@ -342,7 +384,13 @@ ga_getitem(PyObject *self, PyObject *item)
Py_ssize_t iparam = tuple_index(alias->parameters, nparams, arg);
assert(iparam >= 0);
arg = argitems[iparam];
Py_INCREF(arg);
// convert lists to tuples to help with caching in other libaries.
if (PyList_CheckExact(arg)) {
arg = PyList_AsTuple(arg);
}
else {
Py_INCREF(arg);
}
}
else {
arg = subs_tvars(arg, alias->parameters, argitems);