From 3c4594919affbc572d36aa7baaa97cdccf822d34 Mon Sep 17 00:00:00 2001 From: Sebastian Berg Date: Wed, 6 Mar 2024 14:54:43 +0100 Subject: [PATCH 01/25] API: Make `numpy.h` compatible with both NumPy 1.x and 2.x --- include/pybind11/numpy.h | 91 ++++++++++++++++++++++++++++++++-------- 1 file changed, 73 insertions(+), 18 deletions(-) diff --git a/include/pybind11/numpy.h b/include/pybind11/numpy.h index 8551aa2648..6058d81d1c 100644 --- a/include/pybind11/numpy.h +++ b/include/pybind11/numpy.h @@ -54,6 +54,18 @@ template struct npy_format_descriptor; struct PyArrayDescr_Proxy { + PyObject_HEAD + PyObject *typeobj; + char kind; + char type; + char byteorder; + char _former_flags; + int type_num; + /* Additional fields are NumPy version specific. */ +}; + +/* NumPy 1 proxy (always includes legacy fields) */ +struct PyArrayDescr1_Proxy { PyObject_HEAD PyObject *typeobj; char kind; @@ -68,6 +80,27 @@ struct PyArrayDescr_Proxy { PyObject *names; }; +/* NumPy 2 proxy, including legacy fields */ +struct PyArrayDescr2_Proxy { + PyObject_HEAD + PyObject *typeobj; + char kind; + char type; + char byteorder; + char _former_flags; + int type_num; + std::uint64_t flags; + ssize_t elsize; + ssize_t alignment; + PyObject *metadata; + Py_hash_t hash; + void *reserved_null[2]; + /* The following fields only exist if 0 < type_num < 2000 */ + struct _arr_descr *subarray; + PyObject *fields; + PyObject *names; +}; + struct PyArray_Proxy { PyObject_HEAD char *data; @@ -203,6 +236,8 @@ struct npy_api { NPY_ULONG_, NPY_ULONGLONG_, NPY_UINT_), }; + unsigned int PyArray_RUNTIME_VERSION_; + struct PyArray_Dims { Py_intptr_t *ptr; int len; @@ -241,14 +276,6 @@ struct npy_api { PyObject *(*PyArray_FromAny_)(PyObject *, PyObject *, int, int, int, PyObject *); int (*PyArray_DescrConverter_)(PyObject *, PyObject **); bool (*PyArray_EquivTypes_)(PyObject *, PyObject *); - int (*PyArray_GetArrayParamsFromObject_)(PyObject *, - PyObject *, - unsigned char, - PyObject **, - int *, - Py_intptr_t *, - PyObject **, - PyObject *); PyObject *(*PyArray_Squeeze_)(PyObject *); // Unused. Not removed because that affects ABI of the class. int (*PyArray_SetBaseObject_)(PyObject *, PyObject *); @@ -275,7 +302,6 @@ struct npy_api { API_PyArray_View = 137, API_PyArray_DescrConverter = 174, API_PyArray_EquivTypes = 182, - API_PyArray_GetArrayParamsFromObject = 278, API_PyArray_SetBaseObject = 282 }; @@ -290,7 +316,8 @@ struct npy_api { npy_api api; #define DECL_NPY_API(Func) api.Func##_ = (decltype(api.Func##_)) api_ptr[API_##Func]; DECL_NPY_API(PyArray_GetNDArrayCFeatureVersion); - if (api.PyArray_GetNDArrayCFeatureVersion_() < 0x7) { + api.PyArray_RUNTIME_VERSION_ = api.PyArray_GetNDArrayCFeatureVersion_(); + if (api.PyArray_RUNTIME_VERSION_ < 0x7) { pybind11_fail("pybind11 numpy support requires numpy >= 1.7.0"); } DECL_NPY_API(PyArray_Type); @@ -309,7 +336,6 @@ struct npy_api { DECL_NPY_API(PyArray_View); DECL_NPY_API(PyArray_DescrConverter); DECL_NPY_API(PyArray_EquivTypes); - DECL_NPY_API(PyArray_GetArrayParamsFromObject); DECL_NPY_API(PyArray_SetBaseObject); #undef DECL_NPY_API @@ -331,6 +357,14 @@ inline const PyArrayDescr_Proxy *array_descriptor_proxy(const PyObject *ptr) { return reinterpret_cast(ptr); } +inline const PyArrayDescr1_Proxy *array_descriptor1_proxy(const PyObject *ptr) { + return reinterpret_cast(ptr); +} + +inline const PyArrayDescr2_Proxy *array_descriptor2_proxy(const PyObject *ptr) { + return reinterpret_cast(ptr); +} + inline bool check_flags(const void *ptr, int flag) { return (flag == (array_proxy(ptr)->flags & flag)); } @@ -610,10 +644,23 @@ class dtype : public object { } /// Size of the data type in bytes. - ssize_t itemsize() const { return detail::array_descriptor_proxy(m_ptr)->elsize; } + ssize_t itemsize() const { + if (detail::npy_api::get().PyArray_RUNTIME_VERSION_ < 0x12) { + return detail::array_descriptor1_proxy(m_ptr)->elsize; + } + return detail::array_descriptor2_proxy(m_ptr)->elsize; + } /// Returns true for structured data types. - bool has_fields() const { return detail::array_descriptor_proxy(m_ptr)->names != nullptr; } + bool has_fields() const { + if (detail::npy_api::get().PyArray_RUNTIME_VERSION_ < 0x12) { + return detail::array_descriptor1_proxy(m_ptr)->names != nullptr; + } + if (num() < 0 || num() > 2000) { + return false; + } + return detail::array_descriptor2_proxy(m_ptr)->names != nullptr; + } /// Single-character code for dtype's kind. /// For example, floating point types are 'f' and integral types are 'i'. @@ -640,10 +687,20 @@ class dtype : public object { char byteorder() const { return detail::array_descriptor_proxy(m_ptr)->byteorder; } /// Alignment of the data type - int alignment() const { return detail::array_descriptor_proxy(m_ptr)->alignment; } + ssize_t alignment() const { + if (detail::npy_api::get().PyArray_RUNTIME_VERSION_ < 0x12) { + return detail::array_descriptor1_proxy(m_ptr)->alignment; + } + return detail::array_descriptor2_proxy(m_ptr)->alignment; + } /// Flags for the array descriptor - char flags() const { return detail::array_descriptor_proxy(m_ptr)->flags; } + std::uint64_t flags() const { + if (detail::npy_api::get().PyArray_RUNTIME_VERSION_ < 0x12) { + return (unsigned char) detail::array_descriptor1_proxy(m_ptr)->flags; + } + return detail::array_descriptor2_proxy(m_ptr)->flags; + } private: static object &_dtype_from_pep3118() { @@ -810,9 +867,7 @@ class array : public buffer { } /// Byte size of a single element - ssize_t itemsize() const { - return detail::array_descriptor_proxy(detail::array_proxy(m_ptr)->descr)->elsize; - } + ssize_t itemsize() const { return dtype().itemsize(); } /// Total number of bytes ssize_t nbytes() const { return size() * itemsize(); } From 9116d6986f08e9d33879e321656e61c65534b315 Mon Sep 17 00:00:00 2001 From: Sebastian Berg Date: Wed, 6 Mar 2024 15:27:26 +0100 Subject: [PATCH 02/25] TST: Update numpy dtype flags test to not covert flags to char --- tests/test_numpy_dtypes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_numpy_dtypes.py b/tests/test_numpy_dtypes.py index d10457eeb2..c86bc3385c 100644 --- a/tests/test_numpy_dtypes.py +++ b/tests/test_numpy_dtypes.py @@ -178,7 +178,7 @@ def test_dtype(simple_dtype): assert m.test_dtype_num() == [np.dtype(ch).num for ch in expected_chars] assert m.test_dtype_byteorder() == [np.dtype(ch).byteorder for ch in expected_chars] assert m.test_dtype_alignment() == [np.dtype(ch).alignment for ch in expected_chars] - assert m.test_dtype_flags() == [chr(np.dtype(ch).flags) for ch in expected_chars] + assert m.test_dtype_flags() == [np.dtype(ch).flags for ch in expected_chars] def test_recarray(simple_dtype, packed_dtype): From ea9ba5fd146d717b0d83f400d9a97e972470411d Mon Sep 17 00:00:00 2001 From: Sebastian Berg Date: Fri, 8 Mar 2024 09:25:24 +0100 Subject: [PATCH 03/25] API: Add `numpy2.h` instead and make `numpy.h` safe This means that users of `numpy.h` cannot be broken, but need to update to `numpy2.h` if they want to compile for NumPy 2. Using Macros simply and didn't bother to try to remove unnecessary code paths. --- CMakeLists.txt | 1 + include/pybind11/eigen/matrix.h | 2 +- include/pybind11/eigen/tensor.h | 2 +- include/pybind11/numpy.h | 70 +++++++++++++++++++----- include/pybind11/numpy2.h | 5 ++ tests/extra_python_package/test_files.py | 1 + 6 files changed, 66 insertions(+), 15 deletions(-) create mode 100644 include/pybind11/numpy2.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 1e75e99eb9..8bafdacd46 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -161,6 +161,7 @@ set(PYBIND11_HEADERS include/pybind11/iostream.h include/pybind11/functional.h include/pybind11/numpy.h + include/pybind11/numpy2.h include/pybind11/operators.h include/pybind11/pybind11.h include/pybind11/pytypes.h diff --git a/include/pybind11/eigen/matrix.h b/include/pybind11/eigen/matrix.h index 8d4342f81b..0554ca4e4f 100644 --- a/include/pybind11/eigen/matrix.h +++ b/include/pybind11/eigen/matrix.h @@ -9,7 +9,7 @@ #pragma once -#include "../numpy.h" +#include "../numpy2.h" #include "common.h" /* HINT: To suppress warnings originating from the Eigen headers, use -isystem. diff --git a/include/pybind11/eigen/tensor.h b/include/pybind11/eigen/tensor.h index 25d12baca1..21e73fa1a0 100644 --- a/include/pybind11/eigen/tensor.h +++ b/include/pybind11/eigen/tensor.h @@ -7,7 +7,7 @@ #pragma once -#include "../numpy.h" +#include "../numpy2.h" #include "common.h" #if defined(__GNUC__) && !defined(__clang__) && !defined(__INTEL_COMPILER) diff --git a/include/pybind11/numpy.h b/include/pybind11/numpy.h index 6058d81d1c..6a11898244 100644 --- a/include/pybind11/numpy.h +++ b/include/pybind11/numpy.h @@ -53,17 +53,6 @@ struct handle_type_name { template struct npy_format_descriptor; -struct PyArrayDescr_Proxy { - PyObject_HEAD - PyObject *typeobj; - char kind; - char type; - char byteorder; - char _former_flags; - int type_num; - /* Additional fields are NumPy version specific. */ -}; - /* NumPy 1 proxy (always includes legacy fields) */ struct PyArrayDescr1_Proxy { PyObject_HEAD @@ -80,6 +69,22 @@ struct PyArrayDescr1_Proxy { PyObject *names; }; +#ifdef PYBIND11_COMPILE_WITH_NUMPY2_SUPPORT +struct PyArrayDescr_Proxy { + PyObject_HEAD + PyObject *typeobj; + char kind; + char type; + char byteorder; + char _former_flags; + int type_num; + /* Additional fields are NumPy version specific. */ +}; +#else +/* NumPy 1.x only, we can expose all fields */ +typedef PyArrayDescr1_Proxy PyArrayDescr_Proxy; +#endif + /* NumPy 2 proxy, including legacy fields */ struct PyArrayDescr2_Proxy { PyObject_HEAD @@ -164,6 +169,13 @@ PYBIND11_NOINLINE module_ import_numpy_core_submodule(const char *submodule_name object numpy_version = numpy_lib.attr("NumpyVersion")(version_string); int major_version = numpy_version.attr("major").cast(); +#ifndef PYBIND11_COMPILE_WITH_NUMPY2_SUPPORT + if (major_version >= 2) { + throw std::runtime_error("module compiled without NumPy 2 support. Please modify the " + "`pybind11/numpy.h` include to `pybind11/numpy2.h` and recompile " + "(this remains NumPy 1.x compatible but has minor changes)."); + } +#endif /* `numpy.core` was renamed to `numpy._core` in NumPy 2.0 as it officially became a private module. */ std::string numpy_core_path = major_version >= 2 ? "numpy._core" : "numpy.core"; @@ -276,6 +288,16 @@ struct npy_api { PyObject *(*PyArray_FromAny_)(PyObject *, PyObject *, int, int, int, PyObject *); int (*PyArray_DescrConverter_)(PyObject *, PyObject **); bool (*PyArray_EquivTypes_)(PyObject *, PyObject *); +#ifndef PYBIND11_COMPILE_WITH_NUMPY2_SUPPORT + int (*PyArray_GetArrayParamsFromObject_)(PyObject *, + PyObject *, + unsigned char, + PyObject **, + int *, + Py_intptr_t *, + PyObject **, + PyObject *); +#endif PyObject *(*PyArray_Squeeze_)(PyObject *); // Unused. Not removed because that affects ABI of the class. int (*PyArray_SetBaseObject_)(PyObject *, PyObject *); @@ -302,6 +324,9 @@ struct npy_api { API_PyArray_View = 137, API_PyArray_DescrConverter = 174, API_PyArray_EquivTypes = 182, +#ifndef PYBIND11_COMPILE_WITH_NUMPY2_SUPPORT + API_PyArray_GetArrayParamsFromObject = 278, +#endif API_PyArray_SetBaseObject = 282 }; @@ -336,6 +361,9 @@ struct npy_api { DECL_NPY_API(PyArray_View); DECL_NPY_API(PyArray_DescrConverter); DECL_NPY_API(PyArray_EquivTypes); +#ifndef PYBIND11_COMPILE_WITH_NUMPY2_SUPPORT + DECL_NPY_API(PyArray_GetArrayParamsFromObject); +#endif DECL_NPY_API(PyArray_SetBaseObject); #undef DECL_NPY_API @@ -644,14 +672,21 @@ class dtype : public object { } /// Size of the data type in bytes. +#ifndef PYBIND11_COMPILE_WITH_NUMPY2_SUPPORT + ssize_t itemsize() const { return detail::array_descriptor_proxy(m_ptr)->elsize; } +#else ssize_t itemsize() const { if (detail::npy_api::get().PyArray_RUNTIME_VERSION_ < 0x12) { return detail::array_descriptor1_proxy(m_ptr)->elsize; } return detail::array_descriptor2_proxy(m_ptr)->elsize; } +#endif /// Returns true for structured data types. +#ifndef PYBIND11_COMPILE_WITH_NUMPY2_SUPPORT + bool has_fields() const { return detail::array_descriptor_proxy(m_ptr)->names != nullptr; } +#else bool has_fields() const { if (detail::npy_api::get().PyArray_RUNTIME_VERSION_ < 0x12) { return detail::array_descriptor1_proxy(m_ptr)->names != nullptr; @@ -661,6 +696,7 @@ class dtype : public object { } return detail::array_descriptor2_proxy(m_ptr)->names != nullptr; } +#endif /// Single-character code for dtype's kind. /// For example, floating point types are 'f' and integral types are 'i'. @@ -686,21 +722,29 @@ class dtype : public object { /// Single character for byteorder char byteorder() const { return detail::array_descriptor_proxy(m_ptr)->byteorder; } - /// Alignment of the data type +/// Alignment of the data type +#ifndef PYBIND11_COMPILE_WITH_NUMPY2_SUPPORT + int alignment() const { return detail::array_descriptor_proxy(m_ptr)->alignment; } +#else ssize_t alignment() const { if (detail::npy_api::get().PyArray_RUNTIME_VERSION_ < 0x12) { return detail::array_descriptor1_proxy(m_ptr)->alignment; } return detail::array_descriptor2_proxy(m_ptr)->alignment; } +#endif - /// Flags for the array descriptor +/// Flags for the array descriptor +#ifndef PYBIND11_COMPILE_WITH_NUMPY2_SUPPORT + char flags() const { return detail::array_descriptor_proxy(m_ptr)->flags; } +#else std::uint64_t flags() const { if (detail::npy_api::get().PyArray_RUNTIME_VERSION_ < 0x12) { return (unsigned char) detail::array_descriptor1_proxy(m_ptr)->flags; } return detail::array_descriptor2_proxy(m_ptr)->flags; } +#endif private: static object &_dtype_from_pep3118() { diff --git a/include/pybind11/numpy2.h b/include/pybind11/numpy2.h new file mode 100644 index 0000000000..35039f1b3d --- /dev/null +++ b/include/pybind11/numpy2.h @@ -0,0 +1,5 @@ +#pragma once + +#define PYBIND11_COMPILE_WITH_NUMPY2_SUPPORT +#include "numpy.h" +#undef PYBIND11_COMPILE_WITH_NUMPY2_SUPPORT diff --git a/tests/extra_python_package/test_files.py b/tests/extra_python_package/test_files.py index 344e70d5db..262fadc8de 100644 --- a/tests/extra_python_package/test_files.py +++ b/tests/extra_python_package/test_files.py @@ -38,6 +38,7 @@ "include/pybind11/gil_safe_call_once.h", "include/pybind11/iostream.h", "include/pybind11/numpy.h", + "include/pybind11/numpy2.h", "include/pybind11/operators.h", "include/pybind11/options.h", "include/pybind11/pybind11.h", From 4555280de89be75c122e1df2942dcca67aac799e Mon Sep 17 00:00:00 2001 From: Sebastian Berg Date: Fri, 8 Mar 2024 13:58:13 +0100 Subject: [PATCH 04/25] API: Rather than `numpy2.h` use a define for the user. --- CMakeLists.txt | 1 - include/pybind11/eigen/matrix.h | 2 +- include/pybind11/eigen/tensor.h | 2 +- include/pybind11/numpy.h | 24 ++++++++++++------------ include/pybind11/numpy2.h | 5 ----- tests/extra_python_package/test_files.py | 1 - 6 files changed, 14 insertions(+), 21 deletions(-) delete mode 100644 include/pybind11/numpy2.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 8bafdacd46..1e75e99eb9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -161,7 +161,6 @@ set(PYBIND11_HEADERS include/pybind11/iostream.h include/pybind11/functional.h include/pybind11/numpy.h - include/pybind11/numpy2.h include/pybind11/operators.h include/pybind11/pybind11.h include/pybind11/pytypes.h diff --git a/include/pybind11/eigen/matrix.h b/include/pybind11/eigen/matrix.h index 0554ca4e4f..8d4342f81b 100644 --- a/include/pybind11/eigen/matrix.h +++ b/include/pybind11/eigen/matrix.h @@ -9,7 +9,7 @@ #pragma once -#include "../numpy2.h" +#include "../numpy.h" #include "common.h" /* HINT: To suppress warnings originating from the Eigen headers, use -isystem. diff --git a/include/pybind11/eigen/tensor.h b/include/pybind11/eigen/tensor.h index 21e73fa1a0..25d12baca1 100644 --- a/include/pybind11/eigen/tensor.h +++ b/include/pybind11/eigen/tensor.h @@ -7,7 +7,7 @@ #pragma once -#include "../numpy2.h" +#include "../numpy.h" #include "common.h" #if defined(__GNUC__) && !defined(__clang__) && !defined(__INTEL_COMPILER) diff --git a/include/pybind11/numpy.h b/include/pybind11/numpy.h index 6a11898244..859cf3f3ab 100644 --- a/include/pybind11/numpy.h +++ b/include/pybind11/numpy.h @@ -69,7 +69,7 @@ struct PyArrayDescr1_Proxy { PyObject *names; }; -#ifdef PYBIND11_COMPILE_WITH_NUMPY2_SUPPORT +#ifdef PYBIND11_NUMPY2_SUPPORT struct PyArrayDescr_Proxy { PyObject_HEAD PyObject *typeobj; @@ -169,11 +169,11 @@ PYBIND11_NOINLINE module_ import_numpy_core_submodule(const char *submodule_name object numpy_version = numpy_lib.attr("NumpyVersion")(version_string); int major_version = numpy_version.attr("major").cast(); -#ifndef PYBIND11_COMPILE_WITH_NUMPY2_SUPPORT +#ifndef PYBIND11_NUMPY2_SUPPORT if (major_version >= 2) { - throw std::runtime_error("module compiled without NumPy 2 support. Please modify the " - "`pybind11/numpy.h` include to `pybind11/numpy2.h` and recompile " - "(this remains NumPy 1.x compatible but has minor changes)."); + throw std::runtime_error("module compiled without NumPy 2 support. Please " + "define PYBIND11_NUMPY2_SUPPORT before the `numpy2.h` " + "to enable it."); } #endif /* `numpy.core` was renamed to `numpy._core` in NumPy 2.0 as it officially @@ -288,7 +288,7 @@ struct npy_api { PyObject *(*PyArray_FromAny_)(PyObject *, PyObject *, int, int, int, PyObject *); int (*PyArray_DescrConverter_)(PyObject *, PyObject **); bool (*PyArray_EquivTypes_)(PyObject *, PyObject *); -#ifndef PYBIND11_COMPILE_WITH_NUMPY2_SUPPORT +#ifndef PYBIND11_NUMPY2_SUPPORT int (*PyArray_GetArrayParamsFromObject_)(PyObject *, PyObject *, unsigned char, @@ -324,7 +324,7 @@ struct npy_api { API_PyArray_View = 137, API_PyArray_DescrConverter = 174, API_PyArray_EquivTypes = 182, -#ifndef PYBIND11_COMPILE_WITH_NUMPY2_SUPPORT +#ifndef PYBIND11_NUMPY2_SUPPORT API_PyArray_GetArrayParamsFromObject = 278, #endif API_PyArray_SetBaseObject = 282 @@ -361,7 +361,7 @@ struct npy_api { DECL_NPY_API(PyArray_View); DECL_NPY_API(PyArray_DescrConverter); DECL_NPY_API(PyArray_EquivTypes); -#ifndef PYBIND11_COMPILE_WITH_NUMPY2_SUPPORT +#ifndef PYBIND11_NUMPY2_SUPPORT DECL_NPY_API(PyArray_GetArrayParamsFromObject); #endif DECL_NPY_API(PyArray_SetBaseObject); @@ -672,7 +672,7 @@ class dtype : public object { } /// Size of the data type in bytes. -#ifndef PYBIND11_COMPILE_WITH_NUMPY2_SUPPORT +#ifndef PYBIND11_NUMPY2_SUPPORT ssize_t itemsize() const { return detail::array_descriptor_proxy(m_ptr)->elsize; } #else ssize_t itemsize() const { @@ -684,7 +684,7 @@ class dtype : public object { #endif /// Returns true for structured data types. -#ifndef PYBIND11_COMPILE_WITH_NUMPY2_SUPPORT +#ifndef PYBIND11_NUMPY2_SUPPORT bool has_fields() const { return detail::array_descriptor_proxy(m_ptr)->names != nullptr; } #else bool has_fields() const { @@ -723,7 +723,7 @@ class dtype : public object { char byteorder() const { return detail::array_descriptor_proxy(m_ptr)->byteorder; } /// Alignment of the data type -#ifndef PYBIND11_COMPILE_WITH_NUMPY2_SUPPORT +#ifndef PYBIND11_NUMPY2_SUPPORT int alignment() const { return detail::array_descriptor_proxy(m_ptr)->alignment; } #else ssize_t alignment() const { @@ -735,7 +735,7 @@ class dtype : public object { #endif /// Flags for the array descriptor -#ifndef PYBIND11_COMPILE_WITH_NUMPY2_SUPPORT +#ifndef PYBIND11_NUMPY2_SUPPORT char flags() const { return detail::array_descriptor_proxy(m_ptr)->flags; } #else std::uint64_t flags() const { diff --git a/include/pybind11/numpy2.h b/include/pybind11/numpy2.h deleted file mode 100644 index 35039f1b3d..0000000000 --- a/include/pybind11/numpy2.h +++ /dev/null @@ -1,5 +0,0 @@ -#pragma once - -#define PYBIND11_COMPILE_WITH_NUMPY2_SUPPORT -#include "numpy.h" -#undef PYBIND11_COMPILE_WITH_NUMPY2_SUPPORT diff --git a/tests/extra_python_package/test_files.py b/tests/extra_python_package/test_files.py index 262fadc8de..344e70d5db 100644 --- a/tests/extra_python_package/test_files.py +++ b/tests/extra_python_package/test_files.py @@ -38,7 +38,6 @@ "include/pybind11/gil_safe_call_once.h", "include/pybind11/iostream.h", "include/pybind11/numpy.h", - "include/pybind11/numpy2.h", "include/pybind11/operators.h", "include/pybind11/options.h", "include/pybind11/pybind11.h", From 0d585dbf7ddfc0f9762956b5d49205ad1acc14e9 Mon Sep 17 00:00:00 2001 From: Sebastian Berg Date: Mon, 11 Mar 2024 12:41:47 +0100 Subject: [PATCH 05/25] Thread `PYBIND11_NUMPY2_SUPPORT` through things and try to adept test matrix --- .github/workflows/ci.yml | 10 +++++++++- CMakeLists.txt | 4 ++++ include/pybind11/numpy.h | 6 +++--- tests/conftest.py | 1 + tests/pybind11_tests.cpp | 6 ++++++ tests/test_numpy_dtypes.py | 8 +++++++- 6 files changed, 30 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cf03eebfdf..49629e80f4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -108,12 +108,14 @@ jobs: run: python -m pip install pytest-github-actions-annotate-failures # First build - C++11 mode and inplace - # More-or-less randomly adding -DPYBIND11_SIMPLE_GIL_MANAGEMENT=ON here. + # More-or-less randomly adding -DPYBIND11_SIMPLE_GIL_MANAGEMENT=ON here + # (same for PYBIND11_NUMPY2_SUPPORT). - name: Configure C++11 ${{ matrix.args }} run: > cmake -S . -B . -DPYBIND11_WERROR=ON -DPYBIND11_SIMPLE_GIL_MANAGEMENT=ON + -DPYBIND11_NUMPY2_SUPPORT=ON -DDOWNLOAD_CATCH=ON -DDOWNLOAD_EIGEN=ON -DCMAKE_CXX_STANDARD=11 @@ -138,11 +140,13 @@ jobs: # Second build - C++17 mode and in a build directory # More-or-less randomly adding -DPYBIND11_SIMPLE_GIL_MANAGEMENT=OFF here. + # (same for PYBIND11_NUMPY2_SUPPORT, but requires a NumPy 1.x at runtime). - name: Configure C++17 run: > cmake -S . -B build2 -DPYBIND11_WERROR=ON -DPYBIND11_SIMPLE_GIL_MANAGEMENT=OFF + -DPYBIND11_NUMPY2_SUPPORT=OFF -DDOWNLOAD_CATCH=ON -DDOWNLOAD_EIGEN=ON -DCMAKE_CXX_STANDARD=17 @@ -895,8 +899,10 @@ jobs: python-version: ${{ matrix.python }} - name: Prepare env + # Ensure use of NumPy 2 (via NumPy nightlies but can be changed soon) run: | python3 -m pip install -r tests/requirements.txt + python3 -m pip install --upgrade --pre -i https://pypi.anaconda.org/scientific-python-nightly-wheels/simple numpy - name: Update CMake uses: jwlawson/actions-setup-cmake@v2.0 @@ -905,6 +911,7 @@ jobs: run: > cmake -S . -B build -DPYBIND11_WERROR=ON + -DPYBIND11_NUMPY2_SUPPORT=ON -DDOWNLOAD_CATCH=ON -DDOWNLOAD_EIGEN=ON -DCMAKE_CXX_STANDARD=20 @@ -925,6 +932,7 @@ jobs: run: > cmake -S . -B build_partial -DPYBIND11_WERROR=ON + -DPYBIND11_NUMPY2_SUPPORT=ON -DDOWNLOAD_CATCH=ON -DDOWNLOAD_EIGEN=ON -DCMAKE_CXX_STANDARD=20 diff --git a/CMakeLists.txt b/CMakeLists.txt index 1e75e99eb9..df280d11eb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -109,6 +109,7 @@ option(PYBIND11_TEST "Build pybind11 test suite?" ${PYBIND11_MASTER_PROJECT}) option(PYBIND11_NOPYTHON "Disable search for Python" OFF) option(PYBIND11_SIMPLE_GIL_MANAGEMENT "Use simpler GIL management logic that does not support disassociation" OFF) +option(PYBIND11_NUMPY2_SUPPORT "Enable compile in a NumPy 2 and 1 compatible way" OFF) set(PYBIND11_INTERNALS_VERSION "" CACHE STRING "Override the ABI version, may be used to enable the unstable ABI.") @@ -116,6 +117,9 @@ set(PYBIND11_INTERNALS_VERSION if(PYBIND11_SIMPLE_GIL_MANAGEMENT) add_compile_definitions(PYBIND11_SIMPLE_GIL_MANAGEMENT) endif() +if(PYBIND11_NUMPY2_SUPPORT) + add_compile_definitions(PYBIND11_NUMPY2_SUPPORT) +endif() cmake_dependent_option( USE_PYTHON_INCLUDE_DIR diff --git a/include/pybind11/numpy.h b/include/pybind11/numpy.h index 859cf3f3ab..3e8b990c5c 100644 --- a/include/pybind11/numpy.h +++ b/include/pybind11/numpy.h @@ -171,9 +171,9 @@ PYBIND11_NOINLINE module_ import_numpy_core_submodule(const char *submodule_name #ifndef PYBIND11_NUMPY2_SUPPORT if (major_version >= 2) { - throw std::runtime_error("module compiled without NumPy 2 support. Please " - "define PYBIND11_NUMPY2_SUPPORT before the `numpy2.h` " - "to enable it."); + throw std::runtime_error("module compiled without NumPy 2 support. Please define " + "PYBIND11_NUMPY2_SUPPORT before including `numpy2.h` " + "or define it during build to enable NumPy 2 support."); } #endif /* `numpy.core` was renamed to `numpy._core` in NumPy 2.0 as it officially diff --git a/tests/conftest.py b/tests/conftest.py index ad5b47b4b3..1e0653901b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -218,4 +218,5 @@ def pytest_report_header(config): f" {pybind11_tests.cpp_std}" f" {pybind11_tests.PYBIND11_INTERNALS_ID}" f" PYBIND11_SIMPLE_GIL_MANAGEMENT={pybind11_tests.PYBIND11_SIMPLE_GIL_MANAGEMENT}" + f" PYBIND11_NUMPY2_SUPPORT={pybind11_tests.PYBIND11_NUMPY2_SUPPORT}" ) diff --git a/tests/pybind11_tests.cpp b/tests/pybind11_tests.cpp index 6240346487..cf8db84a14 100644 --- a/tests/pybind11_tests.cpp +++ b/tests/pybind11_tests.cpp @@ -95,6 +95,12 @@ PYBIND11_MODULE(pybind11_tests, m) { #else false; #endif + m.attr("PYBIND11_NUMPY2_SUPPORT") = +#if defined(PYBIND11_NUMPY2_SUPPORT) + true; +#else + false; +#endif bind_ConstructorStats(m); diff --git a/tests/test_numpy_dtypes.py b/tests/test_numpy_dtypes.py index c86bc3385c..3e379bfda4 100644 --- a/tests/test_numpy_dtypes.py +++ b/tests/test_numpy_dtypes.py @@ -3,6 +3,7 @@ import pytest import env # noqa: F401 +from pybind11_tests import PYBIND11_NUMPY2_SUPPORT from pybind11_tests import numpy_dtypes as m np = pytest.importorskip("numpy") @@ -178,7 +179,12 @@ def test_dtype(simple_dtype): assert m.test_dtype_num() == [np.dtype(ch).num for ch in expected_chars] assert m.test_dtype_byteorder() == [np.dtype(ch).byteorder for ch in expected_chars] assert m.test_dtype_alignment() == [np.dtype(ch).alignment for ch in expected_chars] - assert m.test_dtype_flags() == [np.dtype(ch).flags for ch in expected_chars] + if PYBIND11_NUMPY2_SUPPORT: + assert m.test_dtype_flags() == [np.dtype(ch).flags for ch in expected_chars] + else: + assert m.test_dtype_flags() == [ + chr(np.dtype(ch).flags) for ch in expected_chars + ] def test_recarray(simple_dtype, packed_dtype): From 0a8743672fdfd4bda05eaa243e51093f4b7a7dee Mon Sep 17 00:00:00 2001 From: Sebastian Berg Date: Mon, 11 Mar 2024 14:01:12 +0100 Subject: [PATCH 06/25] Small fixups (shouldn't matter)? --- include/pybind11/numpy.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/include/pybind11/numpy.h b/include/pybind11/numpy.h index 3e8b990c5c..d318d8ede2 100644 --- a/include/pybind11/numpy.h +++ b/include/pybind11/numpy.h @@ -82,7 +82,7 @@ struct PyArrayDescr_Proxy { }; #else /* NumPy 1.x only, we can expose all fields */ -typedef PyArrayDescr1_Proxy PyArrayDescr_Proxy; +using PyArrayDescr_Proxy = PyArrayDescr1_Proxy; #endif /* NumPy 2 proxy, including legacy fields */ @@ -100,8 +100,8 @@ struct PyArrayDescr2_Proxy { PyObject *metadata; Py_hash_t hash; void *reserved_null[2]; - /* The following fields only exist if 0 < type_num < 2000 */ - struct _arr_descr *subarray; + /* The following fields only exist if 0 < type_num < 2056 */ + char *subarray; PyObject *fields; PyObject *names; }; @@ -691,7 +691,7 @@ class dtype : public object { if (detail::npy_api::get().PyArray_RUNTIME_VERSION_ < 0x12) { return detail::array_descriptor1_proxy(m_ptr)->names != nullptr; } - if (num() < 0 || num() > 2000) { + if (num() < 0 || num() > 2056) { return false; } return detail::array_descriptor2_proxy(m_ptr)->names != nullptr; From 25df74c455fd73f248d72bd4ab7f0581a235b9b1 Mon Sep 17 00:00:00 2001 From: Sebastian Berg Date: Mon, 11 Mar 2024 15:02:17 +0100 Subject: [PATCH 07/25] Fixup. Does upgrading scipy help? (it shouldn't?) (Some other small fixup) --- .github/workflows/ci.yml | 2 +- include/pybind11/numpy.h | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 49629e80f4..e81d3c4f77 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -902,7 +902,7 @@ jobs: # Ensure use of NumPy 2 (via NumPy nightlies but can be changed soon) run: | python3 -m pip install -r tests/requirements.txt - python3 -m pip install --upgrade --pre -i https://pypi.anaconda.org/scientific-python-nightly-wheels/simple numpy + python3 -m pip install --upgrade --pre -i https://pypi.anaconda.org/scientific-python-nightly-wheels/simple numpy scipy - name: Update CMake uses: jwlawson/actions-setup-cmake@v2.0 diff --git a/include/pybind11/numpy.h b/include/pybind11/numpy.h index d318d8ede2..de93c34e0d 100644 --- a/include/pybind11/numpy.h +++ b/include/pybind11/numpy.h @@ -32,7 +32,8 @@ /* This will be true on all flat address space platforms and allows us to reduce the whole npy_intp / ssize_t / Py_intptr_t business down to just ssize_t for all size and dimension types (e.g. shape, strides, indexing), instead of inflicting this - upon the library user. */ + upon the library user. + Note that NumPy 2 now uses ssize_t for `npy_intp` to simplify this. */ static_assert(sizeof(::pybind11::ssize_t) == sizeof(Py_intptr_t), "ssize_t != Py_intptr_t"); static_assert(std::is_signed::value, "Py_intptr_t must be signed"); // We now can reinterpret_cast between py::ssize_t and Py_intptr_t (MSVC + PyPy cares) @@ -100,7 +101,7 @@ struct PyArrayDescr2_Proxy { PyObject *metadata; Py_hash_t hash; void *reserved_null[2]; - /* The following fields only exist if 0 < type_num < 2056 */ + /* The following fields only exist if 0 <= type_num < 2056 */ char *subarray; PyObject *fields; PyObject *names; @@ -691,7 +692,7 @@ class dtype : public object { if (detail::npy_api::get().PyArray_RUNTIME_VERSION_ < 0x12) { return detail::array_descriptor1_proxy(m_ptr)->names != nullptr; } - if (num() < 0 || num() > 2056) { + if (num() < 0 || num() >= 2056) { return false; } return detail::array_descriptor2_proxy(m_ptr)->names != nullptr; From f0457a544944e22853f612580e9ea39bdc169a3a Mon Sep 17 00:00:00 2001 From: Sebastian Berg Date: Tue, 12 Mar 2024 11:12:20 +0100 Subject: [PATCH 08/25] Use NumPy 2 nightlies for ubuntu-latest job also --- .github/workflows/ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e81d3c4f77..e27d1d94b4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -663,12 +663,14 @@ jobs: - name: Install dependencies run: | python3 -m pip install cmake -r tests/requirements.txt + python3 -m pip install --upgrade --pre -i https://pypi.anaconda.org/scientific-python-nightly-wheels/simple numpy scipy - name: Configure shell: bash run: > cmake -S . -B build -DCMAKE_BUILD_TYPE=MinSizeRel + -DPYBIND11_NUMPY2_SUPPORT=ON -DPYBIND11_WERROR=ON -DDOWNLOAD_CATCH=ON -DDOWNLOAD_EIGEN=ON From 247d5c30f01053f4d6f0b5d10d8b82f5d7291525 Mon Sep 17 00:00:00 2001 From: Sebastian Berg Date: Tue, 12 Mar 2024 12:09:54 +0100 Subject: [PATCH 09/25] BUG: Fix numpy.bool check --- include/pybind11/cast.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index 8b5beb0ef6..6aec4c5bf1 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -327,8 +327,9 @@ class type_caster { value = false; return true; } - if (convert || (std::strcmp("numpy.bool_", Py_TYPE(src.ptr())->tp_name) == 0)) { - // (allow non-implicit conversion for numpy booleans) + if (convert || (std::strncmp("numpy.bool", Py_TYPE(src.ptr())->tp_name, 10) == 0)) { + // (allow non-implicit conversion for numpy booleans), use strncmp + // since NumPy 1.x had an additional trailing underscore. Py_ssize_t res = -1; if (src.is_none()) { From 5368842620f86d1d3d6a8615875f83e0586a2d8a Mon Sep 17 00:00:00 2001 From: Sebastian Berg Date: Tue, 12 Mar 2024 12:34:56 +0100 Subject: [PATCH 10/25] TST: Fix complexwarning --- tests/test_numpy_array.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/test_numpy_array.py b/tests/test_numpy_array.py index 0697daf3ef..301c3d5240 100644 --- a/tests/test_numpy_array.py +++ b/tests/test_numpy_array.py @@ -536,7 +536,12 @@ def test_format_descriptors_for_floating_point_types(test_func): @pytest.mark.parametrize("contiguity", [None, "C", "F"]) @pytest.mark.parametrize("noconvert", [False, True]) @pytest.mark.filterwarnings( - "ignore:Casting complex values to real discards the imaginary part:numpy.ComplexWarning" + "ignore:Casting complex values to real discards the imaginary part:" + + ( + "numpy.exceptions.ComplexWarning" + if hasattr(np, "exceptions") + else "numpy.ComplexWarning" + ) ) def test_argument_conversions(forcecast, contiguity, noconvert): function_name = "accept_double" From 96157d6f062a34744b0e96b72c2badaf90956dbd Mon Sep 17 00:00:00 2001 From: Sebastian Berg Date: Tue, 12 Mar 2024 12:55:00 +0100 Subject: [PATCH 11/25] BUG: Fix the fact that only the 50 slot is filled with the copy alias (There were 3 functions all doing the same, only this slot survived 2.x) --- include/pybind11/numpy.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/pybind11/numpy.h b/include/pybind11/numpy.h index de93c34e0d..2c782fd087 100644 --- a/include/pybind11/numpy.h +++ b/include/pybind11/numpy.h @@ -316,7 +316,7 @@ struct npy_api { API_PyArray_DescrFromScalar = 57, API_PyArray_FromAny = 69, API_PyArray_Resize = 80, - API_PyArray_CopyInto = 82, + API_PyArray_CopyInto = 50, API_PyArray_NewCopy = 85, API_PyArray_NewFromDescr = 94, API_PyArray_DescrNewFromType = 96, From 6daf11ad04f09d4592d43b3a690f1c8192899611 Mon Sep 17 00:00:00 2001 From: Sebastian Berg Date: Tue, 12 Mar 2024 12:57:23 +0100 Subject: [PATCH 12/25] TST: One more test tweak --- tests/test_numpy_array.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_numpy_array.py b/tests/test_numpy_array.py index 301c3d5240..6f619e8075 100644 --- a/tests/test_numpy_array.py +++ b/tests/test_numpy_array.py @@ -588,7 +588,7 @@ def test_argument_conversions(forcecast, contiguity, noconvert): def test_dtype_refcount_leak(): from sys import getrefcount - dtype = np.dtype(np.float_) + dtype = np.dtype(np.float64) a = np.array([1], dtype=dtype) before = getrefcount(dtype) m.ndim(a) From 5cf73bc9da40b880ae4a5a9a4020d948246e0a62 Mon Sep 17 00:00:00 2001 From: Sebastian Berg Date: Tue, 12 Mar 2024 13:21:53 +0100 Subject: [PATCH 13/25] TST: Use "long" name for long, since it changed on windows --- tests/test_numpy_dtypes.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_numpy_dtypes.cpp b/tests/test_numpy_dtypes.cpp index 053004a22f..33ca8c07f2 100644 --- a/tests/test_numpy_dtypes.cpp +++ b/tests/test_numpy_dtypes.cpp @@ -406,8 +406,8 @@ TEST_SUBMODULE(numpy_dtypes, m) { // test_dtype std::vector dtype_names{ - "byte", "short", "intc", "int_", "longlong", "ubyte", "ushort", - "uintc", "uint", "ulonglong", "half", "single", "double", "longdouble", + "byte", "short", "intc", "long", "longlong", "ubyte", "ushort", + "uintc", "ulong", "ulonglong", "half", "single", "double", "longdouble", "csingle", "cdouble", "clongdouble", "bool_", "datetime64", "timedelta64", "object_"}; m.def("print_dtypes", []() { From a6414fff097069108233989805fd5b570bcc6ed9 Mon Sep 17 00:00:00 2001 From: Sebastian Berg Date: Tue, 12 Mar 2024 13:36:44 +0100 Subject: [PATCH 14/25] TST: Apparently we didn't always have ulong, so just use `L` --- tests/test_numpy_dtypes.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_numpy_dtypes.cpp b/tests/test_numpy_dtypes.cpp index 33ca8c07f2..2141d27248 100644 --- a/tests/test_numpy_dtypes.cpp +++ b/tests/test_numpy_dtypes.cpp @@ -404,10 +404,10 @@ TEST_SUBMODULE(numpy_dtypes, m) { return l; }); - // test_dtype + // test_dtype (ulong was missing, but uint default changed on windows so use L) std::vector dtype_names{ "byte", "short", "intc", "long", "longlong", "ubyte", "ushort", - "uintc", "ulong", "ulonglong", "half", "single", "double", "longdouble", + "uintc", "L", "ulonglong", "half", "single", "double", "longdouble", "csingle", "cdouble", "clongdouble", "bool_", "datetime64", "timedelta64", "object_"}; m.def("print_dtypes", []() { From 79393f5bebf7c809fef74d76c9f0cb04a9d3b277 Mon Sep 17 00:00:00 2001 From: Sebastian Berg Date: Tue, 12 Mar 2024 13:50:47 +0100 Subject: [PATCH 15/25] TST: Enforce dtype='l' for test as default isn't long anymore on windows --- tests/test_eigen_matrix.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/test_eigen_matrix.py b/tests/test_eigen_matrix.py index a486c2f93b..9a2cafc0a6 100644 --- a/tests/test_eigen_matrix.py +++ b/tests/test_eigen_matrix.py @@ -608,7 +608,9 @@ def test_both_ref_mutators(): def test_nocopy_wrapper(): # get_elem requires a column-contiguous matrix reference, but should be # callable with other types of matrix (via copying): - int_matrix_colmajor = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]], order="F") + int_matrix_colmajor = np.array( + [[1, 2, 3], [4, 5, 6], [7, 8, 9]], dtype="l", order="F" + ) dbl_matrix_colmajor = np.array( int_matrix_colmajor, dtype="double", order="F", copy=True ) From 2514af55bf5dcb6149ef3f8a21d7c6d7502b885b Mon Sep 17 00:00:00 2001 From: Sebastian Berg Date: Sat, 16 Mar 2024 15:34:18 +0100 Subject: [PATCH 16/25] Rename macro and invert logic to PYBIND11_NUMPY_1_ONLY --- .github/workflows/ci.yml | 11 ++++------- CMakeLists.txt | 7 ++++--- include/pybind11/numpy.h | 22 +++++++++++----------- tests/conftest.py | 2 +- tests/pybind11_tests.cpp | 4 ++-- tests/test_numpy_dtypes.py | 4 ++-- 6 files changed, 24 insertions(+), 26 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e27d1d94b4..c7ba06bc23 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -109,13 +109,13 @@ jobs: # First build - C++11 mode and inplace # More-or-less randomly adding -DPYBIND11_SIMPLE_GIL_MANAGEMENT=ON here - # (same for PYBIND11_NUMPY2_SUPPORT). + # (same for PYBIND11_NUMPY_1_ONLY, but requires a NumPy 1.x at runtime). - name: Configure C++11 ${{ matrix.args }} run: > cmake -S . -B . -DPYBIND11_WERROR=ON -DPYBIND11_SIMPLE_GIL_MANAGEMENT=ON - -DPYBIND11_NUMPY2_SUPPORT=ON + -DPYBIND11_NUMPY_1_ONLY=ON -DDOWNLOAD_CATCH=ON -DDOWNLOAD_EIGEN=ON -DCMAKE_CXX_STANDARD=11 @@ -140,13 +140,13 @@ jobs: # Second build - C++17 mode and in a build directory # More-or-less randomly adding -DPYBIND11_SIMPLE_GIL_MANAGEMENT=OFF here. - # (same for PYBIND11_NUMPY2_SUPPORT, but requires a NumPy 1.x at runtime). + # (same for PYBIND11_NUMPY_1_ONLY, but requires a NumPy 1.x at runtime). - name: Configure C++17 run: > cmake -S . -B build2 -DPYBIND11_WERROR=ON -DPYBIND11_SIMPLE_GIL_MANAGEMENT=OFF - -DPYBIND11_NUMPY2_SUPPORT=OFF + -DPYBIND11_NUMPY_1_ONLY=ON -DDOWNLOAD_CATCH=ON -DDOWNLOAD_EIGEN=ON -DCMAKE_CXX_STANDARD=17 @@ -670,7 +670,6 @@ jobs: run: > cmake -S . -B build -DCMAKE_BUILD_TYPE=MinSizeRel - -DPYBIND11_NUMPY2_SUPPORT=ON -DPYBIND11_WERROR=ON -DDOWNLOAD_CATCH=ON -DDOWNLOAD_EIGEN=ON @@ -913,7 +912,6 @@ jobs: run: > cmake -S . -B build -DPYBIND11_WERROR=ON - -DPYBIND11_NUMPY2_SUPPORT=ON -DDOWNLOAD_CATCH=ON -DDOWNLOAD_EIGEN=ON -DCMAKE_CXX_STANDARD=20 @@ -934,7 +932,6 @@ jobs: run: > cmake -S . -B build_partial -DPYBIND11_WERROR=ON - -DPYBIND11_NUMPY2_SUPPORT=ON -DDOWNLOAD_CATCH=ON -DDOWNLOAD_EIGEN=ON -DCMAKE_CXX_STANDARD=20 diff --git a/CMakeLists.txt b/CMakeLists.txt index df280d11eb..890440dc17 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -109,7 +109,8 @@ option(PYBIND11_TEST "Build pybind11 test suite?" ${PYBIND11_MASTER_PROJECT}) option(PYBIND11_NOPYTHON "Disable search for Python" OFF) option(PYBIND11_SIMPLE_GIL_MANAGEMENT "Use simpler GIL management logic that does not support disassociation" OFF) -option(PYBIND11_NUMPY2_SUPPORT "Enable compile in a NumPy 2 and 1 compatible way" OFF) +option(PYBIND11_NUMPY_1_ONLY + "Disable NumPy 2 support to avoid changes to previous pybind11 versions." OFF) set(PYBIND11_INTERNALS_VERSION "" CACHE STRING "Override the ABI version, may be used to enable the unstable ABI.") @@ -117,8 +118,8 @@ set(PYBIND11_INTERNALS_VERSION if(PYBIND11_SIMPLE_GIL_MANAGEMENT) add_compile_definitions(PYBIND11_SIMPLE_GIL_MANAGEMENT) endif() -if(PYBIND11_NUMPY2_SUPPORT) - add_compile_definitions(PYBIND11_NUMPY2_SUPPORT) +if(PYBIND11_NUMPY_1_ONLY) + add_compile_definitions(PYBIND11_NUMPY_1_ONLY) endif() cmake_dependent_option( diff --git a/include/pybind11/numpy.h b/include/pybind11/numpy.h index 2c782fd087..c2dbd0607a 100644 --- a/include/pybind11/numpy.h +++ b/include/pybind11/numpy.h @@ -70,7 +70,7 @@ struct PyArrayDescr1_Proxy { PyObject *names; }; -#ifdef PYBIND11_NUMPY2_SUPPORT +#ifndef PYBIND11_NUMPY_1_ONLY struct PyArrayDescr_Proxy { PyObject_HEAD PyObject *typeobj; @@ -170,10 +170,10 @@ PYBIND11_NOINLINE module_ import_numpy_core_submodule(const char *submodule_name object numpy_version = numpy_lib.attr("NumpyVersion")(version_string); int major_version = numpy_version.attr("major").cast(); -#ifndef PYBIND11_NUMPY2_SUPPORT +#ifdef PYBIND11_NUMPY_1_ONLY if (major_version >= 2) { - throw std::runtime_error("module compiled without NumPy 2 support. Please define " - "PYBIND11_NUMPY2_SUPPORT before including `numpy2.h` " + throw std::runtime_error("module compiled without NumPy 2 support. Do not define " + "PYBIND11_NUMPY_1_ONLY before including `numpy.h` " "or define it during build to enable NumPy 2 support."); } #endif @@ -289,7 +289,7 @@ struct npy_api { PyObject *(*PyArray_FromAny_)(PyObject *, PyObject *, int, int, int, PyObject *); int (*PyArray_DescrConverter_)(PyObject *, PyObject **); bool (*PyArray_EquivTypes_)(PyObject *, PyObject *); -#ifndef PYBIND11_NUMPY2_SUPPORT +#ifdef PYBIND11_NUMPY_1_ONLY int (*PyArray_GetArrayParamsFromObject_)(PyObject *, PyObject *, unsigned char, @@ -325,7 +325,7 @@ struct npy_api { API_PyArray_View = 137, API_PyArray_DescrConverter = 174, API_PyArray_EquivTypes = 182, -#ifndef PYBIND11_NUMPY2_SUPPORT +#ifdef PYBIND11_NUMPY_1_ONLY API_PyArray_GetArrayParamsFromObject = 278, #endif API_PyArray_SetBaseObject = 282 @@ -362,7 +362,7 @@ struct npy_api { DECL_NPY_API(PyArray_View); DECL_NPY_API(PyArray_DescrConverter); DECL_NPY_API(PyArray_EquivTypes); -#ifndef PYBIND11_NUMPY2_SUPPORT +#ifdef PYBIND11_NUMPY_1_ONLY DECL_NPY_API(PyArray_GetArrayParamsFromObject); #endif DECL_NPY_API(PyArray_SetBaseObject); @@ -673,7 +673,7 @@ class dtype : public object { } /// Size of the data type in bytes. -#ifndef PYBIND11_NUMPY2_SUPPORT +#ifdef PYBIND11_NUMPY_1_ONLY ssize_t itemsize() const { return detail::array_descriptor_proxy(m_ptr)->elsize; } #else ssize_t itemsize() const { @@ -685,7 +685,7 @@ class dtype : public object { #endif /// Returns true for structured data types. -#ifndef PYBIND11_NUMPY2_SUPPORT +#ifdef PYBIND11_NUMPY_1_ONLY bool has_fields() const { return detail::array_descriptor_proxy(m_ptr)->names != nullptr; } #else bool has_fields() const { @@ -724,7 +724,7 @@ class dtype : public object { char byteorder() const { return detail::array_descriptor_proxy(m_ptr)->byteorder; } /// Alignment of the data type -#ifndef PYBIND11_NUMPY2_SUPPORT +#ifdef PYBIND11_NUMPY_1_ONLY int alignment() const { return detail::array_descriptor_proxy(m_ptr)->alignment; } #else ssize_t alignment() const { @@ -736,7 +736,7 @@ class dtype : public object { #endif /// Flags for the array descriptor -#ifndef PYBIND11_NUMPY2_SUPPORT +#ifdef PYBIND11_NUMPY_1_ONLY char flags() const { return detail::array_descriptor_proxy(m_ptr)->flags; } #else std::uint64_t flags() const { diff --git a/tests/conftest.py b/tests/conftest.py index 1e0653901b..8ebc702224 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -218,5 +218,5 @@ def pytest_report_header(config): f" {pybind11_tests.cpp_std}" f" {pybind11_tests.PYBIND11_INTERNALS_ID}" f" PYBIND11_SIMPLE_GIL_MANAGEMENT={pybind11_tests.PYBIND11_SIMPLE_GIL_MANAGEMENT}" - f" PYBIND11_NUMPY2_SUPPORT={pybind11_tests.PYBIND11_NUMPY2_SUPPORT}" + f" PYBIND11_NUMPY_1_ONLY={pybind11_tests.PYBIND11_NUMPY_1_ONLY}" ) diff --git a/tests/pybind11_tests.cpp b/tests/pybind11_tests.cpp index cf8db84a14..f3c034343d 100644 --- a/tests/pybind11_tests.cpp +++ b/tests/pybind11_tests.cpp @@ -95,8 +95,8 @@ PYBIND11_MODULE(pybind11_tests, m) { #else false; #endif - m.attr("PYBIND11_NUMPY2_SUPPORT") = -#if defined(PYBIND11_NUMPY2_SUPPORT) + m.attr("PYBIND11_NUMPY_1_ONLY") = +#if defined(PYBIND11_NUMPY_1_ONLY) true; #else false; diff --git a/tests/test_numpy_dtypes.py b/tests/test_numpy_dtypes.py index 3e379bfda4..402b940a4a 100644 --- a/tests/test_numpy_dtypes.py +++ b/tests/test_numpy_dtypes.py @@ -3,7 +3,7 @@ import pytest import env # noqa: F401 -from pybind11_tests import PYBIND11_NUMPY2_SUPPORT +from pybind11_tests import PYBIND11_NUMPY_1_ONLY from pybind11_tests import numpy_dtypes as m np = pytest.importorskip("numpy") @@ -179,7 +179,7 @@ def test_dtype(simple_dtype): assert m.test_dtype_num() == [np.dtype(ch).num for ch in expected_chars] assert m.test_dtype_byteorder() == [np.dtype(ch).byteorder for ch in expected_chars] assert m.test_dtype_alignment() == [np.dtype(ch).alignment for ch in expected_chars] - if PYBIND11_NUMPY2_SUPPORT: + if not PYBIND11_NUMPY_1_ONLY: assert m.test_dtype_flags() == [np.dtype(ch).flags for ch in expected_chars] else: assert m.test_dtype_flags() == [ From 242300d8cf3a27819dcfd4cc9e16eac66fc68fe7 Mon Sep 17 00:00:00 2001 From: "Ralf W. Grosse-Kunstleve" Date: Tue, 19 Mar 2024 06:39:07 -0700 Subject: [PATCH 17/25] PYBIND11_INTERNAL_NUMPY_1_ONLY_DETECTED --- include/pybind11/detail/common.h | 4 ++++ include/pybind11/numpy.h | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/include/pybind11/detail/common.h b/include/pybind11/detail/common.h index 83800e960b..9f8e13e24f 100644 --- a/include/pybind11/detail/common.h +++ b/include/pybind11/detail/common.h @@ -296,6 +296,10 @@ PYBIND11_WARNING_DISABLE_MSVC(4505) # undef copysign #endif +#if defined(PYBIND11_NUMPY_1_ONLY) +# define PYBIND11_INTERNAL_NUMPY_1_ONLY_DETECTED +#endif + #if defined(PYPY_VERSION) && !defined(PYBIND11_SIMPLE_GIL_MANAGEMENT) # define PYBIND11_SIMPLE_GIL_MANAGEMENT #endif diff --git a/include/pybind11/numpy.h b/include/pybind11/numpy.h index c2dbd0607a..640c649ac7 100644 --- a/include/pybind11/numpy.h +++ b/include/pybind11/numpy.h @@ -29,6 +29,10 @@ #include #include +#if defined(PYBIND11_NUMPY_1_ONLY) && !defined(PYBIND11_INTERNAL_NUMPY_1_ONLY_DETECTED) +# error PYBIND11_NUMPY_1_ONLY must be defined before any pybind11 header is included. +#endif + /* This will be true on all flat address space platforms and allows us to reduce the whole npy_intp / ssize_t / Py_intptr_t business down to just ssize_t for all size and dimension types (e.g. shape, strides, indexing), instead of inflicting this From a6b2e117aac8ac4b72dbdef86686d244f2b9f832 Mon Sep 17 00:00:00 2001 From: Sebastian Berg Date: Fri, 22 Mar 2024 11:06:09 +0100 Subject: [PATCH 18/25] Test and code comment expansion --- include/pybind11/numpy.h | 1 + tests/test_numpy_array.py | 1 + tests/test_numpy_dtypes.cpp | 33 +++++++++++++++++++++++++++++---- tests/test_numpy_dtypes.py | 6 ++++-- 4 files changed, 35 insertions(+), 6 deletions(-) diff --git a/include/pybind11/numpy.h b/include/pybind11/numpy.h index 640c649ac7..adc65bcd7d 100644 --- a/include/pybind11/numpy.h +++ b/include/pybind11/numpy.h @@ -320,6 +320,7 @@ struct npy_api { API_PyArray_DescrFromScalar = 57, API_PyArray_FromAny = 69, API_PyArray_Resize = 80, + // CopyInto was slot 82 and 50 was effectively an alias. NumPy 2 removed 82. API_PyArray_CopyInto = 50, API_PyArray_NewCopy = 85, API_PyArray_NewFromDescr = 94, diff --git a/tests/test_numpy_array.py b/tests/test_numpy_array.py index 6f619e8075..25ad09ec3c 100644 --- a/tests/test_numpy_array.py +++ b/tests/test_numpy_array.py @@ -588,6 +588,7 @@ def test_argument_conversions(forcecast, contiguity, noconvert): def test_dtype_refcount_leak(): from sys import getrefcount + # Was np.float_ but that alias for float64 was removed in NumPy 2. dtype = np.dtype(np.float64) a = np.array([1], dtype=dtype) before = getrefcount(dtype) diff --git a/tests/test_numpy_dtypes.cpp b/tests/test_numpy_dtypes.cpp index 2141d27248..519b69f400 100644 --- a/tests/test_numpy_dtypes.cpp +++ b/tests/test_numpy_dtypes.cpp @@ -404,11 +404,36 @@ TEST_SUBMODULE(numpy_dtypes, m) { return l; }); - // test_dtype (ulong was missing, but uint default changed on windows so use L) + // test_dtype + // Below we use `L` for unsigned long as unfortunately the only name that + // works reliably on Both NumPy 2.x and old NumPy 1.x. std::vector dtype_names{ - "byte", "short", "intc", "long", "longlong", "ubyte", "ushort", - "uintc", "L", "ulonglong", "half", "single", "double", "longdouble", - "csingle", "cdouble", "clongdouble", "bool_", "datetime64", "timedelta64", "object_"}; + "byte", + "short", + "intc", + "long", + "longlong", + "ubyte", + "ushort", + "uintc", + "L", + "ulonglong", + "half", + "single", + "double", + "longdouble", + "csingle", + "cdouble", + "clongdouble", + "bool_", + "datetime64", + "timedelta64", + "object_", + // platform dependent aliases (int_ and uint are also NumPy version dependent on windows) + "int_", + "uint", + "intp", + "uintp"}; m.def("print_dtypes", []() { py::list l; diff --git a/tests/test_numpy_dtypes.py b/tests/test_numpy_dtypes.py index 402b940a4a..e7854df4a6 100644 --- a/tests/test_numpy_dtypes.py +++ b/tests/test_numpy_dtypes.py @@ -173,8 +173,10 @@ def test_dtype(simple_dtype): np.zeros(1, m.trailing_padding_dtype()) ) - expected_chars = "bhilqBHILQefdgFDG?MmO" - assert m.test_dtype_kind() == list("iiiiiuuuuuffffcccbMmO") + expected_chars = list("bhilqBHILQefdgFDG?MmO") + # Note that int_ and uint size and mapping is NumPy version dependent: + expected_chars += [np.dtype(_).char for _ in ("int_", "uint", "intp", "uintp")] + assert m.test_dtype_kind() == list("iiiiiuuuuuffffcccbMmOiuiu") assert m.test_dtype_char_() == list(expected_chars) assert m.test_dtype_num() == [np.dtype(ch).num for ch in expected_chars] assert m.test_dtype_byteorder() == [np.dtype(ch).byteorder for ch in expected_chars] From 99b785a26c6bb6d2cb81f8884c019110442e05f2 Mon Sep 17 00:00:00 2001 From: Sebastian Berg Date: Fri, 22 Mar 2024 11:35:18 +0100 Subject: [PATCH 19/25] CI: Use pre-releases of numpy/scipy from pip via explicit version --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c7ba06bc23..e3d0773f6b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -663,7 +663,7 @@ jobs: - name: Install dependencies run: | python3 -m pip install cmake -r tests/requirements.txt - python3 -m pip install --upgrade --pre -i https://pypi.anaconda.org/scientific-python-nightly-wheels/simple numpy scipy + python3 -m pip install 'numpy>=2.0.0b1' 'scipy>=1.13.0rc1' - name: Configure shell: bash @@ -903,7 +903,7 @@ jobs: # Ensure use of NumPy 2 (via NumPy nightlies but can be changed soon) run: | python3 -m pip install -r tests/requirements.txt - python3 -m pip install --upgrade --pre -i https://pypi.anaconda.org/scientific-python-nightly-wheels/simple numpy scipy + python3 -m pip install 'numpy>=2.0.0b1' 'scipy>=1.13.0rc1' - name: Update CMake uses: jwlawson/actions-setup-cmake@v2.0 From 525310fe437eb4b66ced5a8eee3f97dfcf079f88 Mon Sep 17 00:00:00 2001 From: Sebastian Berg Date: Fri, 22 Mar 2024 12:47:13 +0100 Subject: [PATCH 20/25] CI: NumPy 2 only available on almalinux (as it is Python >=3.9) --- .github/workflows/ci.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e3d0773f6b..fd44c1faaf 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -663,6 +663,10 @@ jobs: - name: Install dependencies run: | python3 -m pip install cmake -r tests/requirements.txt + + - name: Ensure NumPy 2 is used (required Python >= 3.9) + if: matrix.container == 'almalinux:9' + run: | python3 -m pip install 'numpy>=2.0.0b1' 'scipy>=1.13.0rc1' - name: Configure From 8d718ff8218d4064eafb5ff75c5aa27abd328d51 Mon Sep 17 00:00:00 2001 From: Sebastian Berg Date: Fri, 22 Mar 2024 13:40:53 +0100 Subject: [PATCH 21/25] MAINT: Match name more exactly and adopt error phrasing --- include/pybind11/cast.h | 9 ++++++++- include/pybind11/numpy.h | 6 +++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index 6aec4c5bf1..10355e37fe 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -327,7 +327,7 @@ class type_caster { value = false; return true; } - if (convert || (std::strncmp("numpy.bool", Py_TYPE(src.ptr())->tp_name, 10) == 0)) { + if (convert || is_numpy_bool(src)) { // (allow non-implicit conversion for numpy booleans), use strncmp // since NumPy 1.x had an additional trailing underscore. @@ -1245,6 +1245,13 @@ cast_safe(object &&o) { return pybind11::cast(std::move(o)); } +// Test if an object is a NumPy boolean (without fetching the type). +inline bool is_numpy_bool(handle object) { + const char *name = Py_TYPE(object.ptr())->tp_name; + // Name changed to `numpy.bool` in NumPy 2, `numpy.bool_` is needed for 1.x support + return std::strcmp("numpy.bool", name) == 0 || std::strcmp("numpy.bool_", name) == 0; +} + PYBIND11_NAMESPACE_END(detail) // The overloads could coexist, i.e. the #if is not strictly speaking needed, diff --git a/include/pybind11/numpy.h b/include/pybind11/numpy.h index adc65bcd7d..f644d86f1a 100644 --- a/include/pybind11/numpy.h +++ b/include/pybind11/numpy.h @@ -176,9 +176,9 @@ PYBIND11_NOINLINE module_ import_numpy_core_submodule(const char *submodule_name #ifdef PYBIND11_NUMPY_1_ONLY if (major_version >= 2) { - throw std::runtime_error("module compiled without NumPy 2 support. Do not define " - "PYBIND11_NUMPY_1_ONLY before including `numpy.h` " - "or define it during build to enable NumPy 2 support."); + throw std::runtime_error("This extension was built with PYBIND11_NUMPY_1_ONLY defined, + but NumPy 2 is used in this process. For NumPy2 compatibility, + this extension needs to be rebuilt without the PYBIND11_NUMPY_1_ONLY define."); } #endif /* `numpy.core` was renamed to `numpy._core` in NumPy 2.0 as it officially From e2e923da50a2c549334faa5dd3e2ad0689b79ed1 Mon Sep 17 00:00:00 2001 From: Sebastian Berg Date: Fri, 22 Mar 2024 13:47:46 +0100 Subject: [PATCH 22/25] MAINT: Pushed early, move helper to be private member --- include/pybind11/cast.h | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index 10355e37fe..09660cb3d8 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -361,6 +361,14 @@ class type_caster { return handle(src ? Py_True : Py_False).inc_ref(); } PYBIND11_TYPE_CASTER(bool, const_name("bool")); + +private: + // Test if an object is a NumPy boolean (without fetching the type). + static inline bool is_numpy_bool(handle object) { + const char *name = Py_TYPE(object.ptr())->tp_name; + // Name changed to `numpy.bool` in NumPy 2, `numpy.bool_` is needed for 1.x support + return std::strcmp("numpy.bool", name) == 0 || std::strcmp("numpy.bool_", name) == 0; + } }; // Helper class for UTF-{8,16,32} C++ stl strings: @@ -1245,13 +1253,6 @@ cast_safe(object &&o) { return pybind11::cast(std::move(o)); } -// Test if an object is a NumPy boolean (without fetching the type). -inline bool is_numpy_bool(handle object) { - const char *name = Py_TYPE(object.ptr())->tp_name; - // Name changed to `numpy.bool` in NumPy 2, `numpy.bool_` is needed for 1.x support - return std::strcmp("numpy.bool", name) == 0 || std::strcmp("numpy.bool_", name) == 0; -} - PYBIND11_NAMESPACE_END(detail) // The overloads could coexist, i.e. the #if is not strictly speaking needed, From c4bd0e0f566b64c8df16dc7f96143c0d353c667c Mon Sep 17 00:00:00 2001 From: Sebastian Berg Date: Fri, 22 Mar 2024 14:08:24 +0100 Subject: [PATCH 23/25] fix error message compilation when using NumPy 1.x-only backcompat --- include/pybind11/numpy.h | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/include/pybind11/numpy.h b/include/pybind11/numpy.h index f644d86f1a..17267800cb 100644 --- a/include/pybind11/numpy.h +++ b/include/pybind11/numpy.h @@ -176,9 +176,10 @@ PYBIND11_NOINLINE module_ import_numpy_core_submodule(const char *submodule_name #ifdef PYBIND11_NUMPY_1_ONLY if (major_version >= 2) { - throw std::runtime_error("This extension was built with PYBIND11_NUMPY_1_ONLY defined, - but NumPy 2 is used in this process. For NumPy2 compatibility, - this extension needs to be rebuilt without the PYBIND11_NUMPY_1_ONLY define."); + throw std::runtime_error( + "This extension was built with PYBIND11_NUMPY_1_ONLY defined, " + "but NumPy 2 is used in this process. For NumPy2 compatibility, " + "this extension needs to be rebuilt without the PYBIND11_NUMPY_1_ONLY define."); } #endif /* `numpy.core` was renamed to `numpy._core` in NumPy 2.0 as it officially From 38f8ead608b8023fdef9cca1aa9794467584e02d Mon Sep 17 00:00:00 2001 From: Sebastian Berg Date: Fri, 22 Mar 2024 14:17:20 +0100 Subject: [PATCH 24/25] silence name shadowing warning --- include/pybind11/cast.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index 09660cb3d8..3f3f966d08 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -365,9 +365,10 @@ class type_caster { private: // Test if an object is a NumPy boolean (without fetching the type). static inline bool is_numpy_bool(handle object) { - const char *name = Py_TYPE(object.ptr())->tp_name; + const char *type_name = Py_TYPE(object.ptr())->tp_name; // Name changed to `numpy.bool` in NumPy 2, `numpy.bool_` is needed for 1.x support - return std::strcmp("numpy.bool", name) == 0 || std::strcmp("numpy.bool_", name) == 0; + return std::strcmp("numpy.bool", type_name) == 0 + || std::strcmp("numpy.bool_", type_name) == 0; } }; From f3af194b914f17e90d183f26799b89bb588d1ed0 Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Tue, 26 Mar 2024 17:16:58 -0400 Subject: [PATCH 25/25] chore: minor optimization Signed-off-by: Henry Schreiner --- include/pybind11/numpy.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/include/pybind11/numpy.h b/include/pybind11/numpy.h index 17267800cb..12a0490e29 100644 --- a/include/pybind11/numpy.h +++ b/include/pybind11/numpy.h @@ -698,10 +698,11 @@ class dtype : public object { if (detail::npy_api::get().PyArray_RUNTIME_VERSION_ < 0x12) { return detail::array_descriptor1_proxy(m_ptr)->names != nullptr; } - if (num() < 0 || num() >= 2056) { + const auto *proxy = detail::array_descriptor2_proxy(m_ptr); + if (proxy->type_num < 0 || proxy->type_num >= 2056) { return false; } - return detail::array_descriptor2_proxy(m_ptr)->names != nullptr; + return proxy->names != nullptr; } #endif