From b9b1c80913f1fbc21ea60257bed6562e18c49802 Mon Sep 17 00:00:00 2001 From: Matthew Rahtz Date: Sat, 5 Mar 2022 16:00:59 +0000 Subject: [PATCH] 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__: