Merge pull request #21617 from charris/update-download-wheels

MAINT, STY: Make download-wheels download source files.
diff --git a/.github/workflows/build_test.yml b/.github/workflows/build_test.yml
index dd4dff0..4a2e3e6 100644
--- a/.github/workflows/build_test.yml
+++ b/.github/workflows/build_test.yml
@@ -250,10 +250,12 @@
         # use x86_64 cross-compiler to speed up the build
         sudo apt update
         sudo apt install -y gcc-arm-linux-gnueabihf g++-arm-linux-gnueabihf
+
+        # Keep the `test_requirements.txt` dependency-subset synced
         docker run --name the_container --interactive -v /:/host arm32v7/ubuntu:focal /bin/bash -c "
           apt update &&
           apt install -y git python3 python3-dev python3-pip &&
-          pip3 install cython==0.29.30 setuptools\<49.2.0 hypothesis==6.23.3 pytest==6.2.5 &&
+          pip3 install cython==0.29.30 setuptools\<49.2.0 hypothesis==6.23.3 pytest==6.2.5 'typing_extensions>=4.2.0' &&
           ln -s /host/lib64 /lib64 &&
           ln -s /host/lib/x86_64-linux-gnu /lib/x86_64-linux-gnu &&
           ln -s /host/usr/arm-linux-gnueabihf /usr/arm-linux-gnueabihf &&
diff --git a/environment.yml b/environment.yml
index c45b406..24bf738 100644
--- a/environment.yml
+++ b/environment.yml
@@ -20,6 +20,7 @@
   - hypothesis
   # For type annotations
   - mypy=0.950
+  - typing_extensions>=4.2.0
   # For building docs
   - sphinx=4.5.0
   - sphinx-panels
diff --git a/numpy/_typing/_generic_alias.py b/numpy/_typing/_generic_alias.py
index 0541ad7..d32814a 100644
--- a/numpy/_typing/_generic_alias.py
+++ b/numpy/_typing/_generic_alias.py
@@ -64,7 +64,7 @@
         args.append(value)
 
     cls = type(alias)
-    return cls(alias.__origin__, tuple(args))
+    return cls(alias.__origin__, tuple(args), alias.__unpacked__)
 
 
 class _GenericAlias:
