From 0a912642cb3b6305af904f8e44169f8bde35924b Mon Sep 17 00:00:00 2001 From: Matthew Rahtz Date: Sun, 30 Jan 2022 16:53:13 +0000 Subject: [PATCH 01/32] PEP 646: Implement support in typing.py --- Lib/test/test_typing.py | 480 +++++++++++++++++++++++++++++++++++++++- Lib/typing.py | 397 +++++++++++++++++++++++++++++---- 2 files changed, 836 insertions(+), 41 deletions(-) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 8449affd03a768..a639fdbedde2f3 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -10,7 +10,8 @@ from copy import copy, deepcopy from typing import Any, NoReturn -from typing import TypeVar, AnyStr +from typing import TypeVar, TypeVarTuple, Unpack, UnpackedTypeVarTuple, AnyStr +from typing import _determine_typevar_substitution from typing import T, KT, VT # Not in __all__. from typing import Union, Optional, Literal from typing import Tuple, List, Dict, MutableMapping @@ -255,6 +256,443 @@ def test_no_bivariant(self): TypeVar('T', covariant=True, contravariant=True) +class TypeVarTupleTests(BaseTestCase): + + def test_instance_is_equal_to_itself(self): + Ts = TypeVarTuple('Ts') + self.assertEqual(Ts, Ts) + + def test_different_instances_are_different(self): + self.assertNotEqual(TypeVarTuple('Ts'), TypeVarTuple('Ts')) + + def test_instance_isinstance_of_typevartuple(self): + Ts = TypeVarTuple('Ts') + self.assertIsInstance(Ts, TypeVarTuple) + + def test_cannot_call_instance(self): + Ts = TypeVarTuple('Ts') + with self.assertRaises(TypeError): + Ts() + + def test_unpacked_typevartuple_is_equal_to_itself(self): + Ts = TypeVarTuple('Ts') + self.assertEqual(Unpack[Ts], Unpack[Ts]) + + def test_parameterised_tuple_is_equal_to_itself(self): + Ts = TypeVarTuple('Ts') + self.assertEqual(tuple[Unpack[Ts]], tuple[Unpack[Ts]]) + self.assertEqual(Tuple[Unpack[Ts]], Tuple[Unpack[Ts]]) + + def tests_tuple_arg_ordering_matters(self): + Ts1 = TypeVarTuple('Ts1') + Ts2 = TypeVarTuple('Ts2') + self.assertNotEqual( + tuple[Unpack[Ts1], Unpack[Ts2]], + tuple[Unpack[Ts2], Unpack[Ts1]], + ) + self.assertNotEqual( + Tuple[Unpack[Ts1], Unpack[Ts2]], + Tuple[Unpack[Ts2], Unpack[Ts1]], + ) + + def test_unpacked_isinstance_of_unpackedtypevartuple(self): + Ts = TypeVarTuple('Ts') + self.assertIsInstance(Unpack[Ts], UnpackedTypeVarTuple) + + def test_tuple_args_and_parameters_are_correct(self): + Ts = TypeVarTuple('Ts') + t1 = tuple[Unpack[Ts]] + self.assertEqual(t1.__args__, (Ts._unpacked,)) + self.assertEqual(t1.__parameters__, (Ts,)) + t2 = Tuple[Unpack[Ts]] + self.assertEqual(t2.__args__, (Ts._unpacked,)) + self.assertEqual(t2.__parameters__, (Ts,)) + + def test_unpack_cannot_be_called(self): + with self.assertRaises(TypeError): + Unpack() + + def test_unpack_fails_for_non_tuple_types(self): + with self.assertRaises(TypeError): + Unpack[list[int]] + with self.assertRaises(TypeError): + Unpack[dict[str, int]] + + def test_repr_is_correct(self): + Ts = TypeVarTuple('Ts') + self.assertEqual(repr(Ts), 'Ts') + self.assertEqual(repr(Unpack[Ts]), '*Ts') + self.assertEqual(repr(tuple[Unpack[Ts]]), 'tuple[*Ts]') + self.assertEqual(repr(Tuple[Unpack[Ts]]), 'typing.Tuple[*Ts]') + + def test_variadic_class_repr_is_correct(self): + Ts = TypeVarTuple('Ts') + class A(Generic[Unpack[Ts]]): pass + B = A[Unpack[Ts], int] + self.assertTrue(repr(B[()]).endswith('A[int]')) + self.assertTrue(repr(B[float]).endswith('A[float, int]')) + self.assertTrue(repr(B[float, str]).endswith('A[float, str, int]')) + + def test_cannot_subclass_class(self): + with self.assertRaises(TypeError): + class C(TypeVarTuple): pass + + def test_cannot_subclass_instance(self): + Ts = TypeVarTuple('Ts') + with self.assertRaises(TypeError): + class C(Ts): pass + with self.assertRaises(TypeError): + class C(Unpack[Ts]): pass + + def test_variadic_class_accepts_arbitrary_number_of_parameters(self): + Ts = TypeVarTuple('Ts') + class C(Generic[Unpack[Ts]]): pass + C[()] + C[int] + C[int, str] + + def test_variadic_class_with_duplicate_typevartuples_fails(self): + Ts1 = TypeVarTuple('Ts1') + Ts2 = TypeVarTuple('Ts2') + with self.assertRaises(TypeError): + class C(Generic[Unpack[Ts1], Unpack[Ts1]]): pass + with self.assertRaises(TypeError): + class C(Generic[Unpack[Ts1], Unpack[Ts2], Unpack[Ts1]]): pass + + def test_type_concatenation_in_variadic_class_argument_list_succeeds(self): + Ts = TypeVarTuple('Ts') + class C(Generic[Unpack[Ts]]): pass + C[int, Unpack[Ts]] + C[Unpack[Ts], int] + C[int, Unpack[Ts], str] + C[int, bool, Unpack[Ts], float, str] + + def test_type_concatenation_in_tuple_argument_list_succeeds(self): + Ts = TypeVarTuple('Ts') + + tuple[int, Unpack[Ts]] + tuple[Unpack[Ts], int] + tuple[int, Unpack[Ts], str] + tuple[int, bool, Unpack[Ts], float, str] + + Tuple[int, Unpack[Ts]] + Tuple[Unpack[Ts], int] + Tuple[int, Unpack[Ts], str] + Tuple[int, bool, Unpack[Ts], float, str] + + def test_variadic_class_definition_using_packed_typevartuple_fails(self): + Ts = TypeVarTuple('Ts') + with self.assertRaises(TypeError): + class C(Generic[Ts]): pass + + def test_variadic_class_definition_using_concrete_types_fails(self): + Ts = TypeVarTuple('Ts') + with self.assertRaises(TypeError): + class E(Generic[Unpack[Ts], int]): pass + + def test_variadic_class_with_2_typevars_accepts_2_or_more_args(self): + Ts = TypeVarTuple('Ts') + T1 = TypeVar('T1') + T2 = TypeVar('T2') + + class A(Generic[T1, T2, Unpack[Ts]]): pass + A[int, str] + A[int, str, float] + with self.assertRaises(TypeError): + A[int] + + class B(Generic[T1, Unpack[Ts], T2]): pass + B[int, str] + B[int, str, float] + with self.assertRaises(TypeError): + B[int] + + class C(Generic[Unpack[Ts], T1, T2]): pass + C[int, str] + C[int, str, float] + with self.assertRaises(TypeError): + C[int] + + def test_variadic_args_annotations_are_correct(self): + Ts = TypeVarTuple('Ts') + def f(*args: Unpack[Ts]): pass + self.assertEqual(f.__annotations__, {'args': Unpack[Ts]}) + + def test_variadic_args_with_ellipsis_annotations_are_correct(self): + Ts = TypeVarTuple('Ts') + + def a(*args: Unpack[tuple[int, ...]]): pass + self.assertEqual(a.__annotations__, + {'args': Unpack[tuple[int, ...]]}) + + def b(*args: Unpack[Tuple[int, ...]]): pass + self.assertEqual(b.__annotations__, + {'args': Unpack[Tuple[int, ...]]}) + + def test_concatenation_in_variadic_args_annotations_are_correct(self): + Ts = TypeVarTuple('Ts') + + # Unpacking using `Unpack`, native `tuple` type + + def a(*args: Unpack[tuple[int, Unpack[Ts]]]): pass + self.assertEqual( + a.__annotations__, + {'args': Unpack[tuple[int, Unpack[Ts]]]}, + ) + + def b(*args: Unpack[tuple[Unpack[Ts], int]]): pass + self.assertEqual( + b.__annotations__, + {'args': Unpack[tuple[Unpack[Ts], int]]}, + ) + + def c(*args: Unpack[tuple[str, Unpack[Ts], int]]): pass + self.assertEqual( + c.__annotations__, + {'args': Unpack[tuple[str, Unpack[Ts], int]]}, + ) + + def d(*args: Unpack[tuple[int, bool, Unpack[Ts], float, str]]): pass + self.assertEqual( + d.__annotations__, + {'args': Unpack[tuple[int, bool, Unpack[Ts], float, str]]}, + ) + + # Unpacking using `Unpack`, `Tuple` type from typing.py + + def e(*args: Unpack[Tuple[int, Unpack[Ts]]]): pass + self.assertEqual( + e.__annotations__, + {'args': Unpack[Tuple[int, Unpack[Ts]]]}, + ) + + def f(*args: Unpack[Tuple[Unpack[Ts], int]]): pass + self.assertEqual( + f.__annotations__, + {'args': Unpack[Tuple[Unpack[Ts], int]]}, + ) + + def g(*args: Unpack[Tuple[str, Unpack[Ts], int]]): pass + self.assertEqual( + g.__annotations__, + {'args': Unpack[Tuple[str, Unpack[Ts], int]]}, + ) + + def h(*args: Unpack[Tuple[int, bool, Unpack[Ts], float, str]]): pass + self.assertEqual( + h.__annotations__, + {'args': Unpack[Tuple[int, bool, Unpack[Ts], float, str]]}, + ) + + def test_variadic_class_same_args_results_in_equalty(self): + Ts = TypeVarTuple('Ts') + class C(Generic[Unpack[Ts]]): pass + + self.assertEqual(C[int], C[int]) + + Ts1 = TypeVarTuple('Ts1') + Ts2 = TypeVarTuple('Ts2') + self.assertEqual( + C[Unpack[Ts1]], + C[Unpack[Ts1]], + ) + self.assertEqual( + C[Unpack[Ts1], Unpack[Ts2]], + C[Unpack[Ts1], Unpack[Ts2]], + ) + self.assertEqual( + C[int, Unpack[Ts1], Unpack[Ts2]], + C[int, Unpack[Ts1], Unpack[Ts2]], + ) + + def test_variadic_class_arg_ordering_matters(self): + Ts = TypeVarTuple('Ts') + class C(Generic[Unpack[Ts]]): pass + + self.assertNotEqual( + C[int, str], + C[str, int], + ) + + Ts1 = TypeVarTuple('Ts1') + Ts2 = TypeVarTuple('Ts2') + self.assertNotEqual( + C[Unpack[Ts1], Unpack[Ts2]], + C[Unpack[Ts2], Unpack[Ts1]], + ) + + def test_variadic_class_arg_typevartuple_identity_matters(self): + Ts = TypeVarTuple('Ts') + class C(Generic[Unpack[Ts]]): pass + Ts1 = TypeVarTuple('Ts1') + Ts2 = TypeVarTuple('Ts2') + self.assertNotEqual(C[Unpack[Ts1]], C[Unpack[Ts2]]) + + def test_typevar_substitution(self): + T1 = TypeVar('T1') + T2 = TypeVar('T2') + Ts = TypeVarTuple('Ts') + + # Too few parameters + self.assertRaises( + TypeError, + _determine_typevar_substitution, + typevars=(T1,), params=(), + ) + self.assertRaises( + TypeError, + _determine_typevar_substitution, + typevars=(T1, T2), params=(int,), + ) + self.assertRaises( + TypeError, + _determine_typevar_substitution, + typevars=(Ts, T1), params=(), + ) + self.assertRaises( + TypeError, + _determine_typevar_substitution, + typevars=(T1, Ts), params=(), + ) + self.assertRaises( + TypeError, + _determine_typevar_substitution, + typevars=(Ts, T1, T2), params=(), + ) + self.assertRaises( + TypeError, + _determine_typevar_substitution, + typevars=(T1, Ts, T2), params=(int,), + ) + self.assertRaises( + TypeError, + _determine_typevar_substitution, + typevars=(T1, T2, Ts), params=(), + ) + + # Too many parameters + self.assertRaises( + TypeError, + _determine_typevar_substitution, + typevars=(T1,), params=(int, str), + ) + self.assertRaises( + TypeError, + _determine_typevar_substitution, + typevars=(T1, T2), params=(int, str, float), + ) + + # Too many TypeVarTuples + self.assertRaises( + TypeError, + _determine_typevar_substitution, + typevars=(Ts, Ts), params=(int, str), + ) + + # Correct number of parameters, TypeVars only + self.assertEqual( + _determine_typevar_substitution((T1,), (int,)), + {T1: int}, + ) + self.assertEqual( + _determine_typevar_substitution((T1, T2), (int, str)), + {T1: int, T2: str}, + ) + + # Correct number of parameters, TypeVarTuples only + self.assertEqual( + _determine_typevar_substitution((Ts,), ()), + {Ts: ()}, + ) + self.assertEqual( + _determine_typevar_substitution((Ts,), (int,)), + {Ts: (int,)}, + ) + self.assertEqual( + _determine_typevar_substitution((Ts,), (int, str)), + {Ts: (int, str)}, + ) + + # Correct number of parameters, TypeVarTuple at the beginning + self.assertEqual( + _determine_typevar_substitution((Ts, T1), (int,)), + {Ts: (), T1: int}, + ) + self.assertEqual( + _determine_typevar_substitution((Ts, T1), (int, str)), + {Ts: (int,), T1: str}, + ) + self.assertEqual( + _determine_typevar_substitution((Ts, T1), (int, str, float)), + {Ts: (int, str), T1: float}, + ) + + # Correct number of parameters, TypeVarTuple at the end + self.assertEqual( + _determine_typevar_substitution((T1, Ts), (int,)), + {T1: int, Ts: ()}, + ) + self.assertEqual( + _determine_typevar_substitution((T1, Ts), (int, str)), + {T1: int, Ts: (str,)}, + ) + self.assertEqual( + _determine_typevar_substitution((T1, Ts), (int, str, float)), + {T1: int, Ts: (str, float)}, + ) + + # Correct number of parameters, TypeVarTuple in the middle + self.assertEqual( + _determine_typevar_substitution((T1, Ts, T2), (int, str)), + {T1: int, Ts: (), T2: str}, + ) + self.assertEqual( + _determine_typevar_substitution((T1, Ts, T2), (int, float, str)), + {T1: int, Ts: (float,), T2: str}, + ) + + def test_callable_args_is_correct(self): + Ts = TypeVarTuple('Ts') + Ts1 = TypeVarTuple('Ts1') + Ts2 = TypeVarTuple('Ts2') + + # TypeVarTuple in the arguments + + a = Callable[[Unpack[Ts]], None] + self.assertEqual(a.__args__, (Unpack[Ts], type(None))) + + b = Callable[[int, Unpack[Ts]], None] + self.assertEqual(b.__args__, (int, Unpack[Ts], type(None))) + + c = Callable[[Unpack[Ts], int], None] + self.assertEqual(c.__args__, (Unpack[Ts], int, type(None))) + + d = Callable[[str, Unpack[Ts], int], None] + self.assertEqual(d.__args__, (str, Unpack[Ts], int, type(None))) + + # TypeVarTuple as the return + + e = Callable[[None], Unpack[Ts]] + self.assertEqual(e.__args__, (type(None), Unpack[Ts])) + + f = Callable[[None], tuple[int, Unpack[Ts]]] + self.assertEqual(f.__args__, (type(None), tuple[int, Unpack[Ts]])) + + g = Callable[[None], tuple[Unpack[Ts], int]] + self.assertEqual(g.__args__, (type(None), tuple[Unpack[Ts], int])) + + h = Callable[[None], tuple[str, Unpack[Ts], int]] + self.assertEqual(h.__args__, (type(None), tuple[str, Unpack[Ts], int])) + + # TypeVarTuple in both + + i = Callable[[Unpack[Ts]], Unpack[Ts]] + self.assertEqual(i.__args__, (Unpack[Ts], Unpack[Ts])) + + j = Callable[[Unpack[Ts1]], Unpack[Ts2]] + self.assertEqual(j.__args__, (Unpack[Ts1], Unpack[Ts2])) + + class UnionTests(BaseTestCase): def test_basics(self): @@ -3427,10 +3865,15 @@ def test_top_level_class_var(self): class GetUtilitiesTestCase(TestCase): def test_get_origin(self): T = TypeVar('T') + Ts = TypeVarTuple('Ts') P = ParamSpec('P') class C(Generic[T]): pass self.assertIs(get_origin(C[int]), C) self.assertIs(get_origin(C[T]), C) + class D(Generic[Unpack[Ts]]): pass + self.assertIs(get_origin(D[int]), D) + self.assertIs(get_origin(D[T]), D) + self.assertIs(get_origin(D[Unpack[Ts]]), D) self.assertIs(get_origin(int), None) self.assertIs(get_origin(ClassVar[int]), ClassVar) self.assertIs(get_origin(Union[int, str]), Union) @@ -3442,6 +3885,13 @@ class C(Generic[T]): pass self.assertIs(get_origin(Annotated[T, 'thing']), Annotated) self.assertIs(get_origin(List), list) self.assertIs(get_origin(Tuple), tuple) + + self.assertIs(get_origin(tuple[Unpack[Ts]]), tuple) + self.assertIs(get_origin(Tuple[Unpack[Ts]]), tuple) + + self.assertIs(get_origin(Unpack[tuple[int]]), tuple) + self.assertIs(get_origin(Unpack[Tuple[int]]), tuple) + self.assertIs(get_origin(Callable), collections.abc.Callable) self.assertIs(get_origin(list[int]), list) self.assertIs(get_origin(list), None) @@ -3451,9 +3901,17 @@ class C(Generic[T]): pass def test_get_args(self): T = TypeVar('T') + Ts = TypeVarTuple('Ts') class C(Generic[T]): pass self.assertEqual(get_args(C[int]), (int,)) self.assertEqual(get_args(C[T]), (T,)) + class D(Generic[Unpack[Ts]]): pass + self.assertEqual(get_args(D[int]), (int,)) + self.assertEqual(get_args(D[T]), (T,)) + self.assertEqual(get_args(D[Unpack[Ts]]), (Unpack[Ts],)) + self.assertEqual(get_args(D[T, Unpack[Ts]]), (T, Unpack[Ts],)) + self.assertEqual(get_args(D[Unpack[Ts], T]), (Unpack[Ts], T)) + self.assertEqual(get_args(D[Unpack[Ts], int, ...]), (Unpack[Ts], int, ...)) self.assertEqual(get_args(int), ()) self.assertEqual(get_args(ClassVar[int]), (int,)) self.assertEqual(get_args(Union[int, str]), (int, str)) @@ -3469,6 +3927,26 @@ class C(Generic[T]): pass (int, Callable[[Tuple[T, ...]], str])) self.assertEqual(get_args(Tuple[int, ...]), (int, ...)) self.assertEqual(get_args(Tuple[()]), ((),)) + + self.assertEqual(get_args(tuple[Unpack[Ts]]), (Unpack[Ts],)) + self.assertEqual(get_args(Tuple[Unpack[Ts]]), (Unpack[Ts],)) + + self.assertEqual(get_args(tuple[Unpack[Ts], int]), (Unpack[Ts], int)) + self.assertEqual(get_args(Tuple[Unpack[Ts], int]), (Unpack[Ts], int)) + + self.assertEqual(get_args(tuple[int, Unpack[Ts]]), (int, Unpack[Ts])) + self.assertEqual(get_args(Tuple[int, Unpack[Ts]]), (int, Unpack[Ts])) + + self.assertEqual(get_args(tuple[int, Unpack[Ts], str]), + (int, Unpack[Ts], str)) + self.assertEqual(get_args(Tuple[int, Unpack[Ts], str]), + (int, Unpack[Ts], str)) + + self.assertEqual(get_args(tuple[Unpack[Ts], int, ...]), + (Unpack[Ts], int, ...)) + self.assertEqual(get_args(Tuple[Unpack[Ts]]), + (Unpack[Ts],)) + self.assertEqual(get_args(Annotated[T, 'one', 2, ['three']]), (T, 'one', 2, ['three'])) self.assertEqual(get_args(List), ()) self.assertEqual(get_args(Tuple), ()) diff --git a/Lib/typing.py b/Lib/typing.py index dac9c6c4f87cfe..10582331bc2d79 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -56,7 +56,10 @@ def _idfunc(_, x): 'Tuple', 'Type', 'TypeVar', + 'TypeVarTuple', + 'StarredTuple', 'Union', + 'UnpackedTypeVarTuple', # ABCs (from collections.abc). 'AbstractSet', # collections.abc.Set. @@ -135,6 +138,7 @@ def _idfunc(_, x): 'TYPE_CHECKING', 'TypeAlias', 'TypeGuard', + 'Unpack', ] # The pseudo-submodules 're' and 'io' are part of the public @@ -177,7 +181,8 @@ def _type_check(arg, msg, is_argument=True, module=None, *, allow_special_forms= 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.UnionType, ParamSpec)): + if isinstance(arg, (type, TypeVar, TypeVarTuple, UnpackedTypeVarTuple, + ForwardRef, types.UnionType, ParamSpec, StarredTuple)): return arg if not callable(arg): raise TypeError(f"{msg} Got {arg!r:.100}.") @@ -227,16 +232,62 @@ def _collect_type_vars(types_, typevar_types=None): return tuple(tvars) -def _check_generic(cls, parameters, elen): - """Check correct count for parameters of a generic cls (internal helper). - This gives a nice error message in case of count mismatch. +def _check_type_parameter_count( + cls, + type_params, +): + """Checks whether number of type parameters to a generic class is correct. + + This function uses introspection on `cls` to determine the expected number + of type parameters, and gives a nice error message in case of count + mismatch. """ - if not elen: - raise TypeError(f"{cls} is not a generic class") - alen = len(parameters) - if alen != elen: - raise TypeError(f"Too {'many' if alen > elen else 'few'} arguments for {cls};" - f" actual {alen}, expected {elen}") + actual_num_type_params = len(type_params) + + # First, can we determine the exact number of type parameters expected? + + if isinstance(cls, _SpecialGenericAlias): + # For types like List and Tuple, we know exactly how many parameters + # to expect. + expected_num_type_params = cls._nparams + elif all(isinstance(p, (TypeVar, ParamSpec)) for p in cls.__parameters__): + # If all type variables are `TypeVar` or `ParamSpec`, the number + # of them should exactly match the number of type variables. + expected_num_type_params = len(cls.__parameters__) + else: + expected_num_type_params = None + + if expected_num_type_params is not None: + if expected_num_type_params == 0: + raise TypeError(f"{cls} is not a generic class") + if actual_num_type_params > expected_num_type_params: + error = "Too many" + elif actual_num_type_params < expected_num_type_params: + error = "Too few" + else: + return + msg = (f"{error} arguments for {cls}; " + f"actual {actual_num_type_params}, " + f"expected {expected_num_type_params}") + raise TypeError(msg) + + # Second, if we can't determine the exact number of type parameters + # expected, can we determine the minimum type of type parameters expected? + + if any(isinstance(p, TypeVarTuple) for p in cls.__parameters__): + min_num_type_params = len([ + p for p in cls.__parameters__ + if isinstance(p, (TypeVar, ParamSpec)) + ]) + if actual_num_type_params >= min_num_type_params: + return + else: + raise TypeError(f"Too few parameters for {cls}; " + f"actual {actual_num_type_params}, " + f"expected at least {min_num_type_params}") + + raise TypeError(f"Couldn't determine type parameter requirements for {cls}") + def _prepare_paramspec_params(cls, params): """Prepares the parameters for a Generic containing ParamSpec @@ -248,7 +299,7 @@ def _prepare_paramspec_params(cls, params): assert isinstance(cls.__parameters__[0], ParamSpec) return (params,) else: - _check_generic(cls, params, len(cls.__parameters__)) + _check_type_parameter_count(cls, params) _params = [] # Convert lists to tuples to help other libraries cache the results. for p, tvar in zip(params, cls.__parameters__): @@ -818,6 +869,117 @@ def __init__(self, name, *constraints, bound=None, self.__module__ = def_mod +class TypeVarTuple(_Final, _Immutable, _root=True): + """Type variable tuple. + + Usage: + + Ts = TypeVarTuple('Ts') # Can be given any name + + Just as a TypeVar (type variable) is a placeholder for a single type, + a TypeVarTuple is a placeholder for an *arbitrary* number of types. For + example, if we define a generic class using a TypeVarTuple: + + class C(Generic[*Ts]): ... + + Then we can parameterize that class with an arbitrary number of type + arguments: + + C[int] # Fine + C[int, str] # Also fine + C[()] # Even this is fine + + For more details, see PEP 646. + """ + + def __init__(self, name): + self._name = name + self._unpacked = UnpackedTypeVarTuple(name, self) + + def __iter__(self): + yield self._unpacked + + def __repr__(self): + return self._name + + +class UnpackedTypeVarTuple(_Final, _Immutable, _root=True): + + def __init__(self, name, packed): + self._name = name + self._packed = packed + # __parameters__ will be use to figure out what the __parameters__ + # of e.g. `tuple[*Ts]` should be. In the case of an unpacked + # TypeVarTuple, it's unclear whether this should refer to the + # UnpackedTypeVarTuple or the TypeVarTuple itself. We somewhat + # arbitrarily decide to go for the latter. + self.__parameters__ = (packed,) + + def __repr__(self): + return '*' + self._name + + +class StarredTuple(_Final, _Immutable, _root=True): + """Implementation of starred tuple for older versions of Python. + + A starred tuple is e.g. tuple[*tuple[int, str]]. In newer versions of + Python, this is handled through a change to `tuple` itself. + """ + + def __init__(self, tup): + self.__origin__ = tuple + self._tuple = tup + self.__args__ = tup.__args__ + self.__parameters__ = tup.__parameters__ + + def __repr__(self): + return '*' + repr(self._tuple) + + def __eq__(self, other): + if isinstance(other, StarredTuple): + return self._tuple == other._tuple + else: + return False + + +class Unpack(_Final, _Immutable, _root=True): + """Implementation of the unpack operator for older versions of Python. + + The type unpack operator takes the child types from some container type + such as `tuple[int, str]` and "pulls them out". For example, + `dict[Unpack[tuple[int, str]]` is equivalent to `dict[int, str]`. + In newer versions of Python, this is implemented using the `*` operator. + + The type unpack operator can be applied to a `TypeVarTuple` instance: + + Ts = TypeVarTuple('Ts') + tuple[Unpack[Ts]] # Equivalent to tuple[*Ts] + + Or to a parameterised `tuple`: + + tuple[Unpack[tuple[int, str]]] # Equivalent to tuple[*tuple[int, str]] + """ + + def __new__(cls, *args, **kwargs): + raise TypeError("Unpack should be used as Unpack[something] rather " + "than Unpack(something)") + + def __class_getitem__(cls, item): + if isinstance(item, types.GenericAlias) and item.__origin__ is tuple: + # tuple[...] + return StarredTuple(item) + elif isinstance(item, _GenericAlias) and item.__origin__ is tuple: + # typing.Tuple[...] + return StarredTuple(item) + elif isinstance(item, TypeVarTuple): + return item._unpacked + else: + raise TypeError( + "typing.Unpack is only supported for parameterised tuple " + "(e.g. tuple[int]) or TypeVarTuple arguments" + ) + + class ParamSpecArgs(_Final, _Immutable, _root=True): """The args for a ParamSpec object. @@ -987,6 +1149,98 @@ def __dir__(self): return list(set(super().__dir__() + [attr for attr in dir(self.__origin__) if not _is_dunder(attr)])) +def _determine_typevar_substitution(typevars, params): + """Determines how to assign type parameters to type variables. + + Args: + typevars: A tuple of TypeVars and (at most one) TypeVarTuple. + params: A tuple of type parameters to substitute into type variables. + + Examples: + T1 = TypeVar('T1') + T2 = TypeVar('T2') + Ts = TypeVarTuple('Ts') + + typevars=(T1,), params=() => TypeError + typevars=(T1,), params=(int,) => {T1: int} + typevars=(T1,), params=(int, str) => TypeError + typevars=(T1, T2), params=(int, str) => {T1: int, T2: str} + typevars=(Ts,), params=() => {Ts: ()} + typevars=(Ts,), params=(int,) => {Ts: (int,)} + typevars=(Ts,), params=(int, str) => {Ts: (int, str)} + typevars=(T, Ts), params=() => TypeError + typevars=(T, Ts), params=(int,) => {T: int, Ts: ()} + typevars=(T, Ts) params=(int, str) => {T, int, Ts: (str,)} + """ + if not typevars: + return {} + + num_typevartuples = len([x for x in typevars + if isinstance(x, TypeVarTuple)]) + if num_typevartuples > 1: + raise TypeError("At most 1 TypeVarTuple may be used in a type " + f"parameter list, but saw {num_typevartuples}") + + # Case 1: typevars does not contain any TypeVarTuples + + if num_typevartuples == 0: + if len(typevars) != len(params): + raise TypeError(f"Number of type variables ({len(typevars)}) " + f"doesn't match number of type " + f"parameters ({len(params)})") + return dict(zip(typevars, params)) + + # Case 2: typevars contains a single TypeVarTuple + + [typevartuple_idx] = [i for i, x in enumerate(typevars) + if isinstance(x, TypeVarTuple)] + typevartuple = typevars[typevartuple_idx] + num_start_typevars = typevartuple_idx + # Assuming len(typevars) == 3: + # * If typevartuple_idx == 0, there are 2 TypeVars at + # the end of typevars. + # * If typevartuple_idx == 2 there are 0 TypeVars at + # the end of typevars. + num_end_typevars = len(typevars) - typevartuple_idx - 1 + if len(params) < num_start_typevars + num_end_typevars: + raise TypeError( + "Expected at least {} type parameters, but only got {}".format( + num_start_typevars + num_end_typevars, len(params) + ) + ) + + if num_start_typevars == num_end_typevars == 0: + return {typevars[0]: params} + elif num_end_typevars == 0: + # Ideally we wouldn't need this block, but if num_end_typevars == 0, + # list[num_start_typevars:-num_end_typevars] doesn't work as expected. + return { + **dict( + zip( + typevars[:num_start_typevars], + params[:num_start_typevars], + ), + ), + typevartuple: params[num_start_typevars:], + } + else: + return { + **dict( + zip( + typevars[:num_start_typevars], + params[:num_start_typevars], + ), + ), + typevartuple: params[num_start_typevars:-num_end_typevars], + **dict( + zip( + typevars[-num_end_typevars:], + params[-num_end_typevars:], + ) + ), + } + + # Special typing constructs Union, Optional, Generic, Callable and Tuple # use three special attributes for internal bookkeeping of generic types: # * __parameters__ is a tuple of unique free type parameters of a generic @@ -1000,7 +1254,7 @@ def __dir__(self): class _GenericAlias(_BaseGenericAlias, _root=True): def __init__(self, origin, params, *, inst=True, name=None, - _typevar_types=TypeVar, + _typevar_types=(TypeVar, UnpackedTypeVarTuple), _paramspec_tvars=False): super().__init__(origin, inst=inst, name=name) if not isinstance(params, tuple): @@ -1008,7 +1262,17 @@ def __init__(self, origin, params, *, inst=True, name=None, self.__args__ = tuple(... if a is _TypingEllipsis else () if a is _TypingEmpty else a for a in params) - self.__parameters__ = _collect_type_vars(params, typevar_types=_typevar_types) + typevars = _collect_type_vars(params, typevar_types=_typevar_types) + parameters_list = [] + # As per the note in the definition of UnpackedTypeVarTuple, if + # an unpacked TypeVarTuple appears in the type parameter list, we use + # the original (packed) TypeVarTuple in __parameters__. + for i, typevar in enumerate(typevars): + if isinstance(typevar, UnpackedTypeVarTuple): + parameters_list.append(typevar._packed) + else: + parameters_list.append(typevar) + self.__parameters__ = tuple(parameters_list) self._typevar_types = _typevar_types self._paramspec_tvars = _paramspec_tvars if not name: @@ -1041,29 +1305,54 @@ def __getitem__(self, params): and any(isinstance(t, ParamSpec) for t in self.__parameters__)): params = _prepare_paramspec_params(self, params) else: - _check_generic(self, params, len(self.__parameters__)) + _check_type_parameter_count(self, params) - subst = dict(zip(self.__parameters__, params)) + subst = _determine_typevar_substitution(self.__parameters__, params) + print() + print('!!! subst:', subst) new_args = [] - for arg in self.__args__: - if isinstance(arg, self._typevar_types): - if isinstance(arg, ParamSpec): - arg = subst[arg] - if not _is_param_expr(arg): + for old_arg in self.__args__: + if isinstance(old_arg, UnpackedTypeVarTuple): + # When an unpacked TypeVarTuple is used as a type parameter, + # the entry in __parameters__ is the (packed) TypeVarTuple + # itself - and since _determine_typevar_substitution uses + # entries from __parameters__ as keys, we need to switch to + # the packed version here. + new_arg = subst[old_arg._packed] + elif isinstance(old_arg, self._typevar_types): + if isinstance(old_arg, ParamSpec): + new_arg = subst[old_arg] + if not _is_param_expr(new_arg): raise TypeError(f"Expected a list of types, an ellipsis, " - f"ParamSpec, or Concatenate. Got {arg}") + f"ParamSpec, or Concatenate. Got {new_arg}") else: - arg = subst[arg] - elif isinstance(arg, (_GenericAlias, GenericAlias, types.UnionType)): - subparams = arg.__parameters__ + new_arg = subst[old_arg] + elif isinstance(old_arg, (_GenericAlias, GenericAlias, types.UnionType)): + subparams = old_arg.__parameters__ if subparams: subargs = tuple(subst[x] for x in subparams) - arg = arg[subargs] + new_arg = old_arg[subargs] + else: + new_arg = old_arg + else: + new_arg = old_arg + # Required to flatten out the args for CallableGenericAlias - if self.__origin__ == collections.abc.Callable and isinstance(arg, tuple): - new_args.extend(arg) + if self.__origin__ == collections.abc.Callable and isinstance(new_arg, tuple): + new_args.extend(new_arg) + elif isinstance(old_arg, UnpackedTypeVarTuple): + # Suppose we had: + # Ts = TypeVarTuple('Ts') + # class A(Generic[*Ts]): pass + # B = A[*Ts, int] + # B[()] + # The `repr` of `B` shouldn't be `A[(), int]`. + # In general, the `new_arg` corresponding to a `TypeVarTuple` + # will always be a tuple of things, so we should `extend` + # rather than `append`. + new_args.extend(new_arg) else: - new_args.append(arg) + new_args.append(new_arg) return self.copy_with(tuple(new_args)) def copy_with(self, params): @@ -1102,6 +1391,9 @@ def __mro_entries__(self, bases): return () return (self.__origin__,) + def __iter__(self): + yield StarredTuple(self) + # _nparams is the number of accepted parameters, e.g. 0 for Hashable, # 1 for List and 2 for Dict. It may be -1 if variable number of @@ -1124,7 +1416,7 @@ def __getitem__(self, params): params = (params,) msg = "Parameters to generic types must be types." params = tuple(_type_check(p, msg) for p in params) - _check_generic(self, params, self._nparams) + _check_type_parameter_count(self, params) return self.copy_with(params) def copy_with(self, params): @@ -1209,10 +1501,10 @@ def __getitem__(self, params): return self.copy_with((_TypingEmpty,)) if not isinstance(params, tuple): params = (params,) - if len(params) == 2 and params[1] is ...: + if len(params) >= 2 and params[-1] is ...: msg = "Tuple[t, ...]: t must be a type." - p = _type_check(params[0], msg) - return self.copy_with((p, _TypingEllipsis)) + params = tuple(_type_check(p, msg) for p in params[:-1]) + return self.copy_with((*params, _TypingEllipsis)) msg = "Tuple[t0, t1, ...]: each t must be a type." params = tuple(_type_check(p, msg) for p in params) return self.copy_with(params) @@ -1312,13 +1604,34 @@ def lookup_name(mapping: Mapping[KT, VT], key: KT, default: VT) -> VT: def __class_getitem__(cls, params): if not isinstance(params, tuple): params = (params,) - if not params and cls is not Tuple: + + try: + num_class_params = len(cls.__parameters__) + except AttributeError: + only_class_parameter_is_typevartuple = False + else: + if ( + num_class_params == 1 + and isinstance(cls.__parameters__[0], TypeVarTuple) + ): + only_class_parameter_is_typevartuple = True + else: + only_class_parameter_is_typevartuple = False + + if ( + not params + and not (cls is Tuple or only_class_parameter_is_typevartuple) + ): raise TypeError( f"Parameter list to {cls.__qualname__}[...] cannot be empty") + 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, ParamSpec)) for p in params): + if not all( + isinstance(p, (TypeVar, UnpackedTypeVarTuple, ParamSpec)) + for p in params + ): raise TypeError( f"Parameters to {cls.__name__}[...] must all be type variables " f"or parameter specification variables.") @@ -1330,10 +1643,12 @@ def __class_getitem__(cls, params): if any(isinstance(t, ParamSpec) for t in cls.__parameters__): params = _prepare_paramspec_params(cls, params) else: - _check_generic(cls, params, len(cls.__parameters__)) - return _GenericAlias(cls, params, - _typevar_types=(TypeVar, ParamSpec), - _paramspec_tvars=True) + _check_type_parameter_count(cls, params) + return _GenericAlias( + cls, params, + _typevar_types=(TypeVar, UnpackedTypeVarTuple, ParamSpec), + _paramspec_tvars=True, + ) def __init_subclass__(cls, *args, **kwargs): super().__init_subclass__(*args, **kwargs) @@ -1345,7 +1660,9 @@ def __init_subclass__(cls, *args, **kwargs): if error: raise TypeError("Cannot inherit from plain Generic") if '__orig_bases__' in cls.__dict__: - tvars = _collect_type_vars(cls.__orig_bases__, (TypeVar, ParamSpec)) + tvars = _collect_type_vars( + cls.__orig_bases__, (TypeVar, ParamSpec, UnpackedTypeVarTuple) + ) # Look for Generic[T1, ..., Tn]. # If found, tvars must be a subset of it. # If not found, tvars is it. @@ -1902,7 +2219,7 @@ def get_origin(tp): if isinstance(tp, _AnnotatedAlias): return Annotated if isinstance(tp, (_BaseGenericAlias, GenericAlias, - ParamSpecArgs, ParamSpecKwargs)): + ParamSpecArgs, ParamSpecKwargs, StarredTuple)): return tp.__origin__ if tp is Generic: return Generic From 33dc1bfaa7b162422076cfcfaef9830481c4617a Mon Sep 17 00:00:00 2001 From: Matthew Rahtz Date: Sun, 30 Jan 2022 20:58:37 +0000 Subject: [PATCH 02/32] Fix typo Co-authored-by: Jelle Zijlstra --- Lib/typing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/typing.py b/Lib/typing.py index 10582331bc2d79..e5e9a0ae81f177 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -272,7 +272,7 @@ def _check_type_parameter_count( raise TypeError(msg) # Second, if we can't determine the exact number of type parameters - # expected, can we determine the minimum type of type parameters expected? + # expected, can we determine the minimum number of type parameters expected? if any(isinstance(p, TypeVarTuple) for p in cls.__parameters__): min_num_type_params = len([ From 51ea1af41fee4b8014c8d10726992d2ff044ce06 Mon Sep 17 00:00:00 2001 From: Matthew Rahtz Date: Sun, 30 Jan 2022 20:58:51 +0000 Subject: [PATCH 03/32] Remove unnecessary whitespace Co-authored-by: Jelle Zijlstra --- Lib/typing.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Lib/typing.py b/Lib/typing.py index e5e9a0ae81f177..487b3771de6d2f 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -232,10 +232,7 @@ def _collect_type_vars(types_, typevar_types=None): return tuple(tvars) -def _check_type_parameter_count( - cls, - type_params, -): +def _check_type_parameter_count(cls, type_params): """Checks whether number of type parameters to a generic class is correct. This function uses introspection on `cls` to determine the expected number From fa133dd33a931f97e7fe383196cfee93c9ac7e20 Mon Sep 17 00:00:00 2001 From: Matthew Rahtz Date: Sun, 30 Jan 2022 20:57:33 +0000 Subject: [PATCH 04/32] Remove some old debug prints --- Lib/typing.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/Lib/typing.py b/Lib/typing.py index 487b3771de6d2f..546887cee3553e 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -1305,8 +1305,6 @@ def __getitem__(self, params): _check_type_parameter_count(self, params) subst = _determine_typevar_substitution(self.__parameters__, params) - print() - print('!!! subst:', subst) new_args = [] for old_arg in self.__args__: if isinstance(old_arg, UnpackedTypeVarTuple): From 6d1126e07979a77788bb9682120cfdc1cb40e693 Mon Sep 17 00:00:00 2001 From: Matthew Rahtz Date: Sun, 30 Jan 2022 21:57:01 +0000 Subject: [PATCH 05/32] Re-implement Unpack using _SpecialForm --- Lib/test/test_typing.py | 47 ++++++----- Lib/typing.py | 173 ++++++++++++++-------------------------- 2 files changed, 85 insertions(+), 135 deletions(-) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index a639fdbedde2f3..6fc0d0823c8277 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -10,7 +10,7 @@ from copy import copy, deepcopy from typing import Any, NoReturn -from typing import TypeVar, TypeVarTuple, Unpack, UnpackedTypeVarTuple, AnyStr +from typing import TypeVar, TypeVarTuple, Unpack, AnyStr from typing import _determine_typevar_substitution from typing import T, KT, VT # Not in __all__. from typing import Union, Optional, Literal @@ -256,6 +256,24 @@ def test_no_bivariant(self): TypeVar('T', covariant=True, contravariant=True) +class UnpackTests(BaseTestCase): + + def test_accepts_single_type(self): + Unpack[Tuple[int]] + + def test_rejects_multiple_types(self): + with self.assertRaises(TypeError): + Unpack[Tuple[int], Tuple[str]] + + def test_rejects_multiple_parameterization(self): + with self.assertRaises(TypeError): + Unpack[Tuple[int]][Tuple[int]] + + def test_cannot_be_called(self): + with self.assertRaises(TypeError): + Unpack() + + class TypeVarTupleTests(BaseTestCase): def test_instance_is_equal_to_itself(self): @@ -295,35 +313,23 @@ def tests_tuple_arg_ordering_matters(self): Tuple[Unpack[Ts2], Unpack[Ts1]], ) - def test_unpacked_isinstance_of_unpackedtypevartuple(self): - Ts = TypeVarTuple('Ts') - self.assertIsInstance(Unpack[Ts], UnpackedTypeVarTuple) - def test_tuple_args_and_parameters_are_correct(self): Ts = TypeVarTuple('Ts') t1 = tuple[Unpack[Ts]] - self.assertEqual(t1.__args__, (Ts._unpacked,)) + self.assertEqual(t1.__args__, (Unpack[Ts],)) self.assertEqual(t1.__parameters__, (Ts,)) t2 = Tuple[Unpack[Ts]] - self.assertEqual(t2.__args__, (Ts._unpacked,)) + self.assertEqual(t2.__args__, (Unpack[Ts],)) self.assertEqual(t2.__parameters__, (Ts,)) - def test_unpack_cannot_be_called(self): - with self.assertRaises(TypeError): - Unpack() - - def test_unpack_fails_for_non_tuple_types(self): - with self.assertRaises(TypeError): - Unpack[list[int]] - with self.assertRaises(TypeError): - Unpack[dict[str, int]] - def test_repr_is_correct(self): Ts = TypeVarTuple('Ts') self.assertEqual(repr(Ts), 'Ts') self.assertEqual(repr(Unpack[Ts]), '*Ts') self.assertEqual(repr(tuple[Unpack[Ts]]), 'tuple[*Ts]') self.assertEqual(repr(Tuple[Unpack[Ts]]), 'typing.Tuple[*Ts]') + self.assertEqual(repr(Unpack[tuple[Unpack[Ts]]]), '*tuple[*Ts]') + self.assertEqual(repr(Unpack[Tuple[Unpack[Ts]]]), '*typing.Tuple[*Ts]') def test_variadic_class_repr_is_correct(self): Ts = TypeVarTuple('Ts') @@ -3885,13 +3891,6 @@ class D(Generic[Unpack[Ts]]): pass self.assertIs(get_origin(Annotated[T, 'thing']), Annotated) self.assertIs(get_origin(List), list) self.assertIs(get_origin(Tuple), tuple) - - self.assertIs(get_origin(tuple[Unpack[Ts]]), tuple) - self.assertIs(get_origin(Tuple[Unpack[Ts]]), tuple) - - self.assertIs(get_origin(Unpack[tuple[int]]), tuple) - self.assertIs(get_origin(Unpack[Tuple[int]]), tuple) - self.assertIs(get_origin(Callable), collections.abc.Callable) self.assertIs(get_origin(list[int]), list) self.assertIs(get_origin(list), None) diff --git a/Lib/typing.py b/Lib/typing.py index 546887cee3553e..fd2be212b5ca84 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -5,7 +5,7 @@ * 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, Concatenate + Any, NoReturn, ClassVar, Union, Optional, Concatenate, Unpack * 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 @@ -57,9 +57,7 @@ def _idfunc(_, x): 'Type', 'TypeVar', 'TypeVarTuple', - 'StarredTuple', 'Union', - 'UnpackedTypeVarTuple', # ABCs (from collections.abc). 'AbstractSet', # collections.abc.Set. @@ -181,8 +179,8 @@ def _type_check(arg, msg, is_argument=True, module=None, *, allow_special_forms= 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, TypeVarTuple, UnpackedTypeVarTuple, - ForwardRef, types.UnionType, ParamSpec, StarredTuple)): + if isinstance(arg, (type, TypeVar, TypeVarTuple, ForwardRef, + types.UnionType, ParamSpec)): return arg if not callable(arg): raise TypeError(f"{msg} Got {arg!r:.100}.") @@ -773,8 +771,20 @@ def __ror__(self, other): def __repr__(self): return f'ForwardRef({self.__forward_arg__!r})' -class _TypeVarLike: - """Mixin for TypeVar-like types (TypeVar and ParamSpec).""" + +def _is_unpacked_typevartuple(x): + return ( + isinstance(x, _UnpackGenericAlias) + and isinstance(x.__parameters__[0], TypeVarTuple) + ) + + +def _is_typevar_like(x): + return _is_unpacked_typevartuple or isinstance(x, (TypeVar, ParamSpec)) + + +class _BoundVarianceMixin: + """Mixin giving __init__ bound and variance arguments.""" def __init__(self, bound, covariant, contravariant): """Used to setup TypeVars and ParamSpec's bound, covariant and contravariant attributes. @@ -807,7 +817,7 @@ def __reduce__(self): return self.__name__ -class TypeVar( _Final, _Immutable, _TypeVarLike, _root=True): +class TypeVar(_Final, _Immutable, _BoundVarianceMixin, _root=True): """Type variable. Usage:: @@ -891,92 +901,14 @@ class C(Generic[*Ts]): ... def __init__(self, name): self._name = name - self._unpacked = UnpackedTypeVarTuple(name, self) def __iter__(self): - yield self._unpacked + yield Unpack[self] def __repr__(self): return self._name -class UnpackedTypeVarTuple(_Final, _Immutable, _root=True): - - def __init__(self, name, packed): - self._name = name - self._packed = packed - # __parameters__ will be use to figure out what the __parameters__ - # of e.g. `tuple[*Ts]` should be. In the case of an unpacked - # TypeVarTuple, it's unclear whether this should refer to the - # UnpackedTypeVarTuple or the TypeVarTuple itself. We somewhat - # arbitrarily decide to go for the latter. - self.__parameters__ = (packed,) - - def __repr__(self): - return '*' + self._name - - -class StarredTuple(_Final, _Immutable, _root=True): - """Implementation of starred tuple for older versions of Python. - - A starred tuple is e.g. tuple[*tuple[int, str]]. In newer versions of - Python, this is handled through a change to `tuple` itself. - """ - - def __init__(self, tup): - self.__origin__ = tuple - self._tuple = tup - self.__args__ = tup.__args__ - self.__parameters__ = tup.__parameters__ - - def __repr__(self): - return '*' + repr(self._tuple) - - def __eq__(self, other): - if isinstance(other, StarredTuple): - return self._tuple == other._tuple - else: - return False - - -class Unpack(_Final, _Immutable, _root=True): - """Implementation of the unpack operator for older versions of Python. - - The type unpack operator takes the child types from some container type - such as `tuple[int, str]` and "pulls them out". For example, - `dict[Unpack[tuple[int, str]]` is equivalent to `dict[int, str]`. - In newer versions of Python, this is implemented using the `*` operator. - - The type unpack operator can be applied to a `TypeVarTuple` instance: - - Ts = TypeVarTuple('Ts') - tuple[Unpack[Ts]] # Equivalent to tuple[*Ts] - - Or to a parameterised `tuple`: - - tuple[Unpack[tuple[int, str]]] # Equivalent to tuple[*tuple[int, str]] - """ - - def __new__(cls, *args, **kwargs): - raise TypeError("Unpack should be used as Unpack[something] rather " - "than Unpack(something)") - - def __class_getitem__(cls, item): - if isinstance(item, types.GenericAlias) and item.__origin__ is tuple: - # tuple[...] - return StarredTuple(item) - elif isinstance(item, _GenericAlias) and item.__origin__ is tuple: - # typing.Tuple[...] - return StarredTuple(item) - elif isinstance(item, TypeVarTuple): - return item._unpacked - else: - raise TypeError( - "typing.Unpack is only supported for parameterised tuple " - "(e.g. tuple[int]) or TypeVarTuple arguments" - ) - - class ParamSpecArgs(_Final, _Immutable, _root=True): """The args for a ParamSpec object. @@ -1015,7 +947,7 @@ def __repr__(self): return f"{self.__origin__.__name__}.kwargs" -class ParamSpec(_Final, _Immutable, _TypeVarLike, _root=True): +class ParamSpec(_Final, _Immutable, _BoundVarianceMixin, _root=True): """Parameter specification variable. Usage:: @@ -1251,7 +1183,7 @@ def _determine_typevar_substitution(typevars, params): class _GenericAlias(_BaseGenericAlias, _root=True): def __init__(self, origin, params, *, inst=True, name=None, - _typevar_types=(TypeVar, UnpackedTypeVarTuple), + _typevar_types=(TypeVar, TypeVarTuple), _paramspec_tvars=False): super().__init__(origin, inst=inst, name=name) if not isinstance(params, tuple): @@ -1259,17 +1191,7 @@ def __init__(self, origin, params, *, inst=True, name=None, self.__args__ = tuple(... if a is _TypingEllipsis else () if a is _TypingEmpty else a for a in params) - typevars = _collect_type_vars(params, typevar_types=_typevar_types) - parameters_list = [] - # As per the note in the definition of UnpackedTypeVarTuple, if - # an unpacked TypeVarTuple appears in the type parameter list, we use - # the original (packed) TypeVarTuple in __parameters__. - for i, typevar in enumerate(typevars): - if isinstance(typevar, UnpackedTypeVarTuple): - parameters_list.append(typevar._packed) - else: - parameters_list.append(typevar) - self.__parameters__ = tuple(parameters_list) + self.__parameters__ = _collect_type_vars(params, typevar_types=_typevar_types) self._typevar_types = _typevar_types self._paramspec_tvars = _paramspec_tvars if not name: @@ -1307,13 +1229,13 @@ def __getitem__(self, params): subst = _determine_typevar_substitution(self.__parameters__, params) new_args = [] for old_arg in self.__args__: - if isinstance(old_arg, UnpackedTypeVarTuple): + if _is_unpacked_typevartuple(old_arg): # When an unpacked TypeVarTuple is used as a type parameter, # the entry in __parameters__ is the (packed) TypeVarTuple # itself - and since _determine_typevar_substitution uses # entries from __parameters__ as keys, we need to switch to # the packed version here. - new_arg = subst[old_arg._packed] + new_arg = subst[old_arg.__parameters__[0]] elif isinstance(old_arg, self._typevar_types): if isinstance(old_arg, ParamSpec): new_arg = subst[old_arg] @@ -1335,7 +1257,7 @@ def __getitem__(self, params): # Required to flatten out the args for CallableGenericAlias if self.__origin__ == collections.abc.Callable and isinstance(new_arg, tuple): new_args.extend(new_arg) - elif isinstance(old_arg, UnpackedTypeVarTuple): + elif _is_unpacked_typevartuple(old_arg): # Suppose we had: # Ts = TypeVarTuple('Ts') # class A(Generic[*Ts]): pass @@ -1387,7 +1309,7 @@ def __mro_entries__(self, bases): return (self.__origin__,) def __iter__(self): - yield StarredTuple(self) + yield Unpack[self] # _nparams is the number of accepted parameters, e.g. 0 for Hashable, @@ -1572,6 +1494,38 @@ def copy_with(self, params): return super().copy_with(params) +@_SpecialForm +def Unpack(self, parameters): + """Implementation of the type unpack operator for older versions of Python. + + The type unpack operator takes the child types from some container type + such as `Tuple[int, str]` and "pulls them out". For example, + `Dict[Unpack[Tuple[int, str]]` is equivalent to `Dict[int, str]`. + In newer versions of Python, this is implemented using the `*` operator. + + The type unpack operator can be applied to a `TypeVarTuple` instance: + + Ts = TypeVarTuple('Ts') + Tuple[Unpack[Ts]] # Equivalent to Tuple[*Ts] + + Or to a parameterised `Tuple`: + + Tuple[Unpack[Tuple[int, str]]] # Equivalent to Tuple[*Tuple[int, str]] + + There is no runtime checking of this operator. + """ + item = _type_check(parameters, f'{self} accepts only single type.') + return _UnpackGenericAlias(origin=self, params=(item,)) + + +class _UnpackGenericAlias(_GenericAlias, _root=True): + + def __repr__(self): + # `Unpack` only takes one argument, so __args__ should contain only + # a single item. + return '*' + repr(self.__args__[0]) + + class Generic: """Abstract base class for generic types. @@ -1623,10 +1577,7 @@ def __class_getitem__(cls, 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, UnpackedTypeVarTuple, ParamSpec)) - for p in params - ): + if not all(_is_typevar_like(p) for p in params): raise TypeError( f"Parameters to {cls.__name__}[...] must all be type variables " f"or parameter specification variables.") @@ -1641,7 +1592,7 @@ def __class_getitem__(cls, params): _check_type_parameter_count(cls, params) return _GenericAlias( cls, params, - _typevar_types=(TypeVar, UnpackedTypeVarTuple, ParamSpec), + _typevar_types=(TypeVar, TypeVarTuple, ParamSpec), _paramspec_tvars=True, ) @@ -1656,7 +1607,7 @@ def __init_subclass__(cls, *args, **kwargs): raise TypeError("Cannot inherit from plain Generic") if '__orig_bases__' in cls.__dict__: tvars = _collect_type_vars( - cls.__orig_bases__, (TypeVar, ParamSpec, UnpackedTypeVarTuple) + cls.__orig_bases__, (TypeVar, TypeVarTuple, ParamSpec) ) # Look for Generic[T1, ..., Tn]. # If found, tvars must be a subset of it. @@ -2214,7 +2165,7 @@ def get_origin(tp): if isinstance(tp, _AnnotatedAlias): return Annotated if isinstance(tp, (_BaseGenericAlias, GenericAlias, - ParamSpecArgs, ParamSpecKwargs, StarredTuple)): + ParamSpecArgs, ParamSpecKwargs)): return tp.__origin__ if tp is Generic: return Generic From 51870e2b1f8f0a9ea915ebf57f3c30a77f06d1e2 Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Sun, 30 Jan 2022 22:05:54 +0000 Subject: [PATCH 06/32] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20b?= =?UTF-8?q?lurb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../NEWS.d/next/Library/2022-01-30-22-05-53.bpo-43224.E-eT22.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Library/2022-01-30-22-05-53.bpo-43224.E-eT22.rst diff --git a/Misc/NEWS.d/next/Library/2022-01-30-22-05-53.bpo-43224.E-eT22.rst b/Misc/NEWS.d/next/Library/2022-01-30-22-05-53.bpo-43224.E-eT22.rst new file mode 100644 index 00000000000000..612cfcf8b2fe64 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2022-01-30-22-05-53.bpo-43224.E-eT22.rst @@ -0,0 +1 @@ +Implement support for PEP 646 in typing.py. \ No newline at end of file From bc12b492a46ee259f852a6f90702f9938673481b Mon Sep 17 00:00:00 2001 From: Matthew Rahtz Date: Tue, 1 Feb 2022 19:16:10 +0000 Subject: [PATCH 07/32] Fix call to is_unpacked_typevartuple Co-authored-by: Jelle Zijlstra --- Lib/typing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/typing.py b/Lib/typing.py index fd2be212b5ca84..0db8f006d9b803 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -780,7 +780,7 @@ def _is_unpacked_typevartuple(x): def _is_typevar_like(x): - return _is_unpacked_typevartuple or isinstance(x, (TypeVar, ParamSpec)) + return _is_unpacked_typevartuple(x) or isinstance(x, (TypeVar, ParamSpec)) class _BoundVarianceMixin: From 4a647cf59cef167761c491d5ede51a04a8eec9b2 Mon Sep 17 00:00:00 2001 From: Matthew Rahtz Date: Sun, 6 Feb 2022 18:52:00 +0000 Subject: [PATCH 08/32] Update docstring for Unpack to reflect that it isn't just for backwards compatibility now --- Lib/typing.py | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/Lib/typing.py b/Lib/typing.py index 0db8f006d9b803..32e91a92a58806 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -1496,23 +1496,33 @@ def copy_with(self, params): @_SpecialForm def Unpack(self, parameters): - """Implementation of the type unpack operator for older versions of Python. + """Type unpack operator. - The type unpack operator takes the child types from some container type - such as `Tuple[int, str]` and "pulls them out". For example, - `Dict[Unpack[Tuple[int, str]]` is equivalent to `Dict[int, str]`. - In newer versions of Python, this is implemented using the `*` operator. + The type unpack operator takes the child types from some container type, + such as `tuple[int, str]` or a `TypeVarTuple`, and 'pulls them out'. For + example: - The type unpack operator can be applied to a `TypeVarTuple` instance: + # For some generic class `Foo`: + Foo[Unpack[tuple[int, str]]] # Equivalent to Foo[int, str] Ts = TypeVarTuple('Ts') - Tuple[Unpack[Ts]] # Equivalent to Tuple[*Ts] + # Specifies that `Bar` is generic in an arbitrary number of types. + # (Think of `Ts` as a tuple of an arbitrary number of individual + # `TypeVar`s, which the `Unpack` is 'pulling out' directly into the + # `Generic[]`.) + class Bar(Generic[Unpack[Ts]]): ... + Bar[int] # Valid + Bar[int, str] # Also valid - Or to a parameterised `Tuple`: + From Python 3.11, this can also be done using the `*` operator: - Tuple[Unpack[Tuple[int, str]]] # Equivalent to Tuple[*Tuple[int, str]] + Foo[*tuple[int, str]] + class Bar(Generic[*Ts]): ... - There is no runtime checking of this operator. + Note that there is only some runtime checking of this operator. Not + everything the runtime allows may be accepted by static type checkers. + + For more information, see PEP 646. """ item = _type_check(parameters, f'{self} accepts only single type.') return _UnpackGenericAlias(origin=self, params=(item,)) From e4e68a670858492989661fd0d8e7f6c9814938e7 Mon Sep 17 00:00:00 2001 From: Matthew Rahtz Date: Sun, 6 Feb 2022 19:47:53 +0000 Subject: [PATCH 09/32] Fix _is_unpacked_typevartuple for argument of Unpack[tuple[...]] --- Lib/typing.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Lib/typing.py b/Lib/typing.py index 32e91a92a58806..9583038ea0f5e0 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -775,6 +775,8 @@ def __repr__(self): def _is_unpacked_typevartuple(x): return ( isinstance(x, _UnpackGenericAlias) + # If x is Unpack[tuple[...]], __parameters__ will be empty. + and x.__parameters__ and isinstance(x.__parameters__[0], TypeVarTuple) ) From 7fb404302efeafe833489e4f63e606c02a788e6f Mon Sep 17 00:00:00 2001 From: Matthew Rahtz Date: Sun, 6 Feb 2022 19:48:05 +0000 Subject: [PATCH 10/32] Add more tests for repr of variadic classes --- Lib/test/test_typing.py | 69 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 65 insertions(+), 4 deletions(-) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 6fc0d0823c8277..6b52ca465dbb62 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -334,10 +334,71 @@ def test_repr_is_correct(self): def test_variadic_class_repr_is_correct(self): Ts = TypeVarTuple('Ts') class A(Generic[Unpack[Ts]]): pass - B = A[Unpack[Ts], int] - self.assertTrue(repr(B[()]).endswith('A[int]')) - self.assertTrue(repr(B[float]).endswith('A[float, int]')) - self.assertTrue(repr(B[float, str]).endswith('A[float, str, int]')) + + self.assertTrue(repr(A[()]).endswith('A[]')) + self.assertTrue(repr(A[float]).endswith('A[float]')) + self.assertTrue(repr(A[float, str]).endswith('A[float, str]')) + self.assertTrue(repr( + A[Unpack[tuple[int, ...]]] + ).endswith( + 'A[*tuple[int, ...]]' + )) + self.assertTrue(repr( + A[float, Unpack[tuple[int, ...]]] + ).endswith( + 'A[float, *tuple[int, ...]]' + )) + self.assertTrue(repr( + A[Unpack[tuple[int, ...]], str] + ).endswith( + 'A[*tuple[int, ...], str]' + )) + self.assertTrue(repr( + A[float, Unpack[tuple[int, ...]], str] + ).endswith( + 'A[float, *tuple[int, ...], str]' + )) + + def test_variadic_class_alias_repr_is_correct(self): + Ts = TypeVarTuple('Ts') + class A(Generic[Unpack[Ts]]): pass + + B = A[Unpack[Ts]] + self.assertTrue(repr(B[()]).endswith('A[]')) + self.assertTrue(repr(B[float]).endswith('A[float]')) + self.assertTrue(repr(B[float, str]).endswith('A[float, str]')) + + C = A[Unpack[Ts], int] + self.assertTrue(repr(C[()]).endswith('A[int]')) + self.assertTrue(repr(C[float]).endswith('A[float, int]')) + self.assertTrue(repr(C[float, str]).endswith('A[float, str, int]')) + + D = A[int, Unpack[Ts]] + self.assertTrue(repr(D[()]).endswith('A[int]')) + self.assertTrue(repr(D[float]).endswith('A[int, float]')) + self.assertTrue(repr(D[float, str]).endswith('A[int, float, str]')) + + D = A[int, Unpack[Ts], str] + self.assertTrue(repr(D[()]).endswith('A[int, str]')) + self.assertTrue(repr(D[float]).endswith('A[int, float, str]')) + self.assertTrue(repr(D[float, bool]).endswith('A[int, float, bool, str]')) + + H = A[Unpack[Ts], Unpack[tuple[str, ...]]] + self.assertTrue(repr( + H[()] + ).endswith( + 'A[*tuple[str, ...]]') + ) + self.assertTrue(repr( + H[float] + ).endswith( + 'A[float, *tuple[str, ...]]' + )) + self.assertTrue(repr( + H[float, int] + ).endswith( + 'A[float, int, *tuple[str, ...]]' + )) def test_cannot_subclass_class(self): with self.assertRaises(TypeError): From 4b1b7f61532cff27176dccaa90f8f7bb5029db7d Mon Sep 17 00:00:00 2001 From: Matthew Rahtz Date: Sun, 6 Feb 2022 19:49:58 +0000 Subject: [PATCH 11/32] Add more tests to test_variadic_class_with_2_typevars_accepts_2_or_more_args --- Lib/test/test_typing.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 6b52ca465dbb62..d18f57d6e52351 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -465,18 +465,21 @@ def test_variadic_class_with_2_typevars_accepts_2_or_more_args(self): class A(Generic[T1, T2, Unpack[Ts]]): pass A[int, str] A[int, str, float] + A[int, str, float, bool] with self.assertRaises(TypeError): A[int] class B(Generic[T1, Unpack[Ts], T2]): pass B[int, str] B[int, str, float] + B[int, str, float, bool] with self.assertRaises(TypeError): B[int] class C(Generic[Unpack[Ts], T1, T2]): pass C[int, str] C[int, str, float] + C[int, str, float, bool] with self.assertRaises(TypeError): C[int] From c4351f9a661079a996794dad8866c364372f084c Mon Sep 17 00:00:00 2001 From: Matthew Rahtz Date: Mon, 7 Feb 2022 19:46:43 +0000 Subject: [PATCH 12/32] test_typevar_substitution: Change to loop over test cases --- Lib/test/test_typing.py | 173 +++++++++++++--------------------------- 1 file changed, 56 insertions(+), 117 deletions(-) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index d18f57d6e52351..68bfc2e6526945 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -603,123 +603,62 @@ def test_typevar_substitution(self): T2 = TypeVar('T2') Ts = TypeVarTuple('Ts') - # Too few parameters - self.assertRaises( - TypeError, - _determine_typevar_substitution, - typevars=(T1,), params=(), - ) - self.assertRaises( - TypeError, - _determine_typevar_substitution, - typevars=(T1, T2), params=(int,), - ) - self.assertRaises( - TypeError, - _determine_typevar_substitution, - typevars=(Ts, T1), params=(), - ) - self.assertRaises( - TypeError, - _determine_typevar_substitution, - typevars=(T1, Ts), params=(), - ) - self.assertRaises( - TypeError, - _determine_typevar_substitution, - typevars=(Ts, T1, T2), params=(), - ) - self.assertRaises( - TypeError, - _determine_typevar_substitution, - typevars=(T1, Ts, T2), params=(int,), - ) - self.assertRaises( - TypeError, - _determine_typevar_substitution, - typevars=(T1, T2, Ts), params=(), - ) - - # Too many parameters - self.assertRaises( - TypeError, - _determine_typevar_substitution, - typevars=(T1,), params=(int, str), - ) - self.assertRaises( - TypeError, - _determine_typevar_substitution, - typevars=(T1, T2), params=(int, str, float), - ) - - # Too many TypeVarTuples - self.assertRaises( - TypeError, - _determine_typevar_substitution, - typevars=(Ts, Ts), params=(int, str), - ) - - # Correct number of parameters, TypeVars only - self.assertEqual( - _determine_typevar_substitution((T1,), (int,)), - {T1: int}, - ) - self.assertEqual( - _determine_typevar_substitution((T1, T2), (int, str)), - {T1: int, T2: str}, - ) - - # Correct number of parameters, TypeVarTuples only - self.assertEqual( - _determine_typevar_substitution((Ts,), ()), - {Ts: ()}, - ) - self.assertEqual( - _determine_typevar_substitution((Ts,), (int,)), - {Ts: (int,)}, - ) - self.assertEqual( - _determine_typevar_substitution((Ts,), (int, str)), - {Ts: (int, str)}, - ) - - # Correct number of parameters, TypeVarTuple at the beginning - self.assertEqual( - _determine_typevar_substitution((Ts, T1), (int,)), - {Ts: (), T1: int}, - ) - self.assertEqual( - _determine_typevar_substitution((Ts, T1), (int, str)), - {Ts: (int,), T1: str}, - ) - self.assertEqual( - _determine_typevar_substitution((Ts, T1), (int, str, float)), - {Ts: (int, str), T1: float}, - ) - - # Correct number of parameters, TypeVarTuple at the end - self.assertEqual( - _determine_typevar_substitution((T1, Ts), (int,)), - {T1: int, Ts: ()}, - ) - self.assertEqual( - _determine_typevar_substitution((T1, Ts), (int, str)), - {T1: int, Ts: (str,)}, - ) - self.assertEqual( - _determine_typevar_substitution((T1, Ts), (int, str, float)), - {T1: int, Ts: (str, float)}, - ) - - # Correct number of parameters, TypeVarTuple in the middle - self.assertEqual( - _determine_typevar_substitution((T1, Ts, T2), (int, str)), - {T1: int, Ts: (), T2: str}, - ) - self.assertEqual( - _determine_typevar_substitution((T1, Ts, T2), (int, float, str)), - {T1: int, Ts: (float,), T2: str}, - ) + # Cases which should generate a TypeError. + # These are tuples of (typevars, params) arguments to + # _determine_typevar_substitution.. + test_cases = [ + # Too few parameters + ((T1,), ()), + ((T1, T2), (int,)), + ((Ts, T1), ()), + ((T1, Ts), ()), + ((Ts, T1, T2), ()), + ((Ts, T1, T2), (int,)), + ((T1, Ts, T2), ()), + ((T1, Ts, T2), (int,)), + ((T1, T2, Ts), ()), + ((T1, T2, Ts), (int,)), + # Too many parameters + ((T1,), (int, str)), + ((T1, T2), (int, str, float)), + # Too many TypeVarTuples + ((Ts, Ts), ()), + ((Ts, Ts), (int,)), + ((Ts, Ts), (int, str)), + ] + for typevars, params in test_cases: + with self.subTest(f'typevars={typevars}, params={params}'): + with self.assertRaises(TypeError): + _determine_typevar_substitution(typevars, params) + + # Cases which should succeed. + # These are tuples of (typevars, params, expected_result). + test_cases = [ + # Correct number of parameters, TypeVars only + ((T1,), (int,), {T1: int}), + ((T1, T2), (int, str), {T1: int, T2: str}), + # Correct number of parameters, TypeVarTuple only + ((Ts,), (), {Ts: ()}), + ((Ts,), (int,), {Ts: (int,)}), + ((Ts,), (int, str), {Ts: (int, str)}), + # Correct number of parameters, TypeVarTuple at the beginning + ((Ts, T1), (int,), {Ts: (), T1: int}), + ((Ts, T1), (int, str), {Ts: (int,), T1: str}), + ((Ts, T1), (int, str, float), {Ts: (int, str), T1: float}), + # Correct number of parameters, TypeVarTuple at the end + ((T1, Ts), (int,), {T1: int, Ts: ()}), + ((T1, Ts), (int, str), {T1: int, Ts: (str,)}), + ((T1, Ts), (int, str, float), {T1: int, Ts: (str, float)}), + # Correct number of parameters, TypeVarTuple in the middle + ((T1, Ts, T2), (int, str), {T1: int, Ts: (), T2: str}), + ((T1, Ts, T2), (int, float, str), {T1: int, Ts: (float,), T2: str}), + ] + for typevars, params, result_or_exception in test_cases: + with self.subTest(f'typevars={typevars}, params={params}'): + self.assertEqual( + _determine_typevar_substitution(typevars, params), + result_or_exception + ) def test_callable_args_is_correct(self): Ts = TypeVarTuple('Ts') From 95625d17d26f5af6570d8756d04637dd5b1849b0 Mon Sep 17 00:00:00 2001 From: Matthew Rahtz Date: Tue, 8 Feb 2022 19:51:11 +0000 Subject: [PATCH 13/32] Fix problems after pulling from main --- Lib/typing.py | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/Lib/typing.py b/Lib/typing.py index 4e11d5527eec5f..836c434c593c3b 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -1211,7 +1211,7 @@ class _GenericAlias(_BaseGenericAlias, _root=True): # TypeVar[bool] def __init__(self, origin, args, *, inst=True, name=None, - _typevar_types=TypeVar, + _typevar_types=(TypeVar, TypeVarTuple), _paramspec_tvars=False): super().__init__(origin, inst=inst, name=name) if not isinstance(args, tuple): @@ -1291,7 +1291,9 @@ def _determine_new_args(self, args): # edge cases. # In the example above, this would be {T3: str} - new_arg_by_param = dict(zip(self.__parameters__, args)) + new_arg_by_param = _determine_typevar_substitution( + self.__parameters__, args, + ) new_args = [] for old_arg in self.__args__: @@ -1303,6 +1305,10 @@ def _determine_new_args(self, args): f"ParamSpec, or Concatenate. Got {new_arg}") elif isinstance(old_arg, self._typevar_types): new_arg = new_arg_by_param[old_arg] + elif (TypeVarTuple in self._typevar_types + and _is_unpacked_typevartuple(old_arg)): + original_typevartuple = old_arg.__parameters__[0] + new_arg = new_arg_by_param[original_typevartuple] elif isinstance(old_arg, (_GenericAlias, GenericAlias, types.UnionType)): subparams = old_arg.__parameters__ if not subparams: @@ -1313,6 +1319,7 @@ def _determine_new_args(self, args): else: new_arg = old_arg + if self.__origin__ == collections.abc.Callable and isinstance(new_arg, tuple): # Consider the following `Callable`. # C = Callable[[int], str] @@ -1325,6 +1332,17 @@ def _determine_new_args(self, args): # ...we need to be careful; `new_args` should end up as # `(int, str, float)` rather than `([int, str], float)`. new_args.extend(new_arg) + elif _is_unpacked_typevartuple(old_arg): + # Consider the following `_GenericAlias`, `B`: + # class A(Generic[*Ts]): ... + # B = A[T, *Ts] + # If we then do: + # B[float, int, str] + # The `new_arg` corresponding to `T` will be `float`, and the + # `new_arg` corresponding to `*Ts` will be `(int, str)`. We + # should join all these types together in a flat list + # `(float, int, str)` - so again, we should `extend`. + new_args.extend(new_arg) else: new_args.append(new_arg) @@ -1583,7 +1601,7 @@ class Bar(Generic[*Ts]): ... For more information, see PEP 646. """ item = _type_check(parameters, f'{self} accepts only single type.') - return _UnpackGenericAlias(origin=self, params=(item,)) + return _UnpackGenericAlias(origin=self, args=(item,)) class _UnpackGenericAlias(_GenericAlias, _root=True): From 89cbdeb866310b0eae1dcadb6be6fbd3cfd0e201 Mon Sep 17 00:00:00 2001 From: Matthew Rahtz Date: Tue, 8 Feb 2022 19:58:56 +0000 Subject: [PATCH 14/32] _determine_typevar_substitution: Change 'params' to 'args' Technically I think 'arguments' is the correct terminology here. --- Lib/typing.py | 48 ++++++++++++++++++++++++------------------------ 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/Lib/typing.py b/Lib/typing.py index 836c434c593c3b..895f6a6d96dad7 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -1081,28 +1081,28 @@ def __dir__(self): return list(set(super().__dir__() + [attr for attr in dir(self.__origin__) if not _is_dunder(attr)])) -def _determine_typevar_substitution(typevars, params): - """Determines how to assign type parameters to type variables. +def _determine_typevar_substitution(typevars, args): + """Determines how to assign type arguments to type variables. Args: typevars: A tuple of TypeVars and (at most one) TypeVarTuple. - params: A tuple of type parameters to substitute into type variables. + args: A tuple of type arguments to substitute into type variables. Examples: T1 = TypeVar('T1') T2 = TypeVar('T2') Ts = TypeVarTuple('Ts') - typevars=(T1,), params=() => TypeError - typevars=(T1,), params=(int,) => {T1: int} - typevars=(T1,), params=(int, str) => TypeError - typevars=(T1, T2), params=(int, str) => {T1: int, T2: str} - typevars=(Ts,), params=() => {Ts: ()} - typevars=(Ts,), params=(int,) => {Ts: (int,)} - typevars=(Ts,), params=(int, str) => {Ts: (int, str)} - typevars=(T, Ts), params=() => TypeError - typevars=(T, Ts), params=(int,) => {T: int, Ts: ()} - typevars=(T, Ts) params=(int, str) => {T, int, Ts: (str,)} + typevars=(T1,), args=() => TypeError + typevars=(T1,), args=(int,) => {T1: int} + typevars=(T1,), args=(int, str) => TypeError + typevars=(T1, T2), args=(int, str) => {T1: int, T2: str} + typevars=(Ts,), args=() => {Ts: ()} + typevars=(Ts,), args=(int,) => {Ts: (int,)} + typevars=(Ts,), args=(int, str) => {Ts: (int, str)} + typevars=(T, Ts), args=() => TypeError + typevars=(T, Ts), args=(int,) => {T: int, Ts: ()} + typevars=(T, Ts) args=(int, str) => {T, int, Ts: (str,)} """ if not typevars: return {} @@ -1116,11 +1116,11 @@ def _determine_typevar_substitution(typevars, params): # Case 1: typevars does not contain any TypeVarTuples if num_typevartuples == 0: - if len(typevars) != len(params): + if len(typevars) != len(args): raise TypeError(f"Number of type variables ({len(typevars)}) " f"doesn't match number of type " - f"parameters ({len(params)})") - return dict(zip(typevars, params)) + f"parameters ({len(args)})") + return dict(zip(typevars, args)) # Case 2: typevars contains a single TypeVarTuple @@ -1134,15 +1134,15 @@ def _determine_typevar_substitution(typevars, params): # * If typevartuple_idx == 2 there are 0 TypeVars at # the end of typevars. num_end_typevars = len(typevars) - typevartuple_idx - 1 - if len(params) < num_start_typevars + num_end_typevars: + if len(args) < num_start_typevars + num_end_typevars: raise TypeError( "Expected at least {} type parameters, but only got {}".format( - num_start_typevars + num_end_typevars, len(params) + num_start_typevars + num_end_typevars, len(args) ) ) if num_start_typevars == num_end_typevars == 0: - return {typevars[0]: params} + return {typevars[0]: args} elif num_end_typevars == 0: # Ideally we wouldn't need this block, but if num_end_typevars == 0, # list[num_start_typevars:-num_end_typevars] doesn't work as expected. @@ -1150,24 +1150,24 @@ def _determine_typevar_substitution(typevars, params): **dict( zip( typevars[:num_start_typevars], - params[:num_start_typevars], + args[:num_start_typevars], ), ), - typevartuple: params[num_start_typevars:], + typevartuple: args[num_start_typevars:], } else: return { **dict( zip( typevars[:num_start_typevars], - params[:num_start_typevars], + args[:num_start_typevars], ), ), - typevartuple: params[num_start_typevars:-num_end_typevars], + typevartuple: args[num_start_typevars:-num_end_typevars], **dict( zip( typevars[-num_end_typevars:], - params[-num_end_typevars:], + args[-num_end_typevars:], ) ), } From d4c487dcb1606f1ccd47399bedd74f08f3cb7c4d Mon Sep 17 00:00:00 2001 From: Matthew Rahtz Date: Tue, 8 Feb 2022 22:50:56 +0000 Subject: [PATCH 15/32] _determine_typevar_substitution: Add support for unpacked tuples --- Lib/test/test_typing.py | 124 +++++++++++++++++++------- Lib/typing.py | 187 +++++++++++++++++++++++++++++++++------- 2 files changed, 248 insertions(+), 63 deletions(-) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 0b49f9499b585d..a055459acaf9b2 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -614,59 +614,123 @@ def test_typevar_substitution(self): Ts = TypeVarTuple('Ts') # Cases which should generate a TypeError. - # These are tuples of (typevars, params) arguments to + # These are tuples of (typevars, args) arguments to # _determine_typevar_substitution.. test_cases = [ - # Too few parameters + # Too few args + + # One TypeVar: if (potentially) 0 args ((T1,), ()), + ((T1,), (Unpack[tuple[()]],)), + ((T1,), (Unpack[tuple[int, ...]],)), + ((T1,), (Unpack[tuple[int, ...]], Unpack[tuple[int, ...]])), + # Two TypeVars: if (potentially) <= 1 args ((T1, T2), (int,)), + ((T1, T2), (Unpack[tuple[int]],)), + ((T1, T2), (Unpack[tuple[int, ...]],)), + ((T1, T2), (Unpack[tuple[int, ...]], Unpack[tuple[int, ...]])), + ((T1, T2), (Unpack[tuple[int, ...]], Unpack[tuple[int, ...]], Unpack[tuple[int, ...]])), + # One TypeVarTuple and one TypeVar: if (potentially) 0 args + # TypeVarTuple first ((Ts, T1), ()), + ((Ts, T1), (Unpack[tuple[()]],)), + ((Ts, T1), (Unpack[tuple[int, ...]],)), + ((Ts, T1), (Unpack[tuple[int, ...]], Unpack[tuple[int, ...]])), + ((Ts, T1), (Unpack[tuple[int, ...]], Unpack[tuple[int, ...]], Unpack[tuple[int, ...]])), + # TypeVarTuple last ((T1, Ts), ()), + ((T1, Ts), (Unpack[tuple[()]],)), + ((T1, Ts), (Unpack[tuple[int, ...]],)), + ((T1, Ts), (Unpack[tuple[int, ...]], Unpack[tuple[int, ...]])), + ((T1, Ts), (Unpack[tuple[int, ...]], Unpack[tuple[int, ...]], Unpack[tuple[int, ...]])), + # OneTypeVarTuple and two TypeVars: if (potentially) <= 1 args + # TypeVarTuple first ((Ts, T1, T2), ()), ((Ts, T1, T2), (int,)), + ((Ts, T1, T2), (Unpack[tuple[int]],)), + ((Ts, T1, T2), (Unpack[tuple[int, ...]],)), + ((Ts, T1, T2), (Unpack[tuple[int, ...]], Unpack[tuple[int, ...]])), + ((Ts, T1, T2), (Unpack[tuple[int, ...]], Unpack[tuple[int, ...]], Unpack[tuple[int, ...]])), + ((Ts, T1, T2), (Unpack[tuple[int, ...]], Unpack[tuple[int, ...]], Unpack[tuple[int, ...]], Unpack[tuple[int, ...]])), + # TypeVarTuple in middle ((T1, Ts, T2), ()), ((T1, Ts, T2), (int,)), + ((T1, Ts, T2), (Unpack[tuple[int]],)), + ((T1, Ts, T2), (Unpack[tuple[int, ...]],)), + ((T1, Ts, T2), (Unpack[tuple[int, ...]], Unpack[tuple[int, ...]])), + ((T1, Ts, T2), (Unpack[tuple[int, ...]], Unpack[tuple[int, ...]], Unpack[tuple[int, ...]])), + ((T1, Ts, T2), (Unpack[tuple[int, ...]], Unpack[tuple[int, ...]], Unpack[tuple[int, ...]], Unpack[tuple[int, ...]])), + # TypeVarTuple last ((T1, T2, Ts), ()), ((T1, T2, Ts), (int,)), - # Too many parameters - ((T1,), (int, str)), - ((T1, T2), (int, str, float)), + ((T1, T2, Ts), (Unpack[tuple[int]],)), + ((T1, T2, Ts), (Unpack[tuple[int, ...]],)), + ((T1, T2, Ts), (Unpack[tuple[int, ...]], Unpack[tuple[int, ...]])), + ((T1, T2, Ts), (Unpack[tuple[int, ...]], Unpack[tuple[int, ...]], Unpack[tuple[int, ...]])), + ((T1, T2, Ts), (Unpack[tuple[int, ...]], Unpack[tuple[int, ...]], Unpack[tuple[int, ...]], Unpack[tuple[int, ...]])), + + # Too many args + + # One TypeVar: if (potentially) >= 2 args + ((T1,), (int, int)), + ((T1,), (Unpack[tuple[int, int]],)), + ((T1,), (Unpack[tuple[int]], Unpack[tuple[int]])), + ((T1,), (int, Unpack[tuple[int, ...]])), + # Two TypeVars: if (potentially) >= 3 args + ((T1, T2), (int, int, int)), + ((T1, T2), (Unpack[tuple[int, int, int]],)), + ((T1, T2), (Unpack[tuple[int]], Unpack[tuple[int]], Unpack[tuple[int]])), + ((T1, T2), (int, int, Unpack[tuple[int, ...]],)), + # Too many TypeVarTuples + ((Ts, Ts), ()), ((Ts, Ts), (int,)), ((Ts, Ts), (int, str)), ] - for typevars, params in test_cases: - with self.subTest(f'typevars={typevars}, params={params}'): + for typevars, args in test_cases: + with self.subTest(f'typevars={typevars}, args={args}'): with self.assertRaises(TypeError): - _determine_typevar_substitution(typevars, params) + _determine_typevar_substitution(typevars, args) # Cases which should succeed. - # These are tuples of (typevars, params, expected_result). + # These are tuples of (typevars, args, expected_result). test_cases = [ - # Correct number of parameters, TypeVars only - ((T1,), (int,), {T1: int}), - ((T1, T2), (int, str), {T1: int, T2: str}), - # Correct number of parameters, TypeVarTuple only - ((Ts,), (), {Ts: ()}), - ((Ts,), (int,), {Ts: (int,)}), - ((Ts,), (int, str), {Ts: (int, str)}), - # Correct number of parameters, TypeVarTuple at the beginning - ((Ts, T1), (int,), {Ts: (), T1: int}), - ((Ts, T1), (int, str), {Ts: (int,), T1: str}), - ((Ts, T1), (int, str, float), {Ts: (int, str), T1: float}), - # Correct number of parameters, TypeVarTuple at the end - ((T1, Ts), (int,), {T1: int, Ts: ()}), - ((T1, Ts), (int, str), {T1: int, Ts: (str,)}), - ((T1, Ts), (int, str, float), {T1: int, Ts: (str, float)}), - # Correct number of parameters, TypeVarTuple in the middle - ((T1, Ts, T2), (int, str), {T1: int, Ts: (), T2: str}), - ((T1, Ts, T2), (int, float, str), {T1: int, Ts: (float,), T2: str}), + # Correct number of args, TypeVars only + ((T1,), (int,), {T1: int}), + ((T1,), (Unpack[tuple[int]],), {T1: int}), + ((T1, T2), (int, str), {T1: int, T2: str}), + ((T1, T2), (Unpack[tuple[int, str]],), {T1: int, T2: str}), + # Correct number of args, TypeVarTuple only + ((Ts,), (), {Ts: ()}), + ((Ts,), (int,), {Ts: (int,)}), + ((Ts,), (Unpack[tuple[int]],), {Ts: (int,)}), + ((Ts,), (int, str), {Ts: (int, str)}), + ((Ts,), (Unpack[tuple[int, ...]],), {Ts: (Unpack[tuple[int, ...]],)}), + # Correct number of args, TypeVarTuple at the beginning + ((Ts, T1), (int,), {Ts: (), T1: int}), + ((Ts, T1), (int, str), {Ts: (int,), T1: str}), + ((Ts, T1), (int, str, float), {Ts: (int, str), T1: float}), + ((Ts, T1), (Unpack[tuple[int, ...]], str), {Ts: (Unpack[tuple[int, ...]],), T1: str}), + ((Ts, T1), (Unpack[tuple[int, ...]], str, bool), {Ts: (Unpack[tuple[int, ...]], str), T1: bool}), + # Correct number of args, TypeVarTuple at the end + ((T1, Ts), (int,), {T1: int, Ts: ()}), + ((T1, Ts), (int, str), {T1: int, Ts: (str,)}), + ((T1, Ts), (int, str, float), {T1: int, Ts: (str, float)}), + ((T1, Ts), (int, Unpack[tuple[str, ...]]), {T1: int, Ts: (Unpack[tuple[str, ...]],)}), + ((T1, Ts), (int, str, Unpack[tuple[float, ...]]), {T1: int, Ts: (str, Unpack[tuple[float, ...]],)}), + # Correct number of args, TypeVarTuple in the middle + ((T1, Ts, T2), (int, str), {T1: int, Ts: (), T2: str}), + ((T1, Ts, T2), (int, float, str), {T1: int, Ts: (float,), T2: str}), + ((T1, Ts, T2), (int, Unpack[tuple[int, ...]], str), {T1: int, Ts: (Unpack[tuple[int, ...]],), T2: str}), + ((T1, Ts, T2), (int, float, Unpack[tuple[bool, ...]], str), {T1: int, Ts: (float, Unpack[tuple[bool, ...]],), T2: str}), + ((T1, Ts, T2), (int, Unpack[tuple[bool, ...]], float, str), {T1: int, Ts: (Unpack[tuple[bool, ...]], float), T2: str}), + ((T1, Ts, T2), (int, complex, Unpack[tuple[bool, ...]], float, str), {T1: int, Ts: (complex, Unpack[tuple[bool, ...]], float), T2: str}), ] - for typevars, params, result_or_exception in test_cases: - with self.subTest(f'typevars={typevars}, params={params}'): + for typevars, args, result_or_exception in test_cases: + with self.subTest(f'typevars={typevars}, args={args}'): self.assertEqual( - _determine_typevar_substitution(typevars, params), + _determine_typevar_substitution(typevars, args), result_or_exception ) diff --git a/Lib/typing.py b/Lib/typing.py index 895f6a6d96dad7..cdc8655398b09e 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -1081,6 +1081,49 @@ def __dir__(self): return list(set(super().__dir__() + [attr for attr in dir(self.__origin__) if not _is_dunder(attr)])) + +def _is_unpacked_tuple(x): + # Is `x` something like `*tuple[int]` or `*tuple[int, ...]`? + if not isinstance(x, _UnpackGenericAlias): + return False + # Alright, `x` is `Unpack[something]`. + + # `x` will always have `__args__`, because Unpack[] and Unpack[()] + # aren't legal. + unpacked_type = x.__args__[0] + + if (hasattr(unpacked_type, '__origin__') + and unpacked_type.__origin__ is tuple): + return True + else: + return False + + +def _is_unpacked_arbitrary_length_tuple(x): + if not _is_unpacked_tuple(x): + return False + unpacked_tuple = x.__args__[0] + + if not hasattr(unpacked_tuple, '__args__'): + # It's `Unpack[tuple]`. We can't make any assumptions about the length + # of the tuple, so it's effectively an arbitrary-length tuple. + return True + + tuple_args = unpacked_tuple.__args__ + if not tuple_args: + # It's `Unpack[tuple[()]]`. + return False + + last_arg = tuple_args[-1] + if last_arg is Ellipsis: + # It's `Unpack[tuple[something, ...]]`, which is arbitrary-length. + return True + + # If the arguments didn't end with an ellipsis, then it's not an + # arbitrary-length tuple. + return False + + def _determine_typevar_substitution(typevars, args): """Determines how to assign type arguments to type variables. @@ -1093,17 +1136,76 @@ def _determine_typevar_substitution(typevars, args): T2 = TypeVar('T2') Ts = TypeVarTuple('Ts') - typevars=(T1,), args=() => TypeError - typevars=(T1,), args=(int,) => {T1: int} - typevars=(T1,), args=(int, str) => TypeError - typevars=(T1, T2), args=(int, str) => {T1: int, T2: str} - typevars=(Ts,), args=() => {Ts: ()} - typevars=(Ts,), args=(int,) => {Ts: (int,)} - typevars=(Ts,), args=(int, str) => {Ts: (int, str)} - typevars=(T, Ts), args=() => TypeError - typevars=(T, Ts), args=(int,) => {T: int, Ts: ()} - typevars=(T, Ts) args=(int, str) => {T, int, Ts: (str,)} + # ==== A single TypeVar ==== + + # == Too few args == + + typevars=(T1,), args=() => TypeError + typevars=(T1,), args=(*tuple[()],) => TypeError + # tuple[int, ...] means ">= 0 ints", but we require "== 1" + typevars=(T1,), args=(*tuple[int, ...],) => TypeError + + # == Right number of args == + + typevars=(T1,), args=(int,) => {T1: int} + typevars=(T1,), args=(*tuple[int],) => {T1: int} + + # == Too many args == + + typevars=(T1,), args=(int, str) => TypeError + typevars=(T1,), args=(*tuple[int, str],) => TypeError + # We could have two ints, so this might be too many. + typevars=(T1,), args=(*tuple[int, ...],) => TypeError + + # ===== Two TypeVars ===== + + typevars=(T1, T2), args=(int, str) => {T1: int, T2: str} + typevars=(T1, T2), args=(*tuple[int, str],) => {T1: int, T2: str} + + # ===== A single TypeVarTuple ===== + + typevars=(Ts,), args=() => {Ts: ()} + typevars=(Ts,), args=(int,) => {Ts: (int,)} + typevars=(Ts,), args=(int, str) => {Ts: (int, str)} + typevars=(Ts,), args=(*tuple[()],) => {Ts: ()} + typevars=(Ts,), args=(*tuple[int],) => {Ts: (int,)} + typevars=(Ts,), args=(*tuple[int, str],) => {Ts: (int, str)} + typevars=(Ts,), args=(*tuple[int, ...],) => {Ts: (*tuple[int, ...])} + + # ===== A single TypeVar and a single TypeVarTuple ===== + + # == Too few args == + + typevars=(T, Ts), args=() => TypeError + typevars=(T, Ts), args=(*tuple[()],) => TypeError + # Again, this means ">= 0 ints", but we need ">= 1". + typevars=(T, Ts), args=(*tuple[int, ...]) => TypeError + + # == Right number of args == + + typevars=(T, Ts), args=(int,) => {T: int, Ts: ()} + typevars=(T, Ts) args=(int, str) => {T, int, Ts: (str,)} + typevars=(T, Ts), args=(*tuple[int]) => {T: int, Ts: ()} + typevars=(T, Ts), args=(*tuple[int, str]) => {T: int, Ts: (str,)} + typevars=(T, Ts), args=(int, *tuple[str, ...]) + => {T: int, Ts: (*tuple[str, ...],)} + typevars=(T, Ts), args=(*tuple[int], *tuple[str, ...]) + => {T: int, Ts: (*tuple[str, ...],)} """ + + # This would be a pretty complicated algorithm in its full generality. + # Fortunately, we can make two observations, which simplify things: + # 1. As of PEP 646, there can only be a maximum of one TypeVarTuple. + # 2. When considering unpacked arbitrary-length tuples + # like *tuple[int, ...], we can't assign them or any part of them to + # regular TypeVars: + # * We can't assign the whole *tuple[int, ...] to a single TypeVar + # because...well, it represents an arbitrary number of types, and a + # TypeVar only holds exactly one type. + # * We can't assign one of the `int`s to a TypeVar, because we can't + # be sure there'll be any `int`s at all: tuple[int, ...] is a tuple + # of *zero* or more ints. + if not typevars: return {} @@ -1113,13 +1215,28 @@ def _determine_typevar_substitution(typevars, args): raise TypeError("At most 1 TypeVarTuple may be used in a type " f"parameter list, but saw {num_typevartuples}") + # First, we should go through `args` to replace e.g. *tuple[int] with int. + new_args = [] + for arg in args: + if (_is_unpacked_tuple(arg) + and not _is_unpacked_arbitrary_length_tuple(arg)): + arg_tuple = arg.__args__[0] # The actual tuple[int] + new_args.extend(arg_tuple.__args__) + else: + new_args.append(arg) + args = tuple(new_args) + # Case 1: typevars does not contain any TypeVarTuples if num_typevartuples == 0: + if any(_is_unpacked_arbitrary_length_tuple(arg) for arg in args): + raise TypeError(f"Cannot assign type arguments '{args}' to type " + f"variables '{typevars}': cannot assign " + "unpacked arbitrary-length tuple to a TypeVar") if len(typevars) != len(args): raise TypeError(f"Number of type variables ({len(typevars)}) " f"doesn't match number of type " - f"parameters ({len(args)})") + f"arguments ({len(args)})") return dict(zip(typevars, args)) # Case 2: typevars contains a single TypeVarTuple @@ -1134,6 +1251,11 @@ def _determine_typevar_substitution(typevars, args): # * If typevartuple_idx == 2 there are 0 TypeVars at # the end of typevars. num_end_typevars = len(typevars) - typevartuple_idx - 1 + + # Even if one of the args is an unpacked arbitrary-length tuple, we still + # need at least as many args as normal TypeVars. + # Couldn't we split the arbitrary-length tuples across multiple TypeVars? + # No, because an arbitrary-length tuple could have length zero! if len(args) < num_start_typevars + num_end_typevars: raise TypeError( "Expected at least {} type parameters, but only got {}".format( @@ -1142,34 +1264,33 @@ def _determine_typevar_substitution(typevars, args): ) if num_start_typevars == num_end_typevars == 0: + # It's just a single TypeVarTuple. return {typevars[0]: args} elif num_end_typevars == 0: - # Ideally we wouldn't need this block, but if num_end_typevars == 0, - # list[num_start_typevars:-num_end_typevars] doesn't work as expected. + args_for_typevartuple = args[num_start_typevars:] + args_for_typevars = args[:num_start_typevars] + if any(_is_unpacked_arbitrary_length_tuple(arg) + for arg in args_for_typevars): + raise TypeError(f"Cannot assign type arguments '{args}' to type " + f"variables '{typevars}': cannot assign " + "arbitrary-length tuple to a TypeVar") return { - **dict( - zip( - typevars[:num_start_typevars], - args[:num_start_typevars], - ), - ), - typevartuple: args[num_start_typevars:], + **dict(zip(typevars[:num_start_typevars], args_for_typevars)), + typevartuple: args_for_typevartuple, } else: + args_for_left_typevars = args[:num_start_typevars] + args_for_typevartuple = args[num_start_typevars:-num_end_typevars] + args_for_right_typevars = args[-num_end_typevars:] + if any(_is_unpacked_arbitrary_length_tuple(arg) + for arg in args_for_left_typevars + args_for_right_typevars): + raise TypeError(f"Cannot assign type arguments '{args}' to type " + f"variables '{typevars}': cannot assign " + "arbitrary-length tuple to a TypeVar") return { - **dict( - zip( - typevars[:num_start_typevars], - args[:num_start_typevars], - ), - ), - typevartuple: args[num_start_typevars:-num_end_typevars], - **dict( - zip( - typevars[-num_end_typevars:], - args[-num_end_typevars:], - ) - ), + **dict(zip(typevars[:num_start_typevars], args_for_left_typevars)), + typevartuple: args_for_typevartuple, + **dict(zip(typevars[-num_end_typevars:], args_for_right_typevars)), } From ac4b5f988a63f83ab8ce46b3e68c681db0e096d1 Mon Sep 17 00:00:00 2001 From: Matthew Rahtz Date: Mon, 14 Feb 2022 19:47:46 +0000 Subject: [PATCH 16/32] Improve docstring for _BoundVarianceMixin --- Lib/typing.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Lib/typing.py b/Lib/typing.py index cdc8655398b09e..4c1a519c8453e7 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -787,7 +787,13 @@ def _is_typevar_like(x): class _BoundVarianceMixin: - """Mixin giving __init__ bound and variance arguments.""" + """Mixin giving __init__ bound and variance arguments. + + This is used by TypeVar and ParamSpec, which both employ the notions of + a type 'bound' (restricting type arguments to be a subtype of some + specified type) and type 'variance' (determining subtype relations between + generic types). + """ def __init__(self, bound, covariant, contravariant): """Used to setup TypeVars and ParamSpec's bound, covariant and contravariant attributes. From fbf1a95752fe8f914dfe8b91dfd5d5c42ef27f7d Mon Sep 17 00:00:00 2001 From: Matthew Rahtz Date: Tue, 15 Feb 2022 18:55:28 +0000 Subject: [PATCH 17/32] Remove arity checks for things involving a TypeVarTuple --- Lib/test/test_typing.py | 6 ---- Lib/typing.py | 78 +++++++++++------------------------------ 2 files changed, 21 insertions(+), 63 deletions(-) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index a055459acaf9b2..65e8ed06e61678 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -476,22 +476,16 @@ class A(Generic[T1, T2, Unpack[Ts]]): pass A[int, str] A[int, str, float] A[int, str, float, bool] - with self.assertRaises(TypeError): - A[int] class B(Generic[T1, Unpack[Ts], T2]): pass B[int, str] B[int, str, float] B[int, str, float, bool] - with self.assertRaises(TypeError): - B[int] class C(Generic[Unpack[Ts], T1, T2]): pass C[int, str] C[int, str, float] C[int, str, float, bool] - with self.assertRaises(TypeError): - C[int] def test_variadic_args_annotations_are_correct(self): Ts = TypeVarTuple('Ts') diff --git a/Lib/typing.py b/Lib/typing.py index 4c1a519c8453e7..3d5fa2e076d1bb 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -231,58 +231,16 @@ def _collect_type_vars(types_, typevar_types=None): return tuple(tvars) -def _check_type_parameter_count(cls, type_params): - """Checks whether number of type parameters to a generic class is correct. - - This function uses introspection on `cls` to determine the expected number - of type parameters, and gives a nice error message in case of count - mismatch. +def _check_generic(cls, parameters, elen): + """Check correct count for parameters of a generic cls (internal helper). + This gives a nice error message in case of count mismatch. """ - actual_num_type_params = len(type_params) - - # First, can we determine the exact number of type parameters expected? - - if isinstance(cls, _SpecialGenericAlias): - # For types like List and Tuple, we know exactly how many parameters - # to expect. - expected_num_type_params = cls._nparams - elif all(isinstance(p, (TypeVar, ParamSpec)) for p in cls.__parameters__): - # If all type variables are `TypeVar` or `ParamSpec`, the number - # of them should exactly match the number of type variables. - expected_num_type_params = len(cls.__parameters__) - else: - expected_num_type_params = None - - if expected_num_type_params is not None: - if expected_num_type_params == 0: - raise TypeError(f"{cls} is not a generic class") - if actual_num_type_params > expected_num_type_params: - error = "Too many" - elif actual_num_type_params < expected_num_type_params: - error = "Too few" - else: - return - msg = (f"{error} arguments for {cls}; " - f"actual {actual_num_type_params}, " - f"expected {expected_num_type_params}") - raise TypeError(msg) - - # Second, if we can't determine the exact number of type parameters - # expected, can we determine the minimum number of type parameters expected? - - if any(isinstance(p, TypeVarTuple) for p in cls.__parameters__): - min_num_type_params = len([ - p for p in cls.__parameters__ - if isinstance(p, (TypeVar, ParamSpec)) - ]) - if actual_num_type_params >= min_num_type_params: - return - else: - raise TypeError(f"Too few parameters for {cls}; " - f"actual {actual_num_type_params}, " - f"expected at least {min_num_type_params}") - - raise TypeError(f"Couldn't determine type parameter requirements for {cls}") + if not elen: + raise TypeError(f"{cls} is not a generic class") + alen = len(parameters) + if alen != elen: + raise TypeError(f"Too {'many' if alen > elen else 'few'} arguments for {cls};" + f" actual {alen}, expected {elen} " + str(parameters)) def _prepare_paramspec_params(cls, params): @@ -295,7 +253,7 @@ def _prepare_paramspec_params(cls, params): assert isinstance(cls.__parameters__[0], ParamSpec) return (params,) else: - _check_type_parameter_count(cls, params) + _check_generic(cls, params, len(cls.__parameters__)) _params = [] # Convert lists to tuples to help other libraries cache the results. for p, tvar in zip(params, cls.__parameters__): @@ -1395,8 +1353,11 @@ def __getitem__(self, args): if (self._paramspec_tvars and any(isinstance(t, ParamSpec) for t in self.__parameters__)): args = _prepare_paramspec_params(self, args) - else: - _check_type_parameter_count(self, args) + elif not any(isinstance(p, TypeVarTuple) for p in self.__parameters__): + # We only run this if there are no TypeVarTuples, because we + # don't check variadic generic arity at runtime (to reduce + # complexity of typing.py). + _check_generic(self, args, len(self.__parameters__)) new_args = self._determine_new_args(args) r = self.copy_with(new_args) @@ -1536,7 +1497,7 @@ def __getitem__(self, params): params = (params,) msg = "Parameters to generic types must be types." params = tuple(_type_check(p, msg) for p in params) - _check_type_parameter_count(self, params) + _check_generic(self, params, self._nparams) return self.copy_with(params) def copy_with(self, params): @@ -1801,8 +1762,11 @@ def __class_getitem__(cls, params): # Subscripting a regular Generic subclass. if any(isinstance(t, ParamSpec) for t in cls.__parameters__): params = _prepare_paramspec_params(cls, params) - else: - _check_type_parameter_count(cls, params) + elif not any(isinstance(p, TypeVarTuple) for p in cls.__parameters__): + # We only run this if there are no TypeVarTuples, because we + # don't check variadic generic arity at runtime (to reduce + # complexity of typing.py). + _check_generic(cls, params, len(cls.__parameters__)) return _GenericAlias( cls, params, _typevar_types=(TypeVar, TypeVarTuple, ParamSpec), From 5e0fcaba3c9203df8eb4d176cb717667b53a0594 Mon Sep 17 00:00:00 2001 From: Matthew Rahtz Date: Tue, 15 Feb 2022 19:09:22 +0000 Subject: [PATCH 18/32] Add basic __args__ tests --- Lib/test/test_typing.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 65e8ed06e61678..49b6c3ba9937c0 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -421,12 +421,15 @@ class C(Ts): pass with self.assertRaises(TypeError): class C(Unpack[Ts]): pass - def test_variadic_class_accepts_arbitrary_number_of_parameters(self): + def test_variadic_class_args_are_correct(self): Ts = TypeVarTuple('Ts') - class C(Generic[Unpack[Ts]]): pass - C[()] - C[int] - C[int, str] + class A(Generic[Unpack[Ts]]): pass + B = A[()] + self.assertEqual(B.__args__, ()) + C = A[int] + self.assertEqual(C.__args__, (int,)) + D = A[int, str] + self.assertEqual(D.__args__, (int, str)) def test_variadic_class_with_duplicate_typevartuples_fails(self): Ts1 = TypeVarTuple('Ts1') From 55f3a5d169add78807dc5bd5a099b8648f8f1849 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Tue, 22 Feb 2022 21:10:30 -0800 Subject: [PATCH 19/32] fix lint in NEWS file --- .../next/Library/2022-01-30-22-05-53.bpo-43224.E-eT22.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Library/2022-01-30-22-05-53.bpo-43224.E-eT22.rst b/Misc/NEWS.d/next/Library/2022-01-30-22-05-53.bpo-43224.E-eT22.rst index 612cfcf8b2fe64..c248dd7b28778c 100644 --- a/Misc/NEWS.d/next/Library/2022-01-30-22-05-53.bpo-43224.E-eT22.rst +++ b/Misc/NEWS.d/next/Library/2022-01-30-22-05-53.bpo-43224.E-eT22.rst @@ -1 +1 @@ -Implement support for PEP 646 in typing.py. \ No newline at end of file +Implement support for PEP 646 in typing.py. From 5212108099b6573ce89be7b80ca80f8f8952f242 Mon Sep 17 00:00:00 2001 From: Matthew Rahtz Date: Sat, 26 Feb 2022 12:13:44 +0000 Subject: [PATCH 20/32] Make repr(A[]) == A[()] instead of A[] --- Lib/test/test_typing.py | 4 ++-- Lib/typing.py | 6 +++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 85b940406ac767..2fd722c2483f43 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -404,7 +404,7 @@ def test_variadic_class_repr_is_correct(self): Ts = TypeVarTuple('Ts') class A(Generic[Unpack[Ts]]): pass - self.assertTrue(repr(A[()]).endswith('A[]')) + self.assertTrue(repr(A[()]).endswith('A[()]')) self.assertTrue(repr(A[float]).endswith('A[float]')) self.assertTrue(repr(A[float, str]).endswith('A[float, str]')) self.assertTrue(repr( @@ -433,7 +433,7 @@ def test_variadic_class_alias_repr_is_correct(self): class A(Generic[Unpack[Ts]]): pass B = A[Unpack[Ts]] - self.assertTrue(repr(B[()]).endswith('A[]')) + self.assertTrue(repr(B[()]).endswith('A[()]')) self.assertTrue(repr(B[float]).endswith('A[float]')) self.assertTrue(repr(B[float, str]).endswith('A[float, str]')) diff --git a/Lib/typing.py b/Lib/typing.py index 787d0bae3c654e..a40f4e76ed3d62 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -1513,7 +1513,11 @@ def __repr__(self): name = 'typing.' + self._name else: name = _type_repr(self.__origin__) - args = ", ".join([_type_repr(a) for a in self.__args__]) + if self.__args__: + args = ", ".join([_type_repr(a) for a in self.__args__]) + else: + # To ensure the repr is eval-able. + args = "()" return f'{name}[{args}]' def __reduce__(self): From f2811a07d3b915f1ac8d6b0f952625045c83cec5 Mon Sep 17 00:00:00 2001 From: Matthew Rahtz Date: Sat, 26 Feb 2022 12:17:50 +0000 Subject: [PATCH 21/32] Add some extra repr tests --- Lib/test/test_typing.py | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 2fd722c2483f43..671ca19c81b96e 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -433,38 +433,43 @@ def test_variadic_class_alias_repr_is_correct(self): class A(Generic[Unpack[Ts]]): pass B = A[Unpack[Ts]] + self.assertTrue(repr(B).endswith('A[*Ts]')) self.assertTrue(repr(B[()]).endswith('A[()]')) self.assertTrue(repr(B[float]).endswith('A[float]')) self.assertTrue(repr(B[float, str]).endswith('A[float, str]')) C = A[Unpack[Ts], int] + self.assertTrue(repr(C).endswith('A[*Ts, int]')) self.assertTrue(repr(C[()]).endswith('A[int]')) self.assertTrue(repr(C[float]).endswith('A[float, int]')) self.assertTrue(repr(C[float, str]).endswith('A[float, str, int]')) D = A[int, Unpack[Ts]] + self.assertTrue(repr(D).endswith('A[int, *Ts]')) self.assertTrue(repr(D[()]).endswith('A[int]')) self.assertTrue(repr(D[float]).endswith('A[int, float]')) self.assertTrue(repr(D[float, str]).endswith('A[int, float, str]')) - D = A[int, Unpack[Ts], str] - self.assertTrue(repr(D[()]).endswith('A[int, str]')) - self.assertTrue(repr(D[float]).endswith('A[int, float, str]')) - self.assertTrue(repr(D[float, bool]).endswith('A[int, float, bool, str]')) + E = A[int, Unpack[Ts], str] + self.assertTrue(repr(E).endswith('A[int, *Ts, str]')) + self.assertTrue(repr(E[()]).endswith('A[int, str]')) + self.assertTrue(repr(E[float]).endswith('A[int, float, str]')) + self.assertTrue(repr(E[float, bool]).endswith('A[int, float, bool, str]')) - H = A[Unpack[Ts], Unpack[tuple[str, ...]]] + F = A[Unpack[Ts], Unpack[tuple[str, ...]]] + self.assertTrue(repr(F).endswith('A[*Ts, *tuple[str, ...]]')) self.assertTrue(repr( - H[()] + F[()] ).endswith( 'A[*tuple[str, ...]]') ) self.assertTrue(repr( - H[float] + F[float] ).endswith( 'A[float, *tuple[str, ...]]' )) self.assertTrue(repr( - H[float, int] + F[float, int] ).endswith( 'A[float, int, *tuple[str, ...]]' )) From f5f4440db92f77539401e65bf131d10208116bcd Mon Sep 17 00:00:00 2001 From: Matthew Rahtz Date: Sat, 26 Feb 2022 12:18:36 +0000 Subject: [PATCH 22/32] Remove debug print --- Lib/typing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/typing.py b/Lib/typing.py index a40f4e76ed3d62..87ca2c6f723a7b 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -243,7 +243,7 @@ def _check_generic(cls, parameters, elen): alen = len(parameters) if alen != elen: raise TypeError(f"Too {'many' if alen > elen else 'few'} arguments for {cls};" - f" actual {alen}, expected {elen} " + str(parameters)) + f" actual {alen}, expected {elen}") def _prepare_paramspec_params(cls, params): From 45ff353901398eb8eeeb114f8b9bc418d27972b0 Mon Sep 17 00:00:00 2001 From: Matthew Rahtz Date: Sat, 26 Feb 2022 12:21:01 +0000 Subject: [PATCH 23/32] Make a couple of simplifications --- Lib/typing.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/Lib/typing.py b/Lib/typing.py index 87ca2c6f723a7b..88450cac29e5b8 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -1125,11 +1125,7 @@ def _is_unpacked_tuple(x): # aren't legal. unpacked_type = x.__args__[0] - if (hasattr(unpacked_type, '__origin__') - and unpacked_type.__origin__ is tuple): - return True - else: - return False + return getattr(unpacked_type, '__origin__', None) is tuple def _is_unpacked_arbitrary_length_tuple(x): @@ -1242,8 +1238,7 @@ def _determine_typevar_substitution(typevars, args): if not typevars: return {} - num_typevartuples = len([x for x in typevars - if isinstance(x, TypeVarTuple)]) + num_typevartuples = sum(1 for x in typevars if isinstance(x, TypeVarTuple)) if num_typevartuples > 1: raise TypeError("At most 1 TypeVarTuple may be used in a type " f"parameter list, but saw {num_typevartuples}") @@ -1476,7 +1471,6 @@ def _determine_new_args(self, args): else: new_arg = old_arg - if self.__origin__ == collections.abc.Callable and isinstance(new_arg, tuple): # Consider the following `Callable`. # C = Callable[[int], str] From ba31d6ec98e0936d4714c01156e492576fefaf10 Mon Sep 17 00:00:00 2001 From: Matthew Rahtz Date: Sat, 26 Feb 2022 12:21:55 +0000 Subject: [PATCH 24/32] Optimize is_typevar_like --- Lib/typing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/typing.py b/Lib/typing.py index 88450cac29e5b8..c81df096f9cccc 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -800,7 +800,7 @@ def _is_unpacked_typevartuple(x): def _is_typevar_like(x): - return _is_unpacked_typevartuple(x) or isinstance(x, (TypeVar, ParamSpec)) + return isinstance(x, (TypeVar, ParamSpec)) or _is_unpacked_typevartuple(x) class _BoundVarianceMixin: From 67893260ad5f0e2d5dd04064709075a537e8d55c Mon Sep 17 00:00:00 2001 From: Matthew Rahtz Date: Sat, 26 Feb 2022 13:26:52 +0000 Subject: [PATCH 25/32] Refactor tests for variadic get_origin and get_origin into separate cases --- Lib/test/test_typing.py | 69 +++++++++++++++++++++-------------------- 1 file changed, 36 insertions(+), 33 deletions(-) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 671ca19c81b96e..5bf7f8058fee2b 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -4021,15 +4021,10 @@ def test_top_level_class_var(self): class GetUtilitiesTestCase(TestCase): def test_get_origin(self): T = TypeVar('T') - Ts = TypeVarTuple('Ts') P = ParamSpec('P') class C(Generic[T]): pass self.assertIs(get_origin(C[int]), C) self.assertIs(get_origin(C[T]), C) - class D(Generic[Unpack[Ts]]): pass - self.assertIs(get_origin(D[int]), D) - self.assertIs(get_origin(D[T]), D) - self.assertIs(get_origin(D[Unpack[Ts]]), D) self.assertIs(get_origin(int), None) self.assertIs(get_origin(ClassVar[int]), ClassVar) self.assertIs(get_origin(Union[int, str]), Union) @@ -4048,19 +4043,18 @@ class D(Generic[Unpack[Ts]]): pass self.assertIs(get_origin(P.args), P) self.assertIs(get_origin(P.kwargs), P) + def test_get_origin_typevartuple(self): + Ts = TypeVarTuple('Ts') + class D(Generic[Unpack[Ts]]): pass + self.assertIs(get_origin(D[int]), D) + self.assertIs(get_origin(D[T]), D) + self.assertIs(get_origin(D[Unpack[Ts]]), D) + def test_get_args(self): T = TypeVar('T') - Ts = TypeVarTuple('Ts') class C(Generic[T]): pass self.assertEqual(get_args(C[int]), (int,)) self.assertEqual(get_args(C[T]), (T,)) - class D(Generic[Unpack[Ts]]): pass - self.assertEqual(get_args(D[int]), (int,)) - self.assertEqual(get_args(D[T]), (T,)) - self.assertEqual(get_args(D[Unpack[Ts]]), (Unpack[Ts],)) - self.assertEqual(get_args(D[T, Unpack[Ts]]), (T, Unpack[Ts],)) - self.assertEqual(get_args(D[Unpack[Ts], T]), (Unpack[Ts], T)) - self.assertEqual(get_args(D[Unpack[Ts], int, ...]), (Unpack[Ts], int, ...)) self.assertEqual(get_args(int), ()) self.assertEqual(get_args(ClassVar[int]), (int,)) self.assertEqual(get_args(Union[int, str]), (int, str)) @@ -4076,26 +4070,6 @@ class D(Generic[Unpack[Ts]]): pass (int, Callable[[Tuple[T, ...]], str])) self.assertEqual(get_args(Tuple[int, ...]), (int, ...)) self.assertEqual(get_args(Tuple[()]), ((),)) - - self.assertEqual(get_args(tuple[Unpack[Ts]]), (Unpack[Ts],)) - self.assertEqual(get_args(Tuple[Unpack[Ts]]), (Unpack[Ts],)) - - self.assertEqual(get_args(tuple[Unpack[Ts], int]), (Unpack[Ts], int)) - self.assertEqual(get_args(Tuple[Unpack[Ts], int]), (Unpack[Ts], int)) - - self.assertEqual(get_args(tuple[int, Unpack[Ts]]), (int, Unpack[Ts])) - self.assertEqual(get_args(Tuple[int, Unpack[Ts]]), (int, Unpack[Ts])) - - self.assertEqual(get_args(tuple[int, Unpack[Ts], str]), - (int, Unpack[Ts], str)) - self.assertEqual(get_args(Tuple[int, Unpack[Ts], str]), - (int, Unpack[Ts], str)) - - self.assertEqual(get_args(tuple[Unpack[Ts], int, ...]), - (Unpack[Ts], int, ...)) - self.assertEqual(get_args(Tuple[Unpack[Ts]]), - (Unpack[Ts],)) - self.assertEqual(get_args(Annotated[T, 'one', 2, ['three']]), (T, 'one', 2, ['three'])) self.assertEqual(get_args(List), ()) self.assertEqual(get_args(Tuple), ()) @@ -4113,6 +4087,35 @@ class D(Generic[Unpack[Ts]]): pass (Concatenate[int, P], int)) self.assertEqual(get_args(list | str), (list, str)) + def test_get_args_typevartuple(self): + Ts = TypeVarTuple('Ts') + class D(Generic[Unpack[Ts]]): pass + self.assertEqual(get_args(D[int]), (int,)) + self.assertEqual(get_args(D[T]), (T,)) + self.assertEqual(get_args(D[Unpack[Ts]]), (Unpack[Ts],)) + self.assertEqual(get_args(D[T, Unpack[Ts]]), (T, Unpack[Ts],)) + self.assertEqual(get_args(D[Unpack[Ts], T]), (Unpack[Ts], T)) + self.assertEqual(get_args(D[Unpack[Ts], int, ...]), (Unpack[Ts], int, ...)) + + self.assertEqual(get_args(tuple[Unpack[Ts]]), (Unpack[Ts],)) + self.assertEqual(get_args(Tuple[Unpack[Ts]]), (Unpack[Ts],)) + + self.assertEqual(get_args(tuple[Unpack[Ts], int]), (Unpack[Ts], int)) + self.assertEqual(get_args(Tuple[Unpack[Ts], int]), (Unpack[Ts], int)) + + self.assertEqual(get_args(tuple[int, Unpack[Ts]]), (int, Unpack[Ts])) + self.assertEqual(get_args(Tuple[int, Unpack[Ts]]), (int, Unpack[Ts])) + + self.assertEqual(get_args(tuple[int, Unpack[Ts], str]), + (int, Unpack[Ts], str)) + self.assertEqual(get_args(Tuple[int, Unpack[Ts], str]), + (int, Unpack[Ts], str)) + + self.assertEqual(get_args(tuple[Unpack[Ts], int, ...]), + (Unpack[Ts], int, ...)) + self.assertEqual(get_args(Tuple[Unpack[Ts]]), + (Unpack[Ts],)) + class CollectionsAbcTests(BaseTestCase): From caf435e4b1e16f9b8b11ebde6481bc96d3dac91b Mon Sep 17 00:00:00 2001 From: Matthew Rahtz Date: Sat, 26 Feb 2022 13:28:46 +0000 Subject: [PATCH 26/32] Remove test for a parameterization that's actually invalid --- Lib/test/test_typing.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 5bf7f8058fee2b..97928a60af8465 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -4095,7 +4095,6 @@ class D(Generic[Unpack[Ts]]): pass self.assertEqual(get_args(D[Unpack[Ts]]), (Unpack[Ts],)) self.assertEqual(get_args(D[T, Unpack[Ts]]), (T, Unpack[Ts],)) self.assertEqual(get_args(D[Unpack[Ts], T]), (Unpack[Ts], T)) - self.assertEqual(get_args(D[Unpack[Ts], int, ...]), (Unpack[Ts], int, ...)) self.assertEqual(get_args(tuple[Unpack[Ts]]), (Unpack[Ts],)) self.assertEqual(get_args(Tuple[Unpack[Ts]]), (Unpack[Ts],)) From 98359384d1d912240083ae9fe4c5935c15849577 Mon Sep 17 00:00:00 2001 From: Matthew Rahtz Date: Sat, 26 Feb 2022 13:41:16 +0000 Subject: [PATCH 27/32] Move some tests from GetUtilitiesTestCase to TypeVarTupleTests --- Lib/test/test_typing.py | 153 ++++++++++++++++++++-------------------- 1 file changed, 77 insertions(+), 76 deletions(-) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 97928a60af8465..aafbb40463ecb0 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -486,6 +486,7 @@ class C(Ts): pass class C(Unpack[Ts]): pass def test_variadic_class_args_are_correct(self): + T = TypeVar('T') Ts = TypeVarTuple('Ts') class A(Generic[Unpack[Ts]]): pass B = A[()] @@ -494,6 +495,82 @@ class A(Generic[Unpack[Ts]]): pass self.assertEqual(C.__args__, (int,)) D = A[int, str] self.assertEqual(D.__args__, (int, str)) + E = A[T] + self.assertEqual(E.__args__, (T,)) + F = A[Unpack[Ts]] + self.assertEqual(F.__args__, (Unpack[Ts],)) + G = A[T, Unpack[Ts]] + self.assertEqual(G.__args__, (T, Unpack[Ts])) + H = A[Unpack[Ts], T] + self.assertEqual(H.__args__, (Unpack[Ts], T)) + + def test_variadic_class_origin_is_correct(self): + Ts = TypeVarTuple('Ts') + class D(Generic[Unpack[Ts]]): pass + self.assertIs(D[int].__origin__, D) + self.assertIs(D[T].__origin__, D) + self.assertIs(D[Unpack[Ts]].__origin__, D) + + def test_tuple_args_are_correct(self): + Ts = TypeVarTuple('Ts') + + self.assertEqual(tuple[Unpack[Ts]].__args__, (Unpack[Ts],)) + self.assertEqual(Tuple[Unpack[Ts]].__args__, (Unpack[Ts],)) + + self.assertEqual(tuple[Unpack[Ts], int].__args__, (Unpack[Ts], int)) + self.assertEqual(Tuple[Unpack[Ts], int].__args__, (Unpack[Ts], int)) + + self.assertEqual(tuple[int, Unpack[Ts]].__args__, (int, Unpack[Ts])) + self.assertEqual(Tuple[int, Unpack[Ts]].__args__, (int, Unpack[Ts])) + + self.assertEqual(tuple[int, Unpack[Ts], str].__args__, + (int, Unpack[Ts], str)) + self.assertEqual(Tuple[int, Unpack[Ts], str].__args__, + (int, Unpack[Ts], str)) + + self.assertEqual(tuple[Unpack[Ts], int].__args__, (Unpack[Ts], int)) + self.assertEqual(Tuple[Unpack[Ts]].__args__, (Unpack[Ts],)) + + def test_callable_args_are_correct(self): + Ts = TypeVarTuple('Ts') + Ts1 = TypeVarTuple('Ts1') + Ts2 = TypeVarTuple('Ts2') + + # TypeVarTuple in the arguments + + a = Callable[[Unpack[Ts]], None] + self.assertEqual(a.__args__, (Unpack[Ts], type(None))) + + b = Callable[[int, Unpack[Ts]], None] + self.assertEqual(b.__args__, (int, Unpack[Ts], type(None))) + + c = Callable[[Unpack[Ts], int], None] + self.assertEqual(c.__args__, (Unpack[Ts], int, type(None))) + + d = Callable[[str, Unpack[Ts], int], None] + self.assertEqual(d.__args__, (str, Unpack[Ts], int, type(None))) + + # TypeVarTuple as the return + + e = Callable[[None], Unpack[Ts]] + self.assertEqual(e.__args__, (type(None), Unpack[Ts])) + + f = Callable[[None], tuple[int, Unpack[Ts]]] + self.assertEqual(f.__args__, (type(None), tuple[int, Unpack[Ts]])) + + g = Callable[[None], tuple[Unpack[Ts], int]] + self.assertEqual(g.__args__, (type(None), tuple[Unpack[Ts], int])) + + h = Callable[[None], tuple[str, Unpack[Ts], int]] + self.assertEqual(h.__args__, (type(None), tuple[str, Unpack[Ts], int])) + + # TypeVarTuple in both + + i = Callable[[Unpack[Ts]], Unpack[Ts]] + self.assertEqual(i.__args__, (Unpack[Ts], Unpack[Ts])) + + j = Callable[[Unpack[Ts1]], Unpack[Ts2]] + self.assertEqual(j.__args__, (Unpack[Ts1], Unpack[Ts2])) def test_variadic_class_with_duplicate_typevartuples_fails(self): Ts1 = TypeVarTuple('Ts1') @@ -795,47 +872,6 @@ def test_typevar_substitution(self): result_or_exception ) - def test_callable_args_is_correct(self): - Ts = TypeVarTuple('Ts') - Ts1 = TypeVarTuple('Ts1') - Ts2 = TypeVarTuple('Ts2') - - # TypeVarTuple in the arguments - - a = Callable[[Unpack[Ts]], None] - self.assertEqual(a.__args__, (Unpack[Ts], type(None))) - - b = Callable[[int, Unpack[Ts]], None] - self.assertEqual(b.__args__, (int, Unpack[Ts], type(None))) - - c = Callable[[Unpack[Ts], int], None] - self.assertEqual(c.__args__, (Unpack[Ts], int, type(None))) - - d = Callable[[str, Unpack[Ts], int], None] - self.assertEqual(d.__args__, (str, Unpack[Ts], int, type(None))) - - # TypeVarTuple as the return - - e = Callable[[None], Unpack[Ts]] - self.assertEqual(e.__args__, (type(None), Unpack[Ts])) - - f = Callable[[None], tuple[int, Unpack[Ts]]] - self.assertEqual(f.__args__, (type(None), tuple[int, Unpack[Ts]])) - - g = Callable[[None], tuple[Unpack[Ts], int]] - self.assertEqual(g.__args__, (type(None), tuple[Unpack[Ts], int])) - - h = Callable[[None], tuple[str, Unpack[Ts], int]] - self.assertEqual(h.__args__, (type(None), tuple[str, Unpack[Ts], int])) - - # TypeVarTuple in both - - i = Callable[[Unpack[Ts]], Unpack[Ts]] - self.assertEqual(i.__args__, (Unpack[Ts], Unpack[Ts])) - - j = Callable[[Unpack[Ts1]], Unpack[Ts2]] - self.assertEqual(j.__args__, (Unpack[Ts1], Unpack[Ts2])) - class UnionTests(BaseTestCase): @@ -4043,13 +4079,6 @@ class C(Generic[T]): pass self.assertIs(get_origin(P.args), P) self.assertIs(get_origin(P.kwargs), P) - def test_get_origin_typevartuple(self): - Ts = TypeVarTuple('Ts') - class D(Generic[Unpack[Ts]]): pass - self.assertIs(get_origin(D[int]), D) - self.assertIs(get_origin(D[T]), D) - self.assertIs(get_origin(D[Unpack[Ts]]), D) - def test_get_args(self): T = TypeVar('T') class C(Generic[T]): pass @@ -4087,34 +4116,6 @@ class C(Generic[T]): pass (Concatenate[int, P], int)) self.assertEqual(get_args(list | str), (list, str)) - def test_get_args_typevartuple(self): - Ts = TypeVarTuple('Ts') - class D(Generic[Unpack[Ts]]): pass - self.assertEqual(get_args(D[int]), (int,)) - self.assertEqual(get_args(D[T]), (T,)) - self.assertEqual(get_args(D[Unpack[Ts]]), (Unpack[Ts],)) - self.assertEqual(get_args(D[T, Unpack[Ts]]), (T, Unpack[Ts],)) - self.assertEqual(get_args(D[Unpack[Ts], T]), (Unpack[Ts], T)) - - self.assertEqual(get_args(tuple[Unpack[Ts]]), (Unpack[Ts],)) - self.assertEqual(get_args(Tuple[Unpack[Ts]]), (Unpack[Ts],)) - - self.assertEqual(get_args(tuple[Unpack[Ts], int]), (Unpack[Ts], int)) - self.assertEqual(get_args(Tuple[Unpack[Ts], int]), (Unpack[Ts], int)) - - self.assertEqual(get_args(tuple[int, Unpack[Ts]]), (int, Unpack[Ts])) - self.assertEqual(get_args(Tuple[int, Unpack[Ts]]), (int, Unpack[Ts])) - - self.assertEqual(get_args(tuple[int, Unpack[Ts], str]), - (int, Unpack[Ts], str)) - self.assertEqual(get_args(Tuple[int, Unpack[Ts], str]), - (int, Unpack[Ts], str)) - - self.assertEqual(get_args(tuple[Unpack[Ts], int, ...]), - (Unpack[Ts], int, ...)) - self.assertEqual(get_args(Tuple[Unpack[Ts]]), - (Unpack[Ts],)) - class CollectionsAbcTests(BaseTestCase): From 26040943c110b34994b772517fcff72963a96294 Mon Sep 17 00:00:00 2001 From: Matthew Rahtz Date: Sat, 5 Mar 2022 14:32:01 +0000 Subject: [PATCH 28/32] Remove accidental cosmetic change --- Lib/typing.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Lib/typing.py b/Lib/typing.py index fec76c9f3df84b..20028e8f4e3cf1 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -245,7 +245,6 @@ def _check_generic(cls, parameters, elen): raise TypeError(f"Too {'many' if alen > elen else 'few'} arguments for {cls};" f" actual {alen}, expected {elen}") - def _prepare_paramspec_params(cls, params): """Prepares the parameters for a Generic containing ParamSpec variables (internal helper). From 75cd9d04aa1409b6b9a447349d2dc34505cd75bc Mon Sep 17 00:00:00 2001 From: Matthew Rahtz Date: Sat, 5 Mar 2022 14:47:31 +0000 Subject: [PATCH 29/32] Add some type annotations --- Lib/typing.py | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/Lib/typing.py b/Lib/typing.py index 20028e8f4e3cf1..f8549535bb0f5c 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -790,7 +790,7 @@ def __repr__(self): return f'ForwardRef({self.__forward_arg__!r}{module_repr})' -def _is_unpacked_typevartuple(x): +def _is_unpacked_typevartuple(x: Any) -> bool: return ( isinstance(x, _UnpackGenericAlias) # If x is Unpack[tuple[...]], __parameters__ will be empty. @@ -799,7 +799,7 @@ def _is_unpacked_typevartuple(x): ) -def _is_typevar_like(x): +def _is_typevar_like(x: Any) -> bool: return isinstance(x, (TypeVar, ParamSpec)) or _is_unpacked_typevartuple(x) @@ -1115,7 +1115,7 @@ def __dir__(self): + [attr for attr in dir(self.__origin__) if not _is_dunder(attr)])) -def _is_unpacked_tuple(x): +def _is_unpacked_tuple(x: Any) -> bool: # Is `x` something like `*tuple[int]` or `*tuple[int, ...]`? if not isinstance(x, _UnpackGenericAlias): return False @@ -1128,7 +1128,7 @@ def _is_unpacked_tuple(x): return getattr(unpacked_type, '__origin__', None) is tuple -def _is_unpacked_arbitrary_length_tuple(x): +def _is_unpacked_arbitrary_length_tuple(x: Any) -> bool: if not _is_unpacked_tuple(x): return False unpacked_tuple = x.__args__[0] @@ -1153,13 +1153,26 @@ def _is_unpacked_arbitrary_length_tuple(x): return False -def _determine_typevar_substitution(typevars, args): +# Alias for readability in type signatures. +_TypeVarOrTypeVarTuple = TypeVar | TypeVarTuple + + +def _determine_typevar_substitution( + typevars: tuple[_TypeVarOrTypeVarTuple, ...], + args: tuple[type, ...] +) -> dict[_TypeVarOrTypeVarTuple, type]: """Determines how to assign type arguments to type variables. Args: typevars: A tuple of TypeVars and (at most one) TypeVarTuple. args: A tuple of type arguments to substitute into type variables. + Returns: + A dictionary mapping type variables to corresponding type arguments. + + Raises: + ValueError: A valid substitution cannot be found. + Examples: T1 = TypeVar('T1') T2 = TypeVar('T2') From 23b393bea7e3c317e15a66de4f08ef879984dbd7 Mon Sep 17 00:00:00 2001 From: Matthew Rahtz Date: Sat, 5 Mar 2022 15:20:40 +0000 Subject: [PATCH 30/32] Fix various things in Generic.__class_getitem__ --- Lib/test/test_typing.py | 5 +++++ Lib/typing.py | 41 ++++++++++++++++++++++------------------- 2 files changed, 27 insertions(+), 19 deletions(-) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 5b55b6608ad94b..cda2937a810bf1 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -2367,6 +2367,11 @@ class NewGeneric(Generic): ... class MyGeneric(Generic[T], Generic[S]): ... with self.assertRaises(TypeError): class MyGeneric(List[T], Generic[S]): ... + with self.assertRaises(TypeError): + Generic[()] + class C(Generic[T]): pass + with self.assertRaises(TypeError): + C[()] def test_init(self): T = TypeVar('T') diff --git a/Lib/typing.py b/Lib/typing.py index f8549535bb0f5c..d79b9c867a3c25 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -1805,28 +1805,31 @@ def lookup_name(mapping: Mapping[KT, VT], key: KT, default: VT) -> VT: @_tp_cache def __class_getitem__(cls, params): + """Parameterizes a generic class. + + At least, parameterizing a generic class is the *main* thing this method + does. For example, for some generic class `Foo`, this is called when we + do `Foo[int]` - there, with `cls=Foo` and `params=int`. + + However, note that this method is also called when defining generic + classes in the first place with `class Foo(Generic[T]): ...`. + """ if not isinstance(params, tuple): params = (params,) - try: - num_class_params = len(cls.__parameters__) - except AttributeError: - only_class_parameter_is_typevartuple = False - else: - if ( - num_class_params == 1 - and isinstance(cls.__parameters__[0], TypeVarTuple) - ): - only_class_parameter_is_typevartuple = True - else: - only_class_parameter_is_typevartuple = False - - if ( - not params - and not (cls is Tuple or only_class_parameter_is_typevartuple) - ): - raise TypeError( - f"Parameter list to {cls.__qualname__}[...] cannot be empty") + if not params: + # We're only ok with `params` being empty if the class's only type + # parameter is a `TypeVarTuple` (which can contain zero types). + class_params = getattr(cls, "__parameters__", None) + only_class_parameter_is_typevartuple = ( + class_params is not None + and len(class_params) == 1 + and isinstance(class_params[0], TypeVarTuple) + ) + if not only_class_parameter_is_typevartuple: + raise TypeError( + f"Parameter list to {cls.__qualname__}[...] cannot be empty" + ) params = tuple(_type_convert(p) for p in params) if cls in (Generic, Protocol): From f09490592544fe64f8b85e470f020c2232179513 Mon Sep 17 00:00:00 2001 From: Matthew Rahtz Date: Sat, 5 Mar 2022 15:51:07 +0000 Subject: [PATCH 31/32] Split up _determine_typevar_substitution --- Lib/typing.py | 182 +++++++++++++++++++++++++++++--------------------- 1 file changed, 106 insertions(+), 76 deletions(-) diff --git a/Lib/typing.py b/Lib/typing.py index d79b9c867a3c25..823ea4de2eee43 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -1157,10 +1157,112 @@ def _is_unpacked_arbitrary_length_tuple(x: Any) -> bool: _TypeVarOrTypeVarTuple = TypeVar | TypeVarTuple +def _replace_degenerate_unpacked_tuples( + args: tuple[type, ...] +) -> tuple[type, ...]: + """Replaces e.g. `*tuple[int]` with just `int` in `args`.""" + new_args = [] + for arg in args: + if (_is_unpacked_tuple(arg) + and not _is_unpacked_arbitrary_length_tuple(arg)): + arg_tuple = arg.__args__[0] # The actual tuple[int] + new_args.extend(arg_tuple.__args__) + else: + new_args.append(arg) + return tuple(new_args) + + +def _determine_typevar_substition_no_typevartuples( + typevars: tuple[TypeVar, ...], + args: tuple[type, ...] +) -> dict[TypeVar, type]: + if any(_is_unpacked_arbitrary_length_tuple(arg) for arg in args): + raise TypeError(f"Cannot assign type arguments '{args}' to type " + f"variables '{typevars}': cannot assign " + "unpacked arbitrary-length tuple to a TypeVar") + if len(typevars) != len(args): + raise TypeError(f"Number of type variables ({len(typevars)}) " + f"doesn't match number of type " + f"arguments ({len(args)})") + return dict(zip(typevars, args)) + + +def _count_num_items_before_and_after( + items: tuple[Any, ...], + item_of_interest: Any, +) -> tuple[int, int]: + item_of_interest_idxs = [i for i, x in enumerate(items) + if x == item_of_interest] + if not item_of_interest_idxs: + raise ValueError("Item of interest not found") + if len(item_of_interest_idxs) > 1: + raise ValueError("Item of interest occurs more than once") + [item_of_interest_idx] = item_of_interest_idxs + num_start = item_of_interest_idx + # Assuming len(items) == 3: + # * If item_of_interest_idx == 0, there are 2 items after item_of_interest. + # * If item_of_interest_idx == 2, there are 0 items after item_of_interest. + num_end = len(items) - item_of_interest_idx - 1 + return num_start, num_end + + +def _determine_typevar_substitution_single_typevartuple( + typevars: tuple[_TypeVarOrTypeVarTuple, ...], + args: tuple[type, ...] +) -> dict[_TypeVarOrTypeVarTuple, type | tuple[type, ...]]: + [typevartuple_idx] = [i for i, x in enumerate(typevars) + if isinstance(x, TypeVarTuple)] + typevartuple = typevars[typevartuple_idx] + num_start_typevars, num_end_typevars = _count_num_items_before_and_after( + typevars, typevartuple + ) + + # Even if one of the args is an unpacked arbitrary-length tuple, we still + # need at least as many args as normal TypeVars. + # Couldn't we split the arbitrary-length tuples across multiple TypeVars? + # No, because an arbitrary-length tuple could have length zero! + if len(args) < num_start_typevars + num_end_typevars: + raise TypeError( + "Expected at least {} type parameters, but only got {}".format( + num_start_typevars + num_end_typevars, len(args) + ) + ) + + if num_start_typevars == num_end_typevars == 0: + # It's just a single TypeVarTuple. + return {typevars[0]: args} + elif num_end_typevars == 0: + args_for_typevartuple = args[num_start_typevars:] + args_for_typevars = args[:num_start_typevars] + if any(_is_unpacked_arbitrary_length_tuple(arg) + for arg in args_for_typevars): + raise TypeError(f"Cannot assign type arguments '{args}' to type " + f"variables '{typevars}': cannot assign " + "arbitrary-length tuple to a TypeVar") + return { + **dict(zip(typevars[:num_start_typevars], args_for_typevars)), + typevartuple: args_for_typevartuple, + } + else: + args_for_left_typevars = args[:num_start_typevars] + args_for_typevartuple = args[num_start_typevars:-num_end_typevars] + args_for_right_typevars = args[-num_end_typevars:] + if any(_is_unpacked_arbitrary_length_tuple(arg) + for arg in args_for_left_typevars + args_for_right_typevars): + raise TypeError(f"Cannot assign type arguments '{args}' to type " + f"variables '{typevars}': cannot assign " + "arbitrary-length tuple to a TypeVar") + return { + **dict(zip(typevars[:num_start_typevars], args_for_left_typevars)), + typevartuple: args_for_typevartuple, + **dict(zip(typevars[-num_end_typevars:], args_for_right_typevars)), + } + + def _determine_typevar_substitution( typevars: tuple[_TypeVarOrTypeVarTuple, ...], args: tuple[type, ...] -) -> dict[_TypeVarOrTypeVarTuple, type]: +) -> dict[_TypeVarOrTypeVarTuple, type | tuple[type, ...]]: """Determines how to assign type arguments to type variables. Args: @@ -1256,83 +1358,11 @@ def _determine_typevar_substitution( raise TypeError("At most 1 TypeVarTuple may be used in a type " f"parameter list, but saw {num_typevartuples}") - # First, we should go through `args` to replace e.g. *tuple[int] with int. - new_args = [] - for arg in args: - if (_is_unpacked_tuple(arg) - and not _is_unpacked_arbitrary_length_tuple(arg)): - arg_tuple = arg.__args__[0] # The actual tuple[int] - new_args.extend(arg_tuple.__args__) - else: - new_args.append(arg) - args = tuple(new_args) - - # Case 1: typevars does not contain any TypeVarTuples + args = _replace_degenerate_unpacked_tuples(args) if num_typevartuples == 0: - if any(_is_unpacked_arbitrary_length_tuple(arg) for arg in args): - raise TypeError(f"Cannot assign type arguments '{args}' to type " - f"variables '{typevars}': cannot assign " - "unpacked arbitrary-length tuple to a TypeVar") - if len(typevars) != len(args): - raise TypeError(f"Number of type variables ({len(typevars)}) " - f"doesn't match number of type " - f"arguments ({len(args)})") - return dict(zip(typevars, args)) - - # Case 2: typevars contains a single TypeVarTuple - - [typevartuple_idx] = [i for i, x in enumerate(typevars) - if isinstance(x, TypeVarTuple)] - typevartuple = typevars[typevartuple_idx] - num_start_typevars = typevartuple_idx - # Assuming len(typevars) == 3: - # * If typevartuple_idx == 0, there are 2 TypeVars at - # the end of typevars. - # * If typevartuple_idx == 2 there are 0 TypeVars at - # the end of typevars. - num_end_typevars = len(typevars) - typevartuple_idx - 1 - - # Even if one of the args is an unpacked arbitrary-length tuple, we still - # need at least as many args as normal TypeVars. - # Couldn't we split the arbitrary-length tuples across multiple TypeVars? - # No, because an arbitrary-length tuple could have length zero! - if len(args) < num_start_typevars + num_end_typevars: - raise TypeError( - "Expected at least {} type parameters, but only got {}".format( - num_start_typevars + num_end_typevars, len(args) - ) - ) - - if num_start_typevars == num_end_typevars == 0: - # It's just a single TypeVarTuple. - return {typevars[0]: args} - elif num_end_typevars == 0: - args_for_typevartuple = args[num_start_typevars:] - args_for_typevars = args[:num_start_typevars] - if any(_is_unpacked_arbitrary_length_tuple(arg) - for arg in args_for_typevars): - raise TypeError(f"Cannot assign type arguments '{args}' to type " - f"variables '{typevars}': cannot assign " - "arbitrary-length tuple to a TypeVar") - return { - **dict(zip(typevars[:num_start_typevars], args_for_typevars)), - typevartuple: args_for_typevartuple, - } - else: - args_for_left_typevars = args[:num_start_typevars] - args_for_typevartuple = args[num_start_typevars:-num_end_typevars] - args_for_right_typevars = args[-num_end_typevars:] - if any(_is_unpacked_arbitrary_length_tuple(arg) - for arg in args_for_left_typevars + args_for_right_typevars): - raise TypeError(f"Cannot assign type arguments '{args}' to type " - f"variables '{typevars}': cannot assign " - "arbitrary-length tuple to a TypeVar") - return { - **dict(zip(typevars[:num_start_typevars], args_for_left_typevars)), - typevartuple: args_for_typevartuple, - **dict(zip(typevars[-num_end_typevars:], args_for_right_typevars)), - } + return _determine_typevar_substition_no_typevartuples(typevars, args) + return _determine_typevar_substitution_single_typevartuple(typevars, args) # Special typing constructs Union, Optional, Generic, Callable and Tuple From b9b1c80913f1fbc21ea60257bed6562e18c49802 Mon Sep 17 00:00:00 2001 From: Matthew Rahtz Date: Sat, 5 Mar 2022 16:00:59 +0000 Subject: [PATCH 32/32] Cut out variadic type substitution logic --- Lib/test/test_typing.py | 178 +++++--------------------------- Lib/typing.py | 220 +--------------------------------------- 2 files changed, 32 insertions(+), 366 deletions(-) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index cda2937a810bf1..81a42badf57825 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -11,7 +11,6 @@ from typing import Any, NoReturn, Never, assert_never from typing import TypeVar, TypeVarTuple, Unpack, AnyStr -from typing import _determine_typevar_substitution from typing import T, KT, VT # Not in __all__. from typing import Union, Optional, Literal from typing import Tuple, List, Dict, MutableMapping @@ -479,45 +478,48 @@ class A(Generic[Unpack[Ts]]): pass B = A[Unpack[Ts]] self.assertTrue(repr(B).endswith('A[*Ts]')) - self.assertTrue(repr(B[()]).endswith('A[()]')) - self.assertTrue(repr(B[float]).endswith('A[float]')) - self.assertTrue(repr(B[float, str]).endswith('A[float, str]')) + with self.assertRaises(NotImplementedError): + B[()] + with self.assertRaises(NotImplementedError): + B[float] + with self.assertRaises(NotImplementedError): + B[float, str] C = A[Unpack[Ts], int] self.assertTrue(repr(C).endswith('A[*Ts, int]')) - self.assertTrue(repr(C[()]).endswith('A[int]')) - self.assertTrue(repr(C[float]).endswith('A[float, int]')) - self.assertTrue(repr(C[float, str]).endswith('A[float, str, int]')) + with self.assertRaises(NotImplementedError): + C[()] + with self.assertRaises(NotImplementedError): + C[float] + with self.assertRaises(NotImplementedError): + C[float, str] D = A[int, Unpack[Ts]] self.assertTrue(repr(D).endswith('A[int, *Ts]')) - self.assertTrue(repr(D[()]).endswith('A[int]')) - self.assertTrue(repr(D[float]).endswith('A[int, float]')) - self.assertTrue(repr(D[float, str]).endswith('A[int, float, str]')) + with self.assertRaises(NotImplementedError): + D[()] + with self.assertRaises(NotImplementedError): + D[float] + with self.assertRaises(NotImplementedError): + D[float, str] E = A[int, Unpack[Ts], str] self.assertTrue(repr(E).endswith('A[int, *Ts, str]')) - self.assertTrue(repr(E[()]).endswith('A[int, str]')) - self.assertTrue(repr(E[float]).endswith('A[int, float, str]')) - self.assertTrue(repr(E[float, bool]).endswith('A[int, float, bool, str]')) + with self.assertRaises(NotImplementedError): + E[()] + with self.assertRaises(NotImplementedError): + E[float] + with self.assertRaises(NotImplementedError): + E[float, bool] F = A[Unpack[Ts], Unpack[tuple[str, ...]]] self.assertTrue(repr(F).endswith('A[*Ts, *tuple[str, ...]]')) - self.assertTrue(repr( + with self.assertRaises(NotImplementedError): F[()] - ).endswith( - 'A[*tuple[str, ...]]') - ) - self.assertTrue(repr( + with self.assertRaises(NotImplementedError): F[float] - ).endswith( - 'A[float, *tuple[str, ...]]' - )) - self.assertTrue(repr( + with self.assertRaises(NotImplementedError): F[float, int] - ).endswith( - 'A[float, int, *tuple[str, ...]]' - )) def test_cannot_subclass_class(self): with self.assertRaises(TypeError): @@ -791,132 +793,6 @@ class C(Generic[Unpack[Ts]]): pass Ts2 = TypeVarTuple('Ts2') self.assertNotEqual(C[Unpack[Ts1]], C[Unpack[Ts2]]) - def test_typevar_substitution(self): - T1 = TypeVar('T1') - T2 = TypeVar('T2') - Ts = TypeVarTuple('Ts') - - # Cases which should generate a TypeError. - # These are tuples of (typevars, args) arguments to - # _determine_typevar_substitution.. - test_cases = [ - # Too few args - - # One TypeVar: if (potentially) 0 args - ((T1,), ()), - ((T1,), (Unpack[tuple[()]],)), - ((T1,), (Unpack[tuple[int, ...]],)), - ((T1,), (Unpack[tuple[int, ...]], Unpack[tuple[int, ...]])), - # Two TypeVars: if (potentially) <= 1 args - ((T1, T2), (int,)), - ((T1, T2), (Unpack[tuple[int]],)), - ((T1, T2), (Unpack[tuple[int, ...]],)), - ((T1, T2), (Unpack[tuple[int, ...]], Unpack[tuple[int, ...]])), - ((T1, T2), (Unpack[tuple[int, ...]], Unpack[tuple[int, ...]], Unpack[tuple[int, ...]])), - # One TypeVarTuple and one TypeVar: if (potentially) 0 args - # TypeVarTuple first - ((Ts, T1), ()), - ((Ts, T1), (Unpack[tuple[()]],)), - ((Ts, T1), (Unpack[tuple[int, ...]],)), - ((Ts, T1), (Unpack[tuple[int, ...]], Unpack[tuple[int, ...]])), - ((Ts, T1), (Unpack[tuple[int, ...]], Unpack[tuple[int, ...]], Unpack[tuple[int, ...]])), - # TypeVarTuple last - ((T1, Ts), ()), - ((T1, Ts), (Unpack[tuple[()]],)), - ((T1, Ts), (Unpack[tuple[int, ...]],)), - ((T1, Ts), (Unpack[tuple[int, ...]], Unpack[tuple[int, ...]])), - ((T1, Ts), (Unpack[tuple[int, ...]], Unpack[tuple[int, ...]], Unpack[tuple[int, ...]])), - # OneTypeVarTuple and two TypeVars: if (potentially) <= 1 args - # TypeVarTuple first - ((Ts, T1, T2), ()), - ((Ts, T1, T2), (int,)), - ((Ts, T1, T2), (Unpack[tuple[int]],)), - ((Ts, T1, T2), (Unpack[tuple[int, ...]],)), - ((Ts, T1, T2), (Unpack[tuple[int, ...]], Unpack[tuple[int, ...]])), - ((Ts, T1, T2), (Unpack[tuple[int, ...]], Unpack[tuple[int, ...]], Unpack[tuple[int, ...]])), - ((Ts, T1, T2), (Unpack[tuple[int, ...]], Unpack[tuple[int, ...]], Unpack[tuple[int, ...]], Unpack[tuple[int, ...]])), - # TypeVarTuple in middle - ((T1, Ts, T2), ()), - ((T1, Ts, T2), (int,)), - ((T1, Ts, T2), (Unpack[tuple[int]],)), - ((T1, Ts, T2), (Unpack[tuple[int, ...]],)), - ((T1, Ts, T2), (Unpack[tuple[int, ...]], Unpack[tuple[int, ...]])), - ((T1, Ts, T2), (Unpack[tuple[int, ...]], Unpack[tuple[int, ...]], Unpack[tuple[int, ...]])), - ((T1, Ts, T2), (Unpack[tuple[int, ...]], Unpack[tuple[int, ...]], Unpack[tuple[int, ...]], Unpack[tuple[int, ...]])), - # TypeVarTuple last - ((T1, T2, Ts), ()), - ((T1, T2, Ts), (int,)), - ((T1, T2, Ts), (Unpack[tuple[int]],)), - ((T1, T2, Ts), (Unpack[tuple[int, ...]],)), - ((T1, T2, Ts), (Unpack[tuple[int, ...]], Unpack[tuple[int, ...]])), - ((T1, T2, Ts), (Unpack[tuple[int, ...]], Unpack[tuple[int, ...]], Unpack[tuple[int, ...]])), - ((T1, T2, Ts), (Unpack[tuple[int, ...]], Unpack[tuple[int, ...]], Unpack[tuple[int, ...]], Unpack[tuple[int, ...]])), - - # Too many args - - # One TypeVar: if (potentially) >= 2 args - ((T1,), (int, int)), - ((T1,), (Unpack[tuple[int, int]],)), - ((T1,), (Unpack[tuple[int]], Unpack[tuple[int]])), - ((T1,), (int, Unpack[tuple[int, ...]])), - # Two TypeVars: if (potentially) >= 3 args - ((T1, T2), (int, int, int)), - ((T1, T2), (Unpack[tuple[int, int, int]],)), - ((T1, T2), (Unpack[tuple[int]], Unpack[tuple[int]], Unpack[tuple[int]])), - ((T1, T2), (int, int, Unpack[tuple[int, ...]],)), - - # Too many TypeVarTuples - - ((Ts, Ts), ()), - ((Ts, Ts), (int,)), - ((Ts, Ts), (int, str)), - ] - for typevars, args in test_cases: - with self.subTest(f'typevars={typevars}, args={args}'): - with self.assertRaises(TypeError): - _determine_typevar_substitution(typevars, args) - - # Cases which should succeed. - # These are tuples of (typevars, args, expected_result). - test_cases = [ - # Correct number of args, TypeVars only - ((T1,), (int,), {T1: int}), - ((T1,), (Unpack[tuple[int]],), {T1: int}), - ((T1, T2), (int, str), {T1: int, T2: str}), - ((T1, T2), (Unpack[tuple[int, str]],), {T1: int, T2: str}), - # Correct number of args, TypeVarTuple only - ((Ts,), (), {Ts: ()}), - ((Ts,), (int,), {Ts: (int,)}), - ((Ts,), (Unpack[tuple[int]],), {Ts: (int,)}), - ((Ts,), (int, str), {Ts: (int, str)}), - ((Ts,), (Unpack[tuple[int, ...]],), {Ts: (Unpack[tuple[int, ...]],)}), - # Correct number of args, TypeVarTuple at the beginning - ((Ts, T1), (int,), {Ts: (), T1: int}), - ((Ts, T1), (int, str), {Ts: (int,), T1: str}), - ((Ts, T1), (int, str, float), {Ts: (int, str), T1: float}), - ((Ts, T1), (Unpack[tuple[int, ...]], str), {Ts: (Unpack[tuple[int, ...]],), T1: str}), - ((Ts, T1), (Unpack[tuple[int, ...]], str, bool), {Ts: (Unpack[tuple[int, ...]], str), T1: bool}), - # Correct number of args, TypeVarTuple at the end - ((T1, Ts), (int,), {T1: int, Ts: ()}), - ((T1, Ts), (int, str), {T1: int, Ts: (str,)}), - ((T1, Ts), (int, str, float), {T1: int, Ts: (str, float)}), - ((T1, Ts), (int, Unpack[tuple[str, ...]]), {T1: int, Ts: (Unpack[tuple[str, ...]],)}), - ((T1, Ts), (int, str, Unpack[tuple[float, ...]]), {T1: int, Ts: (str, Unpack[tuple[float, ...]],)}), - # Correct number of args, TypeVarTuple in the middle - ((T1, Ts, T2), (int, str), {T1: int, Ts: (), T2: str}), - ((T1, Ts, T2), (int, float, str), {T1: int, Ts: (float,), T2: str}), - ((T1, Ts, T2), (int, Unpack[tuple[int, ...]], str), {T1: int, Ts: (Unpack[tuple[int, ...]],), T2: str}), - ((T1, Ts, T2), (int, float, Unpack[tuple[bool, ...]], str), {T1: int, Ts: (float, Unpack[tuple[bool, ...]],), T2: str}), - ((T1, Ts, T2), (int, Unpack[tuple[bool, ...]], float, str), {T1: int, Ts: (Unpack[tuple[bool, ...]], float), T2: str}), - ((T1, Ts, T2), (int, complex, Unpack[tuple[bool, ...]], float, str), {T1: int, Ts: (complex, Unpack[tuple[bool, ...]], float), T2: str}), - ] - for typevars, args, result_or_exception in test_cases: - with self.subTest(f'typevars={typevars}, args={args}'): - self.assertEqual( - _determine_typevar_substitution(typevars, args), - result_or_exception - ) - class UnionTests(BaseTestCase): diff --git a/Lib/typing.py b/Lib/typing.py index 823ea4de2eee43..59e2c672a8372e 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -1153,218 +1153,6 @@ def _is_unpacked_arbitrary_length_tuple(x: Any) -> bool: return False -# Alias for readability in type signatures. -_TypeVarOrTypeVarTuple = TypeVar | TypeVarTuple - - -def _replace_degenerate_unpacked_tuples( - args: tuple[type, ...] -) -> tuple[type, ...]: - """Replaces e.g. `*tuple[int]` with just `int` in `args`.""" - new_args = [] - for arg in args: - if (_is_unpacked_tuple(arg) - and not _is_unpacked_arbitrary_length_tuple(arg)): - arg_tuple = arg.__args__[0] # The actual tuple[int] - new_args.extend(arg_tuple.__args__) - else: - new_args.append(arg) - return tuple(new_args) - - -def _determine_typevar_substition_no_typevartuples( - typevars: tuple[TypeVar, ...], - args: tuple[type, ...] -) -> dict[TypeVar, type]: - if any(_is_unpacked_arbitrary_length_tuple(arg) for arg in args): - raise TypeError(f"Cannot assign type arguments '{args}' to type " - f"variables '{typevars}': cannot assign " - "unpacked arbitrary-length tuple to a TypeVar") - if len(typevars) != len(args): - raise TypeError(f"Number of type variables ({len(typevars)}) " - f"doesn't match number of type " - f"arguments ({len(args)})") - return dict(zip(typevars, args)) - - -def _count_num_items_before_and_after( - items: tuple[Any, ...], - item_of_interest: Any, -) -> tuple[int, int]: - item_of_interest_idxs = [i for i, x in enumerate(items) - if x == item_of_interest] - if not item_of_interest_idxs: - raise ValueError("Item of interest not found") - if len(item_of_interest_idxs) > 1: - raise ValueError("Item of interest occurs more than once") - [item_of_interest_idx] = item_of_interest_idxs - num_start = item_of_interest_idx - # Assuming len(items) == 3: - # * If item_of_interest_idx == 0, there are 2 items after item_of_interest. - # * If item_of_interest_idx == 2, there are 0 items after item_of_interest. - num_end = len(items) - item_of_interest_idx - 1 - return num_start, num_end - - -def _determine_typevar_substitution_single_typevartuple( - typevars: tuple[_TypeVarOrTypeVarTuple, ...], - args: tuple[type, ...] -) -> dict[_TypeVarOrTypeVarTuple, type | tuple[type, ...]]: - [typevartuple_idx] = [i for i, x in enumerate(typevars) - if isinstance(x, TypeVarTuple)] - typevartuple = typevars[typevartuple_idx] - num_start_typevars, num_end_typevars = _count_num_items_before_and_after( - typevars, typevartuple - ) - - # Even if one of the args is an unpacked arbitrary-length tuple, we still - # need at least as many args as normal TypeVars. - # Couldn't we split the arbitrary-length tuples across multiple TypeVars? - # No, because an arbitrary-length tuple could have length zero! - if len(args) < num_start_typevars + num_end_typevars: - raise TypeError( - "Expected at least {} type parameters, but only got {}".format( - num_start_typevars + num_end_typevars, len(args) - ) - ) - - if num_start_typevars == num_end_typevars == 0: - # It's just a single TypeVarTuple. - return {typevars[0]: args} - elif num_end_typevars == 0: - args_for_typevartuple = args[num_start_typevars:] - args_for_typevars = args[:num_start_typevars] - if any(_is_unpacked_arbitrary_length_tuple(arg) - for arg in args_for_typevars): - raise TypeError(f"Cannot assign type arguments '{args}' to type " - f"variables '{typevars}': cannot assign " - "arbitrary-length tuple to a TypeVar") - return { - **dict(zip(typevars[:num_start_typevars], args_for_typevars)), - typevartuple: args_for_typevartuple, - } - else: - args_for_left_typevars = args[:num_start_typevars] - args_for_typevartuple = args[num_start_typevars:-num_end_typevars] - args_for_right_typevars = args[-num_end_typevars:] - if any(_is_unpacked_arbitrary_length_tuple(arg) - for arg in args_for_left_typevars + args_for_right_typevars): - raise TypeError(f"Cannot assign type arguments '{args}' to type " - f"variables '{typevars}': cannot assign " - "arbitrary-length tuple to a TypeVar") - return { - **dict(zip(typevars[:num_start_typevars], args_for_left_typevars)), - typevartuple: args_for_typevartuple, - **dict(zip(typevars[-num_end_typevars:], args_for_right_typevars)), - } - - -def _determine_typevar_substitution( - typevars: tuple[_TypeVarOrTypeVarTuple, ...], - args: tuple[type, ...] -) -> dict[_TypeVarOrTypeVarTuple, type | tuple[type, ...]]: - """Determines how to assign type arguments to type variables. - - Args: - typevars: A tuple of TypeVars and (at most one) TypeVarTuple. - args: A tuple of type arguments to substitute into type variables. - - Returns: - A dictionary mapping type variables to corresponding type arguments. - - Raises: - ValueError: A valid substitution cannot be found. - - Examples: - T1 = TypeVar('T1') - T2 = TypeVar('T2') - Ts = TypeVarTuple('Ts') - - # ==== A single TypeVar ==== - - # == Too few args == - - typevars=(T1,), args=() => TypeError - typevars=(T1,), args=(*tuple[()],) => TypeError - # tuple[int, ...] means ">= 0 ints", but we require "== 1" - typevars=(T1,), args=(*tuple[int, ...],) => TypeError - - # == Right number of args == - - typevars=(T1,), args=(int,) => {T1: int} - typevars=(T1,), args=(*tuple[int],) => {T1: int} - - # == Too many args == - - typevars=(T1,), args=(int, str) => TypeError - typevars=(T1,), args=(*tuple[int, str],) => TypeError - # We could have two ints, so this might be too many. - typevars=(T1,), args=(*tuple[int, ...],) => TypeError - - # ===== Two TypeVars ===== - - typevars=(T1, T2), args=(int, str) => {T1: int, T2: str} - typevars=(T1, T2), args=(*tuple[int, str],) => {T1: int, T2: str} - - # ===== A single TypeVarTuple ===== - - typevars=(Ts,), args=() => {Ts: ()} - typevars=(Ts,), args=(int,) => {Ts: (int,)} - typevars=(Ts,), args=(int, str) => {Ts: (int, str)} - typevars=(Ts,), args=(*tuple[()],) => {Ts: ()} - typevars=(Ts,), args=(*tuple[int],) => {Ts: (int,)} - typevars=(Ts,), args=(*tuple[int, str],) => {Ts: (int, str)} - typevars=(Ts,), args=(*tuple[int, ...],) => {Ts: (*tuple[int, ...])} - - # ===== A single TypeVar and a single TypeVarTuple ===== - - # == Too few args == - - typevars=(T, Ts), args=() => TypeError - typevars=(T, Ts), args=(*tuple[()],) => TypeError - # Again, this means ">= 0 ints", but we need ">= 1". - typevars=(T, Ts), args=(*tuple[int, ...]) => TypeError - - # == Right number of args == - - typevars=(T, Ts), args=(int,) => {T: int, Ts: ()} - typevars=(T, Ts) args=(int, str) => {T, int, Ts: (str,)} - typevars=(T, Ts), args=(*tuple[int]) => {T: int, Ts: ()} - typevars=(T, Ts), args=(*tuple[int, str]) => {T: int, Ts: (str,)} - typevars=(T, Ts), args=(int, *tuple[str, ...]) - => {T: int, Ts: (*tuple[str, ...],)} - typevars=(T, Ts), args=(*tuple[int], *tuple[str, ...]) - => {T: int, Ts: (*tuple[str, ...],)} - """ - - # This would be a pretty complicated algorithm in its full generality. - # Fortunately, we can make two observations, which simplify things: - # 1. As of PEP 646, there can only be a maximum of one TypeVarTuple. - # 2. When considering unpacked arbitrary-length tuples - # like *tuple[int, ...], we can't assign them or any part of them to - # regular TypeVars: - # * We can't assign the whole *tuple[int, ...] to a single TypeVar - # because...well, it represents an arbitrary number of types, and a - # TypeVar only holds exactly one type. - # * We can't assign one of the `int`s to a TypeVar, because we can't - # be sure there'll be any `int`s at all: tuple[int, ...] is a tuple - # of *zero* or more ints. - - if not typevars: - return {} - - num_typevartuples = sum(1 for x in typevars if isinstance(x, TypeVarTuple)) - if num_typevartuples > 1: - raise TypeError("At most 1 TypeVarTuple may be used in a type " - f"parameter list, but saw {num_typevartuples}") - - args = _replace_degenerate_unpacked_tuples(args) - - if num_typevartuples == 0: - return _determine_typevar_substition_no_typevartuples(typevars, args) - return _determine_typevar_substitution_single_typevartuple(typevars, args) - - # Special typing constructs Union, Optional, Generic, Callable and Tuple # use three special attributes for internal bookkeeping of generic types: # * __parameters__ is a tuple of unique free type parameters of a generic @@ -1485,10 +1273,12 @@ def _determine_new_args(self, args): # anything more exotic than a plain `TypeVar`, we need to consider # edge cases. + if any(isinstance(p, TypeVarTuple) for p in self.__parameters__): + raise NotImplementedError( + "Type substitution for TypeVarTuples is not yet implemented" + ) # In the example above, this would be {T3: str} - new_arg_by_param = _determine_typevar_substitution( - self.__parameters__, args, - ) + new_arg_by_param = dict(zip(self.__parameters__, args)) new_args = [] for old_arg in self.__args__: