From 8419f01673858d5002747c9393d4ed0ec22fdb47 Mon Sep 17 00:00:00 2001
From: Jelle Zijlstra <jelle.zijlstra@gmail.com>
Date: Mon, 6 May 2024 15:35:06 -0700
Subject: [PATCH] gh-118647: Add defaults to typing.Generator and
 typing.AsyncGenerator (#118648)

Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
Co-authored-by: Nikita Sobolev <mail@sobolevn.me>
---
 Doc/library/typing.rst                        | 26 ++++++++++++++++---
 Lib/test/test_typing.py                       | 11 ++++++++
 Lib/typing.py                                 | 21 +++++++++++----
 ...-05-06-08-23-01.gh-issue-118648.OVA3jJ.rst |  2 ++
 4 files changed, 51 insertions(+), 9 deletions(-)
 create mode 100644 Misc/NEWS.d/next/Library/2024-05-06-08-23-01.gh-issue-118648.OVA3jJ.rst

diff --git a/Doc/library/typing.rst b/Doc/library/typing.rst
index e06287250641e6..652a3f1f70519c 100644
--- a/Doc/library/typing.rst
+++ b/Doc/library/typing.rst
@@ -3648,8 +3648,14 @@ Aliases to asynchronous ABCs in :mod:`collections.abc`
    is no ``ReturnType`` type parameter. As with :class:`Generator`, the
    ``SendType`` behaves contravariantly.
 
-   If your generator will only yield values, set the ``SendType`` to
-   ``None``::
+   The ``SendType`` defaults to :const:`!None`::
+
+      async def infinite_stream(start: int) -> AsyncGenerator[int]:
+          while True:
+              yield start
+              start = await increment(start)
+
+   It is also possible to set this type explicitly::
 
       async def infinite_stream(start: int) -> AsyncGenerator[int, None]:
           while True:
@@ -3671,6 +3677,9 @@ Aliases to asynchronous ABCs in :mod:`collections.abc`
       now supports subscripting (``[]``).
       See :pep:`585` and :ref:`types-genericalias`.
 
+   .. versionchanged:: 3.13
+      The ``SendType`` parameter now has a default.
+
 .. class:: AsyncIterable(Generic[T_co])
 
    Deprecated alias to :class:`collections.abc.AsyncIterable`.
@@ -3754,8 +3763,14 @@ Aliases to other ABCs in :mod:`collections.abc`
    of :class:`Generator` behaves contravariantly, not covariantly or
    invariantly.
 
-   If your generator will only yield values, set the ``SendType`` and
-   ``ReturnType`` to ``None``::
+   The ``SendType`` and ``ReturnType`` parameters default to :const:`!None`::
+
+      def infinite_stream(start: int) -> Generator[int]:
+          while True:
+              yield start
+              start += 1
+
+   It is also possible to set these types explicitly::
 
       def infinite_stream(start: int) -> Generator[int, None, None]:
           while True:
@@ -3774,6 +3789,9 @@ Aliases to other ABCs in :mod:`collections.abc`
       :class:`collections.abc.Generator` now supports subscripting (``[]``).
       See :pep:`585` and :ref:`types-genericalias`.
 
+   .. versionchanged:: 3.13
+      Default values for the send and return types were added.
+
 .. class:: Hashable
 
    Deprecated alias to :class:`collections.abc.Hashable`.
diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py
index 112db03ae87887..8f0be1fd3f55e5 100644
--- a/Lib/test/test_typing.py
+++ b/Lib/test/test_typing.py
@@ -7289,6 +7289,17 @@ def foo():
         g = foo()
         self.assertIsSubclass(type(g), typing.Generator)
 
+    def test_generator_default(self):
+        g1 = typing.Generator[int]
+        g2 = typing.Generator[int, None, None]
+        self.assertEqual(get_args(g1), (int, type(None), type(None)))
+        self.assertEqual(get_args(g1), get_args(g2))
+
+        g3 = typing.Generator[int, float]
+        g4 = typing.Generator[int, float, None]
+        self.assertEqual(get_args(g3), (int, float, type(None)))
+        self.assertEqual(get_args(g3), get_args(g4))
+
     def test_no_generator_instantiation(self):
         with self.assertRaises(TypeError):
             typing.Generator()
diff --git a/Lib/typing.py b/Lib/typing.py
index ff0e9b811a5867..c159fcfda68ee8 100644
--- a/Lib/typing.py
+++ b/Lib/typing.py
@@ -1328,7 +1328,7 @@ def __getattr__(self, attr):
         raise AttributeError(attr)
 
     def __setattr__(self, attr, val):
-        if _is_dunder(attr) or attr in {'_name', '_inst', '_nparams'}:
+        if _is_dunder(attr) or attr in {'_name', '_inst', '_nparams', '_defaults'}:
             super().__setattr__(attr, val)
         else:
             setattr(self.__origin__, attr, val)
@@ -1578,11 +1578,12 @@ def __iter__(self):
 # parameters are accepted (needs custom __getitem__).
 
 class _SpecialGenericAlias(_NotIterable, _BaseGenericAlias, _root=True):
-    def __init__(self, origin, nparams, *, inst=True, name=None):
+    def __init__(self, origin, nparams, *, inst=True, name=None, defaults=()):
         if name is None:
             name = origin.__name__
         super().__init__(origin, inst=inst, name=name)
         self._nparams = nparams
+        self._defaults = defaults
         if origin.__module__ == 'builtins':
             self.__doc__ = f'A generic version of {origin.__qualname__}.'
         else:
@@ -1594,12 +1595,22 @@ def __getitem__(self, params):
             params = (params,)
         msg = "Parameters to generic types must be types."
         params = tuple(_type_check(p, msg) for p in params)
+        if (self._defaults
+            and len(params) < self._nparams
+            and len(params) + len(self._defaults) >= self._nparams
+        ):
+            params = (*params, *self._defaults[len(params) - self._nparams:])
         actual_len = len(params)
+
         if actual_len != self._nparams:
+            if self._defaults:
+                expected = f"at least {self._nparams - len(self._defaults)}"
+            else:
+                expected = str(self._nparams)
             if not self._nparams:
                 raise TypeError(f"{self} is not a generic class")
             raise TypeError(f"Too {'many' if actual_len > self._nparams else 'few'} arguments for {self};"
-                            f" actual {actual_len}, expected {self._nparams}")
+                            f" actual {actual_len}, expected {expected}")
         return self.copy_with(params)
 
     def copy_with(self, params):