@@ -80,7 +80,14 @@
 
     """
 
-    __slots__ = ("__weakref__", "_origin", "_args", "_parameters", "_hash")
+    __slots__ = (
+        "__weakref__",
+        "_origin",
+        "_args",
+        "_parameters",
+        "_hash",
+        "_starred",
+    )
 
     @property
     def __origin__(self) -> type:
@@ -95,14 +102,27 @@
         """Type variables in the ``GenericAlias``."""
         return super().__getattribute__("_parameters")
 
+    @property
+    def __unpacked__(self) -> bool:
+        return super().__getattribute__("_starred")
+
+    @property
+    def __typing_unpacked_tuple_args__(self) -> tuple[object, ...] | None:
+        # NOTE: This should return `__args__` if `__origin__` is a tuple,
+        # which should never be the case with how `_GenericAlias` is used
+        # within numpy
+        return None
+
     def __init__(
         self,
         origin: type,
         args: object | tuple[object, ...],
+        starred: bool = False,
     ) -> None:
         self._origin = origin
         self._args = args if isinstance(args, tuple) else (args,)
         self._parameters = tuple(_parse_parameters(self.__args__))
+        self._starred = starred
 
     @property
     def __call__(self) -> type[Any]:
@@ -110,10 +130,10 @@
 
     def __reduce__(self: _T) -> tuple[
         type[_T],
-        tuple[type[Any], tuple[object, ...]],
+        tuple[type[Any], tuple[object, ...], bool],
     ]:
         cls = type(self)
-        return cls, (self.__origin__, self.__args__)
+        return cls, (self.__origin__, self.__args__, self.__unpacked__)
 
     def __mro_entries__(self, bases: Iterable[object]) -> tuple[type[Any]]:
         return (self.__origin__,)
@@ -130,7 +150,11 @@
         try:
             return super().__getattribute__("_hash")
         except AttributeError:
-            self._hash: int = hash(self.__origin__) ^ hash(self.__args__)
+            self._hash: int = (
+                hash(self.__origin__) ^
+                hash(self.__args__) ^
+                hash(self.__unpacked__)
+            )
             return super().__getattribute__("_hash")
 
     def __instancecheck__(self, obj: object) -> NoReturn:
@@ -147,7 +171,8 @@
         """Return ``repr(self)``."""
         args = ", ".join(_to_str(i) for i in self.__args__)
         origin = _to_str(self.__origin__)
-        return f"{origin}[{args}]"
+        prefix = "*" if self.__unpacked__ else ""
+        return f"{prefix}{origin}[{args}]"
 
     def __getitem__(self: _T, key: object | tuple[object, ...]) -> _T:
         """Return ``self[key]``."""
@@ -169,9 +194,17 @@
             return NotImplemented
         return (
             self.__origin__ == value.__origin__ and
-            self.__args__ == value.__args__
+            self.__args__ == value.__args__ and
+            self.__unpacked__ == getattr(
+                value, "__unpacked__", self.__unpacked__
+            )
         )
 
+    def __iter__(self: _T) -> Generator[_T, None, None]:
+        """Return ``iter(self)``."""
+        cls = type(self)
+        yield cls(self.__origin__, self.__args__, True)
+
     _ATTR_EXCEPTIONS: ClassVar[frozenset[str]] = frozenset({
         "__origin__",
         "__args__",
@@ -181,6 +214,8 @@
         "__reduce_ex__",
         "__copy__",
         "__deepcopy__",
+        "__unpacked__",
+        "__typing_unpacked_tuple_args__",
     })
 
     def __getattribute__(self, name: str) -> Any:
diff --git a/numpy/core/include/numpy/numpyconfig.h b/numpy/core/include/numpy/numpyconfig.h
index b2e7c45..6be87b7 100644
--- a/numpy/core/include/numpy/numpyconfig.h
+++ b/numpy/core/include/numpy/numpyconfig.h
@@ -63,6 +63,7 @@
 #define NPY_1_20_API_VERSION 0x0000000e
 #define NPY_1_21_API_VERSION 0x0000000e
 #define NPY_1_22_API_VERSION 0x0000000f
-#define NPY_1_23_API_VERSION 0x0000000f
+#define NPY_1_23_API_VERSION 0x00000010
+#define NPY_1_24_API_VERSION 0x00000010
 
 #endif  /* NUMPY_CORE_INCLUDE_NUMPY_NPY_NUMPYCONFIG_H_ */
diff --git a/numpy/core/setup.py b/numpy/core/setup.py
index fe90201..41f702b 100644
--- a/numpy/core/setup.py
+++ b/numpy/core/setup.py
@@ -476,11 +476,8 @@
     local_dir = config.local_path
     codegen_dir = join(local_dir, 'code_generators')
 
-    if is_released:
-        warnings.simplefilter('error', MismatchCAPIWarning)
-
     # Check whether we have a mismatch between the set C API VERSION and the
-    # actual C API VERSION
+    # actual C API VERSION. Will raise a MismatchCAPIError if so.
     check_api_version(C_API_VERSION, codegen_dir)
 
     generate_umath_py = join(codegen_dir, 'generate_umath.py')
diff --git a/numpy/core/setup_common.py b/numpy/core/setup_common.py
index 44fa0a6..27281fd 100644
--- a/numpy/core/setup_common.py
+++ b/numpy/core/setup_common.py
@@ -31,6 +31,8 @@
 # (*not* C_ABI_VERSION) would be increased.  Whenever binary compatibility is
 # broken, both C_API_VERSION and C_ABI_VERSION should be increased.
 #
+# The version needs to be kept in sync with that in cversions.txt.
+#
 # 0x00000008 - 1.7.x
 # 0x00000009 - 1.8.x
 # 0x00000009 - 1.9.x
@@ -45,10 +47,11 @@
 # 0x0000000e - 1.20.x
 # 0x0000000e - 1.21.x
 # 0x0000000f - 1.22.x
-# 0x0000000f - 1.23.x
-C_API_VERSION = 0x0000000f
+# 0x00000010 - 1.23.x
+# 0x00000010 - 1.24.x
+C_API_VERSION = 0x00000010
 
-class MismatchCAPIWarning(Warning):
+class MismatchCAPIError(ValueError):
     pass
 
 
@@ -84,14 +87,13 @@
     # To compute the checksum of the current API, use numpy/core/cversions.py
     if not curapi_hash == api_hash:
         msg = ("API mismatch detected, the C API version "
-               "numbers have to be updated. Current C api version is %d, "
-               "with checksum %s, but recorded checksum for C API version %d "
-               "in core/codegen_dir/cversions.txt is %s. If functions were "
-               "added in the C API, you have to update C_API_VERSION in %s."
+               "numbers have to be updated. Current C api version is "
+               f"{apiversion}, with checksum {curapi_hash}, but recorded "
+               f"checksum in core/codegen_dir/cversions.txt is {api_hash}. If "
+               "functions were added in the C API, you have to update "
+               f"C_API_VERSION in {__file__}."
                )
-        warnings.warn(msg % (apiversion, curapi_hash, apiversion, api_hash,
-                             __file__),
-                      MismatchCAPIWarning, stacklevel=2)
+        raise MismatchCAPIError(msg)
 
 
 FUNC_CALL_ARGS = {}
diff --git a/numpy/core/src/multiarray/alloc.c b/numpy/core/src/multiarray/alloc.c
index 6f18054..8b765aa 100644
--- a/numpy/core/src/multiarray/alloc.c
+++ b/numpy/core/src/multiarray/alloc.c
@@ -424,10 +424,7 @@
 };
 /* singleton capsule of the default handler */
 PyObject *PyDataMem_DefaultHandler;
-
-#if (!defined(PYPY_VERSION_NUM) || PYPY_VERSION_NUM >= 0x07030600)
 PyObject *current_handler;
-#endif
 
 int uo_index=0;   /* user_override index */
 
@@ -539,7 +536,6 @@
 PyDataMem_SetHandler(PyObject *handler)
 {
     PyObject *old_handler;
-#if (!defined(PYPY_VERSION_NUM) || PYPY_VERSION_NUM >= 0x07030600)
     PyObject *token;
     if (PyContextVar_Get(current_handler, NULL, &old_handler)) {
         return NULL;
@@ -554,27 +550,6 @@
     }
     Py_DECREF(token);
     return old_handler;
-#else
-    PyObject *p;
-    p = PyThreadState_GetDict();
-    if (p == NULL) {
-        return NULL;
-    }
-    old_handler = PyDict_GetItemString(p, "current_allocator");
-    if (old_handler == NULL) {
-        old_handler = PyDataMem_DefaultHandler
-    }
-    Py_INCREF(old_handler);
-    if (handler == NULL) {
-        handler = PyDataMem_DefaultHandler;
-    }
-    const int error = PyDict_SetItemString(p, "current_allocator", handler);
-    if (error) {
-        Py_DECREF(old_handler);
-        return NULL;
-    }
-    return old_handler;
-#endif
 }
 
 /*NUMPY_API
@@ -585,28 +560,10 @@
 PyDataMem_GetHandler()
 {
     PyObject *handler;
-#if (!defined(PYPY_VERSION_NUM) || PYPY_VERSION_NUM >= 0x07030600)
     if (PyContextVar_Get(current_handler, NULL, &handler)) {
         return NULL;
     }
     return handler;
-#else
-    PyObject *p = PyThreadState_GetDict();
-    if (p == NULL) {
-        return NULL;
-    }
-    handler = PyDict_GetItem(p, npy_ma_str_current_allocator);
-    if (handler == NULL) {
-        handler = PyCapsule_New(&default_handler, "mem_handler", NULL);
-        if (handler == NULL) {
-            return NULL;
-        }
-    }
-    else {
-        Py_INCREF(handler);
-    }
-    return handler;
-#endif
 }
 
 NPY_NO_EXPORT PyObject *
diff --git a/numpy/core/src/multiarray/alloc.h b/numpy/core/src/multiarray/alloc.h
index e82f2d9..0f5a752 100644
--- a/numpy/core/src/multiarray/alloc.h
+++ b/numpy/core/src/multiarray/alloc.h
@@ -44,9 +44,7 @@
 }
 
 extern PyDataMem_Handler default_handler;
-#if (!defined(PYPY_VERSION_NUM) || PYPY_VERSION_NUM >= 0x07030600)
 extern PyObject *current_handler; /* PyContextVar/PyCapsule */
-#endif
 
 NPY_NO_EXPORT PyObject *
 get_handler_name(PyObject *NPY_UNUSED(self), PyObject *obj);
diff --git a/numpy/core/src/multiarray/compiled_base.c b/numpy/core/src/multiarray/compiled_base.c
index 5853e06..d898a35 100644
--- a/numpy/core/src/multiarray/compiled_base.c
+++ b/numpy/core/src/multiarray/compiled_base.c
@@ -1393,11 +1393,7 @@
 {
     PyObject *obj;
     PyObject *str;
-    #if !defined(PYPY_VERSION_NUM) || PYPY_VERSION_NUM > 0x07030300
     const char *docstr;
-    #else
-    char *docstr;
-    #endif
     static char *msg = "already has a different docstring";
 
     /* Don't add docstrings */
diff --git a/numpy/core/src/multiarray/multiarraymodule.c b/numpy/core/src/multiarray/multiarraymodule.c
index ce47276..5209d69 100644
--- a/numpy/core/src/multiarray/multiarraymodule.c
+++ b/numpy/core/src/multiarray/multiarraymodule.c
@@ -4997,16 +4997,15 @@
     if (PyDataMem_DefaultHandler == NULL) {
         goto err;
     }
-#if (!defined(PYPY_VERSION_NUM) || PYPY_VERSION_NUM >= 0x07030600)
     /*
      * Initialize the context-local current handler
      * with the default PyDataMem_Handler capsule.
-    */
+     */
     current_handler = PyContextVar_New("current_allocator", PyDataMem_DefaultHandler);
     if (current_handler == NULL) {
         goto err;
     }
-#endif
+
     return m;
 
  err:
diff --git a/numpy/core/src/multiarray/typeinfo.c b/numpy/core/src/multiarray/typeinfo.c
index 8cf6bc1..18179f7 100644
--- a/numpy/core/src/multiarray/typeinfo.c
+++ b/numpy/core/src/multiarray/typeinfo.c
@@ -9,12 +9,6 @@
 #include "npy_pycompat.h"
 #include "typeinfo.h"
 
-#if (defined(PYPY_VERSION_NUM) && (PYPY_VERSION_NUM <= 0x07030000))
-/* PyPy issue 3160 */
-#include <structseq.h>
-#endif
-
-
 
 static PyTypeObject PyArray_typeinfoType;
 static PyTypeObject PyArray_typeinforangedType;
@@ -99,17 +93,6 @@
     return entry;
 }
 
-/* Python version needed for older PyPy */
-#if (defined(PYPY_VERSION_NUM) && (PYPY_VERSION_NUM < 0x07020000))
-    static int
-    PyStructSequence_InitType2(PyTypeObject *type, PyStructSequence_Desc *desc) {
-        PyStructSequence_InitType(type, desc);
-        if (PyErr_Occurred()) {
-            return -1;
-        }
-        return 0;
-    }
-#endif
 
 NPY_NO_EXPORT int
 typeinfo_init_structsequences(PyObject *multiarray_dict)
diff --git a/numpy/typing/tests/test_generic_alias.py b/numpy/typing/tests/test_generic_alias.py
index ae55ef4..093e121 100644
--- a/numpy/typing/tests/test_generic_alias.py
+++ b/numpy/typing/tests/test_generic_alias.py
@@ -10,6 +10,7 @@
 import pytest
 import numpy as np
 from numpy._typing._generic_alias import _GenericAlias
+from typing_extensions import Unpack
 
 ScalarType = TypeVar("ScalarType", bound=np.generic, covariant=True)
 T1 = TypeVar("T1")
@@ -55,8 +56,6 @@
         ("__origin__", lambda n: n.__origin__),
         ("__args__", lambda n: n.__args__),
         ("__parameters__", lambda n: n.__parameters__),
-        ("__reduce__", lambda n: n.__reduce__()[1:]),
-        ("__reduce_ex__", lambda n: n.__reduce_ex__(1)[1:]),
         ("__mro_entries__", lambda n: n.__mro_entries__([object])),
         ("__hash__", lambda n: hash(n)),
         ("__repr__", lambda n: repr(n)),
@@ -66,7 +65,6 @@
         ("__getitem__", lambda n: n[Union[T1, T2]][np.float32, np.float64]),
         ("__eq__", lambda n: n == n),
         ("__ne__", lambda n: n != np.ndarray),
-        ("__dir__", lambda n: dir(n)),
         ("__call__", lambda n: n((1,), np.int64, BUFFER)),
         ("__call__", lambda n: n(shape=(1,), dtype=np.int64, buffer=BUFFER)),
         ("subclassing", lambda n: _get_subclass_mro(n)),
@@ -100,6 +98,45 @@
             value_ref = func(NDArray_ref)
             assert value == value_ref
 
+    def test_dir(self) -> None:
+        value = dir(NDArray)
+        if sys.version_info < (3, 9):
+            return
+
+        # A number attributes only exist in `types.GenericAlias` in >= 3.11
+        if sys.version_info < (3, 11, 0, "beta", 3):
+            value.remove("__typing_unpacked_tuple_args__")
+        if sys.version_info < (3, 11, 0, "beta", 1):
+            value.remove("__unpacked__")
+        assert value == dir(NDArray_ref)
+
+    @pytest.mark.parametrize("name,func,dev_version", [
+        ("__iter__", lambda n: len(list(n)), ("beta", 1)),
+        ("__iter__", lambda n: next(iter(n)), ("beta", 1)),
+        ("__unpacked__", lambda n: n.__unpacked__, ("beta", 1)),
+        ("Unpack", lambda n: Unpack[n], ("beta", 1)),
+
+        # The right operand should now have `__unpacked__ = True`,
+        # and they are thus now longer equivalent
+        ("__ne__", lambda n: n != next(iter(n)), ("beta", 1)),
+
+        # >= beta3 stuff
+        ("__typing_unpacked_tuple_args__",
+         lambda n: n.__typing_unpacked_tuple_args__, ("beta", 3)),
+    ])
+    def test_py311_features(
+        self,
+        name: str,
+        func: FuncType,
+        dev_version: tuple[str, int],
+    ) -> None:
+        """Test Python 3.11 features."""
+        value = func(NDArray)
+
+        if sys.version_info >= (3, 11, 0, *dev_version):
+            value_ref = func(NDArray_ref)
+            assert value == value_ref
+
     def test_weakref(self) -> None:
         """Test ``__weakref__``."""
         value = weakref.ref(NDArray)()
diff --git a/test_requirements.txt b/test_requirements.txt
index ff39dbc..c5fec8c 100644
--- a/test_requirements.txt
+++ b/test_requirements.txt
@@ -11,3 +11,4 @@
 # - Mypy relies on C API features not present in PyPy
 # NOTE: Keep mypy in sync with environment.yml
 mypy==0.950; platform_python_implementation != "PyPy"
+typing_extensions>=4.2.0