[Python-checkins] gh-91162: Fix substitution of unpacked tuples in generic aliases (GH-92335)

serhiy-storchaka webhook-mailer at python.org
Sun May 8 11:32:37 EDT 2022


https://github.com/python/cpython/commit/9d25db9db1617f012d7dba118b5b8f2b9e25e116
commit: 9d25db9db1617f012d7dba118b5b8f2b9e25e116
branch: main
author: Serhiy Storchaka <storchaka at gmail.com>
committer: serhiy-storchaka <storchaka at gmail.com>
date: 2022-05-08T18:32:32+03:00
summary:

gh-91162: Fix substitution of unpacked tuples in generic aliases (GH-92335)

files:
M Include/internal/pycore_global_strings.h
M Include/internal/pycore_runtime_init.h
M Lib/test/test_typing.py
M Lib/typing.py
M Objects/genericaliasobject.c

diff --git a/Include/internal/pycore_global_strings.h b/Include/internal/pycore_global_strings.h
index 4e1f2ec6c3529..cfa8ae99d1b6d 100644
--- a/Include/internal/pycore_global_strings.h
+++ b/Include/internal/pycore_global_strings.h
@@ -201,8 +201,9 @@ struct _Py_global_strings {
         STRUCT_FOR_ID(__subclasshook__)
         STRUCT_FOR_ID(__truediv__)
         STRUCT_FOR_ID(__trunc__)
+        STRUCT_FOR_ID(__typing_is_unpacked_typevartuple__)
         STRUCT_FOR_ID(__typing_subst__)
-        STRUCT_FOR_ID(__typing_unpacked__)
+        STRUCT_FOR_ID(__typing_unpacked_tuple_args__)
         STRUCT_FOR_ID(__warningregistry__)
         STRUCT_FOR_ID(__weakref__)
         STRUCT_FOR_ID(__xor__)
diff --git a/Include/internal/pycore_runtime_init.h b/Include/internal/pycore_runtime_init.h
index c1c5fd562e6b8..57cacb97bcf1a 100644
--- a/Include/internal/pycore_runtime_init.h
+++ b/Include/internal/pycore_runtime_init.h
@@ -824,8 +824,9 @@ extern "C" {
                 INIT_ID(__subclasshook__), \
                 INIT_ID(__truediv__), \
                 INIT_ID(__trunc__), \
+                INIT_ID(__typing_is_unpacked_typevartuple__), \
                 INIT_ID(__typing_subst__), \
-                INIT_ID(__typing_unpacked__), \
+                INIT_ID(__typing_unpacked_tuple_args__), \
                 INIT_ID(__warningregistry__), \
                 INIT_ID(__weakref__), \
                 INIT_ID(__xor__), \
diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py
index 6e1e8d6e1b473..2afac23539155 100644
--- a/Lib/test/test_typing.py
+++ b/Lib/test/test_typing.py
@@ -603,22 +603,10 @@ class C(Generic[T]): pass
             ('generic[T]',                        '[int]',                   'generic[int]'),
             ('generic[T]',                        '[int, str]',              'TypeError'),
             ('generic[T]',                        '[tuple_type[int, ...]]',  'generic[tuple_type[int, ...]]'),
-            # Should raise TypeError: a) according to the tentative spec,
-            # unpacked types cannot be used as arguments to aliases that expect
-            # a fixed number of arguments; b) it's equivalent to generic[()].
-            ('generic[T]',                        '[*tuple[()]]',            'generic[*tuple[()]]'),
-            ('generic[T]',                        '[*Tuple[()]]',            'TypeError'),
-            # Should raise TypeError according to the tentative spec: unpacked
-            # types cannot be used as arguments to aliases that expect a fixed
-            # number of arguments.
-            ('generic[T]',                        '[*tuple[int]]',           'generic[*tuple[int]]'),
-            ('generic[T]',                        '[*Tuple[int]]',           'TypeError'),
-            # Ditto.
-            ('generic[T]',                        '[*tuple[int, str]]',      'generic[*tuple[int, str]]'),
-            ('generic[T]',                        '[*Tuple[int, str]]',      'TypeError'),
-            # Ditto.
-            ('generic[T]',                        '[*tuple[int, ...]]',      'generic[*tuple[int, ...]]'),
-            ('generic[T]',                        '[*Tuple[int, ...]]',      'TypeError'),
+            ('generic[T]',                        '[*tuple_type[int]]',      'generic[int]'),
+            ('generic[T]',                        '[*tuple_type[()]]',       'TypeError'),
+            ('generic[T]',                        '[*tuple_type[int, str]]', 'TypeError'),
+            ('generic[T]',                        '[*tuple_type[int, ...]]', 'TypeError'),
             ('generic[T]',                        '[*Ts]',                   'TypeError'),
             ('generic[T]',                        '[T, *Ts]',                'TypeError'),
             ('generic[T]',                        '[*Ts, T]',                'TypeError'),
@@ -664,23 +652,29 @@ class C(Generic[T1, T2]): pass
             ('generic[T1, T2]',                        '[int, str]',                                        'generic[int, str]'),
             ('generic[T1, T2]',                        '[int, str, bool]',                                  'TypeError'),
             ('generic[T1, T2]',                        '[*tuple_type[int]]',                                'TypeError'),
-            ('generic[T1, T2]',                        '[*tuple_type[int, str]]',                           'TypeError'),
+            ('generic[T1, T2]',                        '[*tuple_type[int, str]]',                           'generic[int, str]'),
             ('generic[T1, T2]',                        '[*tuple_type[int, str, bool]]',                     'TypeError'),
 
-            # Should raise TypeError according to the tentative spec: unpacked
-            # types cannot be used as arguments to aliases that expect a fixed
-            # number of arguments.
-            ('generic[T1, T2]',                        '[*tuple[int, str], *tuple[float, bool]]',           'generic[*tuple[int, str], *tuple[float, bool]]'),
-            ('generic[T1, T2]',                        '[*Tuple[int, str], *Tuple[float, bool]]',           'TypeError'),
+            ('generic[T1, T2]',                        '[int, *tuple_type[str]]',                           'generic[int, str]'),
+            ('generic[T1, T2]',                        '[*tuple_type[int], str]',                           'generic[int, str]'),
+            ('generic[T1, T2]',                        '[*tuple_type[int], *tuple_type[str]]',              'generic[int, str]'),
+            ('generic[T1, T2]',                        '[*tuple_type[int, str], *tuple_type[()]]',          'generic[int, str]'),
+            ('generic[T1, T2]',                        '[*tuple_type[()], *tuple_type[int, str]]',          'generic[int, str]'),
+            ('generic[T1, T2]',                        '[*tuple_type[int], *tuple_type[()]]',               'TypeError'),
+            ('generic[T1, T2]',                        '[*tuple_type[()], *tuple_type[int]]',               'TypeError'),
+            ('generic[T1, T2]',                        '[*tuple_type[int, str], *tuple_type[float]]',       'TypeError'),
+            ('generic[T1, T2]',                        '[*tuple_type[int], *tuple_type[str, float]]',       'TypeError'),
+            ('generic[T1, T2]',                        '[*tuple_type[int, str], *tuple_type[float, bool]]', 'TypeError'),
 
             ('generic[T1, T2]',                        '[tuple_type[int, ...]]',                            'TypeError'),
             ('generic[T1, T2]',                        '[tuple_type[int, ...], tuple_type[str, ...]]',      'generic[tuple_type[int, ...], tuple_type[str, ...]]'),
+            # Should raise TypeError according to the tentative spec: unpacked
+            # types cannot be used as arguments to aliases that expect a fixed
+            # number of arguments.
             ('generic[T1, T2]',                        '[*tuple_type[int, ...]]',                           'TypeError'),
-
-            # Ditto.
-            ('generic[T1, T2]',                        '[*tuple[int, ...], *tuple[str, ...]]',              'generic[*tuple[int, ...], *tuple[str, ...]]'),
-            ('generic[T1, T2]',                        '[*Tuple[int, ...], *Tuple[str, ...]]',              'TypeError'),
-
+            ('generic[T1, T2]',                        '[int, *tuple_type[str, ...]]',                      'TypeError'),
+            ('generic[T1, T2]',                        '[*tuple_type[int, ...], str]',                      'TypeError'),
+            ('generic[T1, T2]',                        '[*tuple_type[int, ...], *tuple_type[str, ...]]',    'TypeError'),
             ('generic[T1, T2]',                        '[*Ts]',                                             'TypeError'),
             ('generic[T1, T2]',                        '[T, *Ts]',                                          'TypeError'),
             ('generic[T1, T2]',                        '[*Ts, T]',                                          'TypeError'),
@@ -720,7 +714,7 @@ class C(Generic[T1, T2, T3]): pass
         tests = [
             # Alias                                    # Args                                               # Expected result
             ('generic[T1, bool, T2]',                  '[int, str]',                                        'generic[int, bool, str]'),
-            ('generic[T1, bool, T2]',                  '[*tuple_type[int, str]]',                           'TypeError'),
+            ('generic[T1, bool, T2]',                  '[*tuple_type[int, str]]',                           'generic[int, bool, str]'),
         ]
 
         for alias_template, args_template, expected_template in tests:
@@ -753,96 +747,43 @@ class C(Generic[*Ts]): pass
         # Tuple because tuple currently behaves differently.
         tests = [
             # Alias                                    # Args                                            # Expected result
-            ('C[*Ts]',                                 '[()]',                                           'C[()]'),
-            ('tuple[*Ts]',                             '[()]',                                           'tuple[()]'),
-            ('Tuple[*Ts]',                             '[()]',                                           'Tuple[()]'),
-
-            ('C[*Ts]',                                 '[int]',                                          'C[int]'),
-            ('tuple[*Ts]',                             '[int]',                                          'tuple[int]'),
-            ('Tuple[*Ts]',                             '[int]',                                          'Tuple[int]'),
-
-            ('C[*Ts]',                                 '[int, str]',                                     'C[int, str]'),
-            ('tuple[*Ts]',                             '[int, str]',                                     'tuple[int, str]'),
-            ('Tuple[*Ts]',                             '[int, str]',                                     'Tuple[int, str]'),
-
-            ('C[*Ts]',                                 '[*tuple_type[int]]',                             'C[*tuple_type[int]]'),  # Should be C[int]
-            ('tuple[*Ts]',                             '[*tuple_type[int]]',                             'tuple[*tuple_type[int]]'),  # Should be tuple[int]
-            ('Tuple[*Ts]',                             '[*tuple_type[int]]',                             'Tuple[*tuple_type[int]]'),  # Should be Tuple[int]
-
-            ('C[*Ts]',                                 '[*tuple_type[*Ts]]',                             'C[*tuple_type[*Ts]]'),  # Should be C[*Ts]
-            ('tuple[*Ts]',                             '[*tuple_type[*Ts]]',                             'tuple[*tuple_type[*Ts]]'),  # Should be tuple[*Ts]
-            ('Tuple[*Ts]',                             '[*tuple_type[*Ts]]',                             'Tuple[*tuple_type[*Ts]]'),  # Should be Tuple[*Ts]
-
-            ('C[*Ts]',                                 '[*tuple_type[int, str]]',                        'C[*tuple_type[int, str]]'),  # Should be C[int, str]
-            ('tuple[*Ts]',                             '[*tuple_type[int, str]]',                        'tuple[*tuple_type[int, str]]'),  # Should be tuple[int, str]
-            ('Tuple[*Ts]',                             '[*tuple_type[int, str]]',                        'Tuple[*tuple_type[int, str]]'),  # Should be Tuple[int, str]
-
-            ('C[*Ts]',                                 '[tuple_type[int, ...]]',                         'C[tuple_type[int, ...]]'),
-            ('tuple[*Ts]',                             '[tuple_type[int, ...]]',                         'tuple[tuple_type[int, ...]]'),
-            ('Tuple[*Ts]',                             '[tuple_type[int, ...]]',                         'Tuple[tuple_type[int, ...]]'),
-
-            ('C[*Ts]',                                 '[tuple_type[int, ...], tuple_type[str, ...]]',   'C[tuple_type[int, ...], tuple_type[str, ...]]'),
-            ('tuple[*Ts]',                             '[tuple_type[int, ...], tuple_type[str, ...]]',   'tuple[tuple_type[int, ...], tuple_type[str, ...]]'),
-            ('Tuple[*Ts]',                             '[tuple_type[int, ...], tuple_type[str, ...]]',   'Tuple[tuple_type[int, ...], tuple_type[str, ...]]'),
-
-            ('C[*Ts]',                                 '[*tuple_type[int, ...]]',                        'C[*tuple_type[int, ...]]'),
-            ('tuple[*Ts]',                             '[*tuple_type[int, ...]]',                        'tuple[*tuple_type[int, ...]]'),
-            ('Tuple[*Ts]',                             '[*tuple_type[int, ...]]',                        'Tuple[*tuple_type[int, ...]]'),
+            ('generic[*Ts]',                           '[()]',                                           'generic[()]'),
+            ('generic[*Ts]',                           '[int]',                                          'generic[int]'),
+            ('generic[*Ts]',                           '[int, str]',                                     'generic[int, str]'),
+            ('generic[*Ts]',                           '[*tuple_type[int]]',                             'generic[int]'),
+            ('generic[*Ts]',                           '[*tuple_type[*Ts]]',                             'generic[*Ts]'),
+            ('generic[*Ts]',                           '[*tuple_type[int, str]]',                        'generic[int, str]'),
+            ('generic[*Ts]',                           '[tuple_type[int, ...]]',                         'generic[tuple_type[int, ...]]'),
+            ('generic[*Ts]',                           '[tuple_type[int, ...], tuple_type[str, ...]]',   'generic[tuple_type[int, ...], tuple_type[str, ...]]'),
+            ('generic[*Ts]',                           '[*tuple_type[int, ...]]',                        'generic[*tuple_type[int, ...]]'),
 
             # Technically, multiple unpackings are forbidden by PEP 646, but we
             # choose to be less restrictive at runtime, to allow folks room
             # to experiment. So all three of these should be valid.
-            ('C[*Ts]',                                 '[*tuple_type[int, ...], *tuple_type[str, ...]]', 'C[*tuple_type[int, ...], *tuple_type[str, ...]]'),
-            ('tuple[*Ts]',                             '[*tuple_type[int, ...], *tuple_type[str, ...]]', 'tuple[*tuple_type[int, ...], *tuple_type[str, ...]]'),
-            ('Tuple[*Ts]',                             '[*tuple_type[int, ...], *tuple_type[str, ...]]', 'Tuple[*tuple_type[int, ...], *tuple_type[str, ...]]'),
-
-            ('C[*Ts]',                                 '[*Ts]',                                          'C[*Ts]'),
-            ('tuple[*Ts]',                             '[*Ts]',                                          'tuple[*Ts]'),
-            ('Tuple[*Ts]',                             '[*Ts]',                                          'Tuple[*Ts]'),
-
-            ('C[*Ts]',                                 '[T, *Ts]',                                       'C[T, *Ts]'),
-            ('tuple[*Ts]',                             '[T, *Ts]',                                       'tuple[T, *Ts]'),
-            ('Tuple[*Ts]',                             '[T, *Ts]',                                       'Tuple[T, *Ts]'),
+            ('generic[*Ts]',                           '[*tuple_type[int, ...], *tuple_type[str, ...]]', 'generic[*tuple_type[int, ...], *tuple_type[str, ...]]'),
 
-            ('C[*Ts]',                                 '[*Ts, T]',                                       'C[*Ts, T]'),
-            ('tuple[*Ts]',                             '[*Ts, T]',                                       'tuple[*Ts, T]'),
-            ('Tuple[*Ts]',                             '[*Ts, T]',                                       'Tuple[*Ts, T]'),
+            ('generic[*Ts]',                           '[*Ts]',                                          'generic[*Ts]'),
+            ('generic[*Ts]',                           '[T, *Ts]',                                       'generic[T, *Ts]'),
+            ('generic[*Ts]',                           '[*Ts, T]',                                       'generic[*Ts, T]'),
+            ('generic[T, *Ts]',                        '[int]',                                          'generic[int]'),
+            ('generic[T, *Ts]',                        '[int, str]',                                     'generic[int, str]'),
+            ('generic[T, *Ts]',                        '[int, str, bool]',                               'generic[int, str, bool]'),
 
-            ('C[T, *Ts]',                              '[int]',                                          'C[int]'),
-            ('tuple[T, *Ts]',                          '[int]',                                          'tuple[int]'),
-            ('Tuple[T, *Ts]',                          '[int]',                                          'Tuple[int]'),
+            ('generic[T, *Ts]',                        '[*tuple[int, ...]]',                             'TypeError'),  # Should be generic[int, *tuple[int, ...]]
 
-            ('C[T, *Ts]',                              '[int, str]',                                     'C[int, str]'),
-            ('tuple[T, *Ts]',                          '[int, str]',                                     'tuple[int, str]'),
-            ('Tuple[T, *Ts]',                          '[int, str]',                                     'Tuple[int, str]'),
-
-            ('C[T, *Ts]',                              '[int, str, bool]',                               'C[int, str, bool]'),
-            ('tuple[T, *Ts]',                          '[int, str, bool]',                               'tuple[int, str, bool]'),
-            ('Tuple[T, *Ts]',                          '[int, str, bool]',                               'Tuple[int, str, bool]'),
-
-            ('C[T, *Ts]',                              '[*tuple[int, ...]]',                             'C[*tuple[int, ...]]'),  # Should be C[int, *tuple[int, ...]]
-            ('C[T, *Ts]',                              '[*Tuple[int, ...]]',                             'TypeError'),  # Ditto
-            ('tuple[T, *Ts]',                          '[*tuple[int, ...]]',                             'tuple[*tuple[int, ...]]'),  # Should be tuple[int, *tuple[int, ...]]
-            ('tuple[T, *Ts]',                          '[*Tuple[int, ...]]',                             'TypeError'),  # Should be tuple[int, *Tuple[int, ...]]
-            ('Tuple[T, *Ts]',                          '[*tuple[int, ...]]',                             'Tuple[*tuple[int, ...]]'),  # Should be Tuple[int, *tuple[int, ...]]
-            ('Tuple[T, *Ts]',                          '[*Tuple[int, ...]]',                             'TypeError'),  # Should be Tuple[int, *Tuple[int, ...]]
-
-            ('C[*Ts, T]',                              '[int]',                                          'C[int]'),
-            ('tuple[*Ts, T]',                          '[int]',                                          'tuple[int]'),
-            ('Tuple[*Ts, T]',                          '[int]',                                          'Tuple[int]'),
-
-            ('C[*Ts, T]',                              '[int, str]',                                     'C[int, str]'),
-            ('tuple[*Ts, T]',                          '[int, str]',                                     'tuple[int, str]'),
-            ('Tuple[*Ts, T]',                          '[int, str]',                                     'Tuple[int, str]'),
-
-            ('C[*Ts, T]',                              '[int, str, bool]',                               'C[int, str, bool]'),
-            ('tuple[*Ts, T]',                          '[int, str, bool]',                               'tuple[int, str, bool]'),
-            ('Tuple[*Ts, T]',                          '[int, str, bool]',                               'Tuple[int, str, bool]'),
+            ('generic[*Ts, T]',                        '[int]',                                          'generic[int]'),
+            ('generic[*Ts, T]',                        '[int, str]',                                     'generic[int, str]'),
+            ('generic[*Ts, T]',                          '[int, str, bool]',                             'generic[int, str, bool]'),
 
             ('generic[T, *tuple_type[int, ...]]',      '[str]',                                          'generic[str, *tuple_type[int, ...]]'),
             ('generic[T1, T2, *tuple_type[int, ...]]', '[str, bool]',                                    'generic[str, bool, *tuple_type[int, ...]]'),
             ('generic[T1, *tuple_type[int, ...], T2]', '[str, bool]',                                    'generic[str, *tuple_type[int, ...], bool]'),
             ('generic[T1, *tuple_type[int, ...], T2]', '[str, bool, float]',                             'TypeError'),
+
+            ('generic[T1, *tuple_type[T2, ...]]',      '[int, str]',                                     'generic[int, *tuple_type[str, ...]]'),
+            ('generic[*tuple_type[T1, ...], T2]',      '[int, str]',                                     'generic[*tuple_type[int, ...], str]'),
+            ('generic[T1, *tuple_type[generic[*Ts], ...]]', '[int, str, bool]',                          'generic[int, *tuple_type[generic[str, bool], ...]]'),
+            ('generic[*tuple_type[generic[*Ts], ...], T1]', '[int, str, bool]',                          'generic[*tuple_type[generic[int, str], ...], bool]'),
         ]
 
         for alias_template, args_template, expected_template in tests:
diff --git a/Lib/typing.py b/Lib/typing.py
index 46ef2d9cf6005..9fa4c48b45e85 100644
--- a/Lib/typing.py
+++ b/Lib/typing.py
@@ -271,6 +271,16 @@ 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 _unpack_args(args):
+    newargs = []
+    for arg in args:
+        subargs = getattr(arg, '__typing_unpacked_tuple_args__', None)
+        if subargs is not None and not (subargs and subargs[-1] is ...):
+            newargs.extend(subargs)
+        else:
+            newargs.append(arg)
+    return newargs
+
 def _prepare_paramspec_params(cls, params):
     """Prepares the parameters for a Generic containing ParamSpec
     variables (internal helper).
@@ -892,12 +902,8 @@ def __repr__(self):
 
 
 def _is_unpacked_typevartuple(x: Any) -> bool:
-    return (
-            isinstance(x, _UnpackGenericAlias)
-            # If x is Unpack[tuple[...]], __parameters__ will be empty.
-            and x.__parameters__
-            and isinstance(x.__parameters__[0], TypeVarTuple)
-    )
+    return ((not isinstance(x, type)) and
+            getattr(x, '__typing_is_unpacked_typevartuple__', False))
 
 
 def _is_typevar_like(x: Any) -> bool:
@@ -1010,7 +1016,8 @@ def __init__(self, name, *constraints, bound=None,
     def __typing_subst__(self, arg):
         msg = "Parameters to generic types must be types."
         arg = _type_check(arg, msg, is_argument=True)
-        if (isinstance(arg, _GenericAlias) and arg.__origin__ is Unpack):
+        if ((isinstance(arg, _GenericAlias) and arg.__origin__ is Unpack) or
+            (isinstance(arg, GenericAlias) and getattr(arg, '__unpacked__', False))):
             raise TypeError(f"{arg} is not valid as type argument")
         return arg
 
@@ -1369,19 +1376,17 @@ def __getitem__(self, args):
         if self.__origin__ in (Generic, Protocol):
             # Can't subscript Generic[...] or Protocol[...].
             raise TypeError(f"Cannot subscript already-subscripted {self}")
+        if not self.__parameters__:
+            raise TypeError(f"{self} is not a generic class")
 
         # Preprocess `args`.
         if not isinstance(args, tuple):
             args = (args,)
         args = tuple(_type_convert(p) for p in args)
+        args = _unpack_args(args)
         if (self._paramspec_tvars
                 and any(isinstance(t, ParamSpec) for t in self.__parameters__)):
             args = _prepare_paramspec_params(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)
@@ -1405,16 +1410,28 @@ def _determine_new_args(self, args):
         params = self.__parameters__
         # In the example above, this would be {T3: str}
         new_arg_by_param = {}
+        typevartuple_index = None
         for i, param in enumerate(params):
             if isinstance(param, TypeVarTuple):
-                j = len(args) - (len(params) - i - 1)
-                if j < i:
-                    raise TypeError(f"Too few arguments for {self}")
-                new_arg_by_param.update(zip(params[:i], args[:i]))
-                new_arg_by_param[param] = args[i: j]
-                new_arg_by_param.update(zip(params[i + 1:], args[j:]))
-                break
+                if typevartuple_index is not None:
+                    raise TypeError(f"More than one TypeVarTuple parameter in {self}")
+                typevartuple_index = i
+
+        alen = len(args)
+        plen = len(params)
+        if typevartuple_index is not None:
+            i = typevartuple_index
+            j = alen - (plen - i - 1)
+            if j < i:
+                raise TypeError(f"Too few arguments for {self};"
+                                f" actual {alen}, expected at least {plen-1}")
+            new_arg_by_param.update(zip(params[:i], args[:i]))
+            new_arg_by_param[params[i]] = tuple(args[i: j])
+            new_arg_by_param.update(zip(params[i + 1:], args[j:]))
         else:
+            if alen != plen:
+                raise TypeError(f"Too {'many' if alen > plen else 'few'} arguments for {self};"
+                                f" actual {alen}, expected {plen}")
             new_arg_by_param.update(zip(params, args))
 
         new_args = []
@@ -1722,14 +1739,25 @@ def __repr__(self):
         return '*' + repr(self.__args__[0])
 
     def __getitem__(self, args):
-        if self.__typing_unpacked__():
+        if self.__typing_is_unpacked_typevartuple__:
             return args
         return super().__getitem__(args)
 
-    def __typing_unpacked__(self):
-        # If x is Unpack[tuple[...]], __parameters__ will be empty.
-        return bool(self.__parameters__ and
-                    isinstance(self.__parameters__[0], TypeVarTuple))
+    @property
+    def __typing_unpacked_tuple_args__(self):
+        assert self.__origin__ is Unpack
+        assert len(self.__args__) == 1
+        arg, = self.__args__
+        if isinstance(arg, _GenericAlias):
+            assert arg.__origin__ is tuple
+            return arg.__args__
+        return None
+
+    @property
+    def __typing_is_unpacked_typevartuple__(self):
+        assert self.__origin__ is Unpack
+        assert len(self.__args__) == 1
+        return isinstance(self.__args__[0], TypeVarTuple)
 
 
 class Generic:
diff --git a/Objects/genericaliasobject.c b/Objects/genericaliasobject.c
index 5eeb1dbb338d2..39fd70999ecbe 100644
--- a/Objects/genericaliasobject.c
+++ b/Objects/genericaliasobject.c
@@ -320,20 +320,85 @@ subs_tvars(PyObject *obj, PyObject *params,
 static int
 _is_unpacked_typevartuple(PyObject *arg)
 {
-    PyObject *meth;
-    int res = _PyObject_LookupAttr(arg, &_Py_ID(__typing_unpacked__), &meth);
+    PyObject *tmp;
+    if (PyType_Check(arg)) { // TODO: Add test
+        return 0;
+    }
+    int res = _PyObject_LookupAttr(arg, &_Py_ID(__typing_is_unpacked_typevartuple__), &tmp);
     if (res > 0) {
-        PyObject *tmp = PyObject_CallNoArgs(meth);
-        Py_DECREF(meth);
-        if (tmp == NULL) {
-            return -1;
-        }
         res = PyObject_IsTrue(tmp);
         Py_DECREF(tmp);
     }
     return res;
 }
 
+static PyObject *
+_unpacked_tuple_args(PyObject *arg)
+{
+    PyObject *result;
+    assert(!PyType_Check(arg));
+    // Fast path
+    if (_PyGenericAlias_Check(arg) &&
+            ((gaobject *)arg)->starred &&
+            ((gaobject *)arg)->origin == (PyObject *)&PyTuple_Type)
+    {
+        result = ((gaobject *)arg)->args;
+        Py_INCREF(result);
+        return result;
+    }
+
+    if (_PyObject_LookupAttr(arg, &_Py_ID(__typing_unpacked_tuple_args__), &result) > 0) {
+        if (result == Py_None) {
+            Py_DECREF(result);
+            return NULL;
+        }
+        return result;
+    }
+    return NULL;
+}
+
+static PyObject *
+_unpack_args(PyObject *item)
+{
+    PyObject *newargs = PyList_New(0);
+    if (newargs == NULL) {
+        return NULL;
+    }
+    int is_tuple = PyTuple_Check(item);
+    Py_ssize_t nitems = is_tuple ? PyTuple_GET_SIZE(item) : 1;
+    PyObject **argitems = is_tuple ? &PyTuple_GET_ITEM(item, 0) : &item;
+    for (Py_ssize_t i = 0; i < nitems; i++) {
+        item = argitems[i];
+        if (!PyType_Check(item)) {
+            PyObject *subargs = _unpacked_tuple_args(item);
+            if (subargs != NULL &&
+                PyTuple_Check(subargs) &&
+                !(PyTuple_GET_SIZE(subargs) &&
+                  PyTuple_GET_ITEM(subargs, PyTuple_GET_SIZE(subargs)-1) == Py_Ellipsis))
+            {
+                if (PyList_SetSlice(newargs, PY_SSIZE_T_MAX, PY_SSIZE_T_MAX, subargs) < 0) {
+                    Py_DECREF(subargs);
+                    Py_DECREF(newargs);
+                    return NULL;
+                }
+                Py_DECREF(subargs);
+                continue;
+            }
+            Py_XDECREF(subargs);
+            if (PyErr_Occurred()) {
+                Py_DECREF(newargs);
+                return NULL;
+            }
+        }
+        if (PyList_Append(newargs, item) < 0) {
+            Py_DECREF(newargs);
+            return NULL;
+        }
+    }
+    Py_SETREF(newargs, PySequence_Tuple(newargs));
+    return newargs;
+}
+
 PyObject *
 _Py_subs_parameters(PyObject *self, PyObject *args, PyObject *parameters, PyObject *item)
 {
@@ -343,18 +408,26 @@ _Py_subs_parameters(PyObject *self, PyObject *args, PyObject *parameters, PyObje
                             "%R is not a generic class",
                             self);
     }
+    item = _unpack_args(item);
     int is_tuple = PyTuple_Check(item);
     Py_ssize_t nitems = is_tuple ? PyTuple_GET_SIZE(item) : 1;
     PyObject **argitems = is_tuple ? &PyTuple_GET_ITEM(item, 0) : &item;
-    Py_ssize_t varparam = 0;
-    for (; varparam < nparams; varparam++) {
-        PyObject *param = PyTuple_GET_ITEM(parameters, varparam);
+    Py_ssize_t varparam = nparams;
+    for (Py_ssize_t i = 0; i < nparams; i++) {
+        PyObject *param = PyTuple_GET_ITEM(parameters, i);
         if (Py_TYPE(param)->tp_iter) {  // TypeVarTuple
-            break;
+            if (varparam < nparams) {
+                Py_DECREF(item);
+                return PyErr_Format(PyExc_TypeError,
+                                    "More than one TypeVarTuple parameter in %S",
+                                    self);
+            }
+            varparam = i;
         }
     }
     if (varparam < nparams) {
         if (nitems < nparams - 1) {
+            Py_DECREF(item);
             return PyErr_Format(PyExc_TypeError,
                                 "Too few arguments for %R",
                                 self);
@@ -362,10 +435,11 @@ _Py_subs_parameters(PyObject *self, PyObject *args, PyObject *parameters, PyObje
     }
     else {
         if (nitems != nparams) {
+            Py_DECREF(item);
             return PyErr_Format(PyExc_TypeError,
-                                "Too %s arguments for %R",
+                                "Too %s arguments for %R; actual %zd, expected %zd",
                                 nitems > nparams ? "many" : "few",
-                                self);
+                                self, nitems, nparams);
         }
     }
     /* Replace all type variables (specified by parameters)
@@ -377,6 +451,7 @@ _Py_subs_parameters(PyObject *self, PyObject *args, PyObject *parameters, PyObje
     Py_ssize_t nargs = PyTuple_GET_SIZE(args);
     PyObject *newargs = PyTuple_New(nargs);
     if (newargs == NULL) {
+        Py_DECREF(item);
         return NULL;
     }
     for (Py_ssize_t iarg = 0, jarg = 0; iarg < nargs; iarg++) {
@@ -384,11 +459,13 @@ _Py_subs_parameters(PyObject *self, PyObject *args, PyObject *parameters, PyObje
         int unpack = _is_unpacked_typevartuple(arg);
         if (unpack < 0) {
             Py_DECREF(newargs);
+            Py_DECREF(item);
             return NULL;
         }
         PyObject *subst;
         if (_PyObject_LookupAttr(arg, &_Py_ID(__typing_subst__), &subst) < 0) {
             Py_DECREF(newargs);
+            Py_DECREF(item);
             return NULL;
         }
         if (subst) {
@@ -397,6 +474,7 @@ _Py_subs_parameters(PyObject *self, PyObject *args, PyObject *parameters, PyObje
             if (iparam == varparam) {
                 Py_DECREF(subst);
                 Py_DECREF(newargs);
+                Py_DECREF(item);
                 PyErr_SetString(PyExc_TypeError,
                         "Substitution of bare TypeVarTuple is not supported");
                 return NULL;
@@ -412,6 +490,7 @@ _Py_subs_parameters(PyObject *self, PyObject *args, PyObject *parameters, PyObje
         }
         if (arg == NULL) {
             Py_DECREF(newargs);
+            Py_DECREF(item);
             return NULL;
         }
         if (unpack) {
@@ -419,6 +498,7 @@ _Py_subs_parameters(PyObject *self, PyObject *args, PyObject *parameters, PyObje
                     &PyTuple_GET_ITEM(arg, 0), PyTuple_GET_SIZE(arg));
             Py_DECREF(arg);
             if (jarg < 0) {
+                Py_DECREF(item);
                 return NULL;
             }
         }
@@ -428,6 +508,7 @@ _Py_subs_parameters(PyObject *self, PyObject *args, PyObject *parameters, PyObje
         }
     }
 
+    Py_DECREF(item);
     return newargs;
 }
 
@@ -454,6 +535,7 @@ ga_getitem(PyObject *self, PyObject *item)
     }
 
     PyObject *res = Py_GenericAlias(alias->origin, newargs);
+    ((gaobject *)res)->starred = alias->starred;
 
     Py_DECREF(newargs);
     return res;
@@ -518,6 +600,7 @@ static const char* const attr_exceptions[] = {
     "__args__",
     "__unpacked__",
     "__parameters__",
+    "__typing_unpacked_tuple_args__",
     "__mro_entries__",
     "__reduce_ex__",  // needed so we don't look up object.__reduce_ex__
     "__reduce__",
@@ -689,8 +772,20 @@ ga_parameters(PyObject *self, void *unused)
     return alias->parameters;
 }
 
+static PyObject *
+ga_unpacked_tuple_args(PyObject *self, void *unused)
+{
+    gaobject *alias = (gaobject *)self;
+    if (alias->starred && alias->origin == (PyObject *)&PyTuple_Type) {
+        Py_INCREF(alias->args);
+        return alias->args;
+    }
+    Py_RETURN_NONE;
+}
+
 static PyGetSetDef ga_properties[] = {
     {"__parameters__", ga_parameters, (setter)NULL, "Type variables in the GenericAlias.", NULL},
+    {"__typing_unpacked_tuple_args__", ga_unpacked_tuple_args, (setter)NULL, NULL},
     {0}
 };
 



More information about the Python-checkins mailing list