@@ -2813,8 +2824,8 @@ class Other(Leaf):  # Error reported by type checker
 OrderedDict = _alias(collections.OrderedDict, 2)
 Counter = _alias(collections.Counter, 1)
 ChainMap = _alias(collections.ChainMap, 2)
-Generator = _alias(collections.abc.Generator, 3)
-AsyncGenerator = _alias(collections.abc.AsyncGenerator, 2)
+Generator = _alias(collections.abc.Generator, 3, defaults=(types.NoneType, types.NoneType))
+AsyncGenerator = _alias(collections.abc.AsyncGenerator, 2, defaults=(types.NoneType,))
 Type = _alias(type, 1, inst=False, name='Type')
 Type.__doc__ = \
     """Deprecated alias to builtins.type.
diff --git a/Misc/NEWS.d/next/Library/2024-05-06-08-23-01.gh-issue-118648.OVA3jJ.rst b/Misc/NEWS.d/next/Library/2024-05-06-08-23-01.gh-issue-118648.OVA3jJ.rst
new file mode 100644
index 00000000000000..7695fb0ba20df8
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2024-05-06-08-23-01.gh-issue-118648.OVA3jJ.rst
@@ -0,0 +1,2 @@
+Add type parameter defaults to :class:`typing.Generator` and
+:class:`typing.AsyncGenerator`